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 прохода генерации глифа:

  1. Один проход для отредактированного диапазона, в случае набора текста для каждого нажатия клавиши с NSRange длины 1, отправив edited уведомление с обоими [.editedCharacters, .editedAttributes] первый отвечает за перемещение каретки;
  2. Еще один проход для любого диапазона зависит от подсветки синтаксиса, отправка 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()
    }
Другие вопросы по тегам