NSTextStorageDelegate's textStorage(_,willProcessEditing:,range:,changeInLength:) перемещает выделение
Я пытаюсь реализовать текстовый редактор, окрашивающий синтаксис, который также делает такие вещи, как вставка пробела в начале новой строки или замена текста текстовыми вложениями.
После повторного просмотра документов после того, как в предыдущей реализации возникли проблемы с отменой, кажется, что рекомендуемым узким местом для этого является метод textStorage NSTextStorageDelegate(_,willProcessEditing:,range:,changeInLength:) (который утверждает, что Delegates can change the characters or attributes.
, в то время как didProcessEditing
говорит, что я могу только изменить атрибуты). Это работает хорошо, за исключением того, что всякий раз, когда я фактически изменяю атрибуты или текст, метка вставки текста перемещается в конец любого диапазона текста, который я изменяю (поэтому, если я изменяю стиль всей строки, курсор перемещается в конец строки).
Кто-нибудь знает, какие дополнительные вызовы я пропускаю, которые говорят NSTextStorage/NSTextView не испортить знак вставки? Кроме того, после того, как я вставлю текст, мне, возможно, придется сказать ему, чтобы переместить метку вставки для учета текста, который я вставил.
Примечание: я видел, что изменение NSTextStorage приводит к смещению точки вставки в конец строки, но это предполагает, что я подклассифицирую NSTextStorage, поэтому я не могу использовать решение там (и предпочел бы не подкласс NSTextStorage, так как это полу - абстрактный подкласс, и я бы потерял некоторые виды поведения класса Apple, если бы я его подклассил).
2 ответа
Я выяснил источник проблемы.
И единственное решение, которое будет работать надежно, исходя из причин, присущих инфраструктуре Какао, а не просто обходных путей. (Обратите внимание, что, вероятно, существует, по крайней мере, еще один метастабильный подход, основанный на куче быстрых исправлений, который дает аналогичный результат, но с появлением метастабильных альтернатив он будет очень хрупким и потребует множества усилий для его обслуживания.)
TL; DR Проблема:
NSTextStorage
улавливаетсяedited
вызывает и комбинирует диапазоны, начиная с изменения, измененного пользователем (например, вставка), затем добавляя все диапазоны изaddAttributes(_:range:)
звонки во время выделения.TL; DR Решение: выполнить выделение из
textDidChange(_:)
исключительно.
подробности
Это относится только к одному processEditing()
бежать, как в NSTextStorage
подклассы и в NSTextStorageDelegate
Обратные вызовы.
Единственный безопасный способ выполнить выделение, которое я нашел, это подключиться к NSText.didChangeNotification
или реализовать NSTextDelegate.textDidChange(_:)
,
Согласно комментариям @Willeke к вопросу OP, это лучшее место для внесения изменений после прохождения макета. Но в отличие от комментария, настройка обратно NSText.selectedRange
не хватает. Вы не заметите проблему после исправления выбора после того, как каретка отодвинулась до
- Вы выделяете целые блоки текста,
- охватывающий несколько строк, и
- превышение видимого (
NSClipView
) границы представления прокрутки.
В этом редком случае большинство нажатий клавиш заставляют представление прокрутки колебаться или подпрыгивать. Но нет никаких дополнительных быстрых решений против этого. Я старался. Также не запрещается отправка команд прокрутки из частного API в NSLayoutManager
ни избегая прокрутки, переопределяя все методы с "прокруткой" в них из NSTextView
Подкласс работает хорошо. Конечно, вы можете полностью прекратить прокрутку к точке вставки, но не повезло, если вы получили надежный алгоритм, который не прокручивает только при выполнении выделения.
didChangeNotification
Подход работает надежно во всех ситуациях, которые мне и тестировщикам моего приложения удалось придумать (включая аварийную ситуацию, такую же странную, как прокрутка текста, а затем, во время анимации, заменить строку на что-то более короткое - да, постарайтесь понять, что такие вещи из журналов сбоев, которые сообщают о недопустимой генерации глифов...).
Этот подход работает, потому что он делает 2 прохода генерации глифа:
- Один проход для отредактированного диапазона, в случае набора текста для каждого нажатия клавиши с
NSRange
длины 1, отправивedited
уведомление с обоими[.editedCharacters, .editedAttributes]
первый отвечает за перемещение каретки; - Еще один проход для любого диапазона зависит от подсветки синтаксиса, отправка
edited
уведомление с[.editedAttributes]
только, таким образом, не влияя на положение каретки вообще.
Еще больше деталей
Если вы хотите узнать больше об источнике проблемы, я добавлю свои исследования, различные подходы и подробности решения в гораздо более длинное сообщение в блоге для справки. Это здесь, однако, само решение. http://christiantietze.de/posts/2017/11/syntax-highlight-nstextstorage-insertion-point-change/
Приведенный выше принятый ответ с центром уведомлений сработал для меня, но мне пришлось включить еще одну вещь при редактировании текста. (Что может отличаться от выбора).
The
editedRange
принадлежащий
NSTextStorage
был удар после обратного вызова центра уведомлений. Поэтому я сам отслеживаю последнее известное значение, переопределяя
processEditing
функцию и использовать это значение позже, когда я получу обратный вызов.
override func processEditing() {
// Hack.. the editedRange property when reading from the notification center callback is weird
lastEditedRange = editedRange
super.processEditing()
}