Подклассы NSTextStorage прерывает редактирование списка - PullRequest
0 голосов
/ 21 ноября 2018

У меня есть базовое приложение Mac со стандартным NSTextView.Я пытаюсь реализовать и использовать подкласс NSTextStorage, но даже очень простая реализация нарушает поведение редактирования списка:

  1. Я добавляю маркированный список с двумя элементами
  2. Iскопируйте и вставьте этот список далее в документ
  3. Нажатие Enter в вставленном списке разрывает форматирование последнего элемента списка.

Вот краткое видео:

NSTextStorage list issue

Два вопроса:

  1. Точки маркера вставленного списка используют меньший размер шрифта
  2. Нажатие клавиши Enter после второгоэлемент списка разбивает третий элемент

Это прекрасно работает, когда я не заменяю хранилище текста.

Вот мой код:

ViewController.swift

@IBOutlet var textView:NSTextView!

override func viewDidLoad() {
   [...]
   textView.layoutManager?.replaceTextStorage(TestTextStorage())
}

TestTextStorage.swift

class TestTextStorage: NSTextStorage {

    let backingStore = NSMutableAttributedString()

    override var string: String {
        return backingStore.string
    }

    override func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [NSAttributedString.Key:Any] {
        return backingStore.attributes(at: location, effectiveRange: range)
    }

    override func replaceCharacters(in range: NSRange, with str: String) {
        beginEditing()
        backingStore.replaceCharacters(in: range, with:str)
        edited(.editedCharacters, range: range,
               changeInLength: (str as NSString).length - range.length)
        endEditing()
    }

    override func setAttributes(_ attrs: [NSAttributedString.Key: Any]?, range: NSRange) {
        beginEditing()
        backingStore.setAttributes(attrs, range: range)
        edited(.editedAttributes, range: range, changeInLength: 0)
        endEditing()
    }
}

1 Ответ

0 голосов
/ 23 ноября 2018

Вы нашли ошибку в Swift (и, возможно, не только в библиотеках Swift, может быть, в чем-то более фундаментальном).

Так что же происходит?

Вы сможете увидеть это немного лучше, если создадите нумерованный список, а не маркированный список.Вам не нужно делать никаких копий и вставок, просто:

  1. Введите «aa», нажмите return, введите «bb»
  2. Выберите все и отформатируйте как нумерованный список
  3. Поместите курсор в конец «aa» и нажмите «Return» ...

То, что вы видите, - беспорядок, но вы можете видеть, что два исходных числа все еще там иНовый средний элемент списка, который вы начали, нажимая return, - это место, где весь беспорядок.

Когда вы нажимаете return, система текста должна перенумеровать элементы списка, поскольку вы только что вставили новый элемент.Во-первых, оказывается, что он выполняет это «перенумерацию», даже если это маркированный список, именно поэтому вы видите беспорядок в своем примере.Во-вторых, он выполняет перенумерацию, начиная с начала списка и пронумеровывая каждый элемент списка и вставляя новый номер для только что созданного элемента.

Процесс в задаче-C

Если вы переведете свой код Swift в эквивалентный Objective-C и проследите за ним, вы сможете наблюдать за процессом.Начиная с:

1) aa
2) bb

внутренний буфер выглядит примерно так:

\t1)\taa\n\t2)\tbb

сначала вставляется возврат:

\t1)\taa\n\n\t2)\tbb

, а затем внутренняя подпрограмма _reformListAtIndex: вызывается и начинается «перенумерация».Сначала он заменяет \t1)\t на \t1) - номер не изменился.Затем он вставляет \t2)\t между двумя новыми строками, так как на данный момент мы имеем:

\t1)\taa\n\t2)\t\n\t2)\tbb

, а затем заменяет исходный \t2)\t на \t3)\t, давая:

\t1)\taa\n\t2)\t\n\t3)\tbb

и работа сделана.Все эти замены основаны на указании диапазона символов для замены, вставка использует диапазон длины 0 и проходит:

- (void)replaceCharactersInRange:(NSRange)range withString:(NSString * _Nonnull)str

, который в Swift заменяется на:

override func replaceCharacters(in range: NSRange, with str: String)

Процесс в Swift

В строках Objective-C есть ссылочная семантика , измените строку и все части кода со ссылкой на строку, см.менять.В Swift строки имеют семантику значений , а строки копируются (по крайней мере условно) при передаче в функции и т. Д .;если копия изменяется в вызываемой функции, вызывающая сторона не увидит это изменение в своей копии.

Текстовая система была написана в (или для) Objective-C, и разумно предположить, что она может воспользоватьсясемантика ссылки.Когда вы заменяете часть своего кода на Swift, код Swift должен немного потанцевать, на этапе перенумерации списка, когда вызывается replaceCharacters(), стек будет выглядеть примерно так:

#0  0x0000000100003470 in SwiftTextStorage.replaceCharacters(in:with:)
#1  0x0000000100003a00 in @objc SwiftTextStorage.replaceCharacters(in:with:) ()
#2  0x00007fff2cdc30c7 in -[NSMutableAttributedString replaceCharactersInRange:withAttributedString:] ()
#3  0x00007fff28998c41 in -[NSTextView(NSKeyBindingCommands) _reformListAtIndex:] ()
#4  0x00007fff284fd555 in -[NSTextView(NSKeyBindingCommands) insertNewline:] ()

Frame # 4 isкод Objective-C, вызываемый при нажатии return, после вставки новой строки вызывает внутреннюю подпрограмму _reformListAtIndex:, кадр № 3, чтобы выполнить перенумерацию.Это вызывает другую подпрограмму Objective-C в кадре № 2, которая, в свою очередь, вызывает кадр № 1, что, по его мнению, является методом Objective-C replaceCharactersInRange:withString:, но на самом деле является заменой Swift.Эта замена немного танцует, преобразуя ссылочные семантические строки Objective-C в строки семантики значений Swift, а затем вызывает кадр № 0, Swift replaceCharacters().

Танцы жесткие

Если вы проследите свой код Swift так же, как вы делали перевод Objective-C, когда перенумерация переходит к этапу изменения оригинала с \t2)\t на \t3)\t, вы увидите ошибку, диапазон, заданный для оригинала \t2)\t - это то, что было до того, как новый \t2)\t был вставлен на предыдущем шаге (т.е. он отключен на 4 позиции) ... и вы получите беспорядок и еще несколько танцевальных шагов позжекод вылетает со строковой ссылкой на ошибку, так как все индексы неверны.

Это говорит о том, что код Objective-C опирается на эталонную семантику, и хореограф танца Swift, преобразующего ссылку на значение и обратно в эталонную семантику, не смог удовлетворить ожидания кода Objective-C: так что либо когда Objective-Код C или некоторый код Swift, который заменил его, вычисляет диапазон исходного \t2)\t, который он делает для строки, которая не была изменена предыдущей вставкой нового \t2)\t.

Смущенный?Хорошо, танцы иногда могут вызвать у вас головокружение; -)

Исправить?

Кодировать ваш подкласс NSTextStorage в Objective-C, перейти к bugreport.apple.com и сообщитьошибка.

HTH (больше, чем у вас кружится голова)

...