Хабрахабр

[Перевод] Редактирование текста тоже вас ненавидит

Опубликованная месяц назад статья Алексис Бингесснер «Рендеринг текста вас ненавидит» очень мне близка.

Неудовлетворённый существующими библиотеками на ContentEditable, я подумал: «Эй, да просто заново реализую выделение текста! В далёком 2017 году я разрабатывал интерактивный текстовый редактор в браузере. Наивен. Разве это сложно?» Я был молод. На самом деле попытка решить эту проблему отняла несколько лет моей жизни, в том числе год оплачиваемой работы с утра до вечера по разработке текстового редактора для новой ОС. Прикинул, что справлюсь за две недели.

Я слышал много, очень много страшных историй. На работе мне посчастливилось многое узнать у наставников с огромным опытом в этой области. Вот список интерфейсов для ввода текста в этой новой версии: В том числе об инженере, который поддерживал приложение Windows с кастомной реализацией текстового поля — и хотел перейти с устаревшего API ввода текста на новую версию.

Почти уверен, что есть ещё восемь (8!) различных типов блокировок для устранения проблем параллелизма, хотя честно не читал их документацию, поэтому не цитируйте меня по этому поводу.
Всё правильно, 128 интерфейсов для ввода текста. Тот инженер полтора года (полный рабочий день!) дорабатывал свой редактор, но в итоге потерпел неудачу и остался на старом API.

Ввод текста — это сложно.

Как человек с той стороны, могу добавить несколько моментов именно про ввод. Алексис местами упоминает про выделение текста, но её личный опыт больше связан с рендерингом.

Вертикальное перемещение курсора

Я уже освещал это в предыдущей статье, но можем быстро повторить здесь.

Пока всё довольно разумно. В этом примере, если нажать вверх, курсор уйдёт в начало строки, перед словом hello. Но если нажать вверх, а затем вниз, курсор сначала прыгнет перед hello, а затем встанет после some.

Вы спросите, почему он прыгает вправо? Это может показаться не очень логичным. То же самое поведение предотвращает перемещение курсоров влево при вертикальном перемещении через короткие строки. Ну, при вертикальных перемещениях каждый курсор запоминает позицию x в пикселях, и она обновляется только при нажатии влево или вправо, а не вверх и вниз.

Близость

Ладно, теперь мы знаем, что при выделении текста у нас два фрагмента состояния: байтовое смещение внутри строки и координата x в пикселях, упомянутая выше. Проблема решена? Ну, нет.

Рассмотрим две позиции курсора на очень длинной строке:

Между ними нет символа новой строки, так как строка мягко переносится. Поскольку loooooooooong — это одно слово, у двух позиций курсора в точности одинаковое байтовое смещение в строке. Большинство систем называют этот бит affinity (близость). Нашим курсорам нужен дополнительный бит, который скажет, на какую строку перенестись. Он же используется в смешанном двунаправленном тексте, про который мы скоро поговорим.

Модификаторы эмодзи

Допустим, я отправляю другу сообщение. Для выражения своих чувств хочу добавить забавный эмодзи. Ввожу в текстовой области поднятый вверх большой палец, букву a и модификатор эмодзи для тона кожи. Это выглядит так:

Устанавливаю курсор после неё и нажимаю Backspace. Ой, не хотел писать букву. Я видел несколько вариантов, в зависимости от редактора. Что произойдёт?

  • Плохо №1 может показаться правильным. Но так работает текстовый редактор с поддержкой устаревшего рендеринга эмодзи, например, Sublime Text. Это плохо, потому что эмодзи светлого пальца кодируется как жёлтый палец, за которым сразу следует модификатор светлого тона кожи. Они не объединяится в один символ, как положено. Даже если я скопирую светлый палец из другого приложения, он всё равно отобразится неправильно, как здесь.
  • Плохо №2 — это то, что Chrome 77 делает в адресной строке. Не на веб-страницах, а только в адресной строке. Это не проблема рендеринга, так как копипаст эмодзи с тоном кожи работает. Вместо этого Chrome удаляет букву, а заметив следующий за буквой модификатор, заодно удаляет и его. Упс.
  • Плохо №3 соответствует спецификации Юникода, как положено сливать эмодзи. Но это довольно непонятно для пользователей, и, кстати, нужно сдвинуть курсор, чтобы он не застрял на полпути внутри эмодзи.

Все варианты плохие, поэтому вы можете предположить, что наверное есть какой-то четвёртый вариант. Есть! Многие редакторы, такие как TextEdit, даже не позволяет поставить курсор после буквы, так как модификатор тона кожи рассматривается как единое целое с предыдущим символом. Это имеет смысл в контексте эмодзи и даже хорошо работает в данном случае, но что если модификатор указан первым символом в строке?

TextEdit не позволит поместить курсор в начале второй строки! Теперь модификатор изменяет символ новой строки. Я лично считаю это решение «тоже плохим».

Это я сам сделал, чтобы отразить свои чувства по поводу всей ситуации. Возможно, вы также заметили, что большой палец вверх стал большим пальцем вниз.

Например, угадайте, что произойдёт, если я здесь нажму 4? Кстати, TextEdit специально делает курсор на первой строке очень глючным.

Вы также можете подумать, что между цифрами есть пробелы. Угу. Их нет.

Двунаправленный текст

Алексис упоминает разделённые выделения в смешанном двунаправленном тексте, как в этом примере из TextEdit:

Это действительно имеет смысл, поскольку арабский язык в строках кодируется справа налево, так что выделение кажется разделённым, но по байтам представляет собой непрерывный диапазон.

Поэтому немного удивительно, что мы можем получить такое выделение:

Да, плохо. Да, это визуально непрерывное, но разделённое по байтам выделение. Альтернатива — поменять местами клавиши влево/вправо внутри текста с направлением справа налево, что тоже плохо. Так делают некоторые редакторы, если выделять текст клавишами со стрелками вместо мыши. Здесь нет хороших вариантов.

В качестве бонуса, попробуйте понять, что происходит здесь:

Господи… не хочу это комментировать.

Дело в методах ввода

Программное обеспечение, которое переводит нажатия клавиш во ввод, называется «метод ввода» (input method) или «редактор метода ввода» (input method editor). Для латинского алфавита это не очень интересное ПО, так как каждое нажатие клавиши напрямую сопоставляется с вставкой одного символа. Но во многих письменностях символы не помещаются на клавиатуру, поэтому приходится проявлять творческий подход. Например, в некоторых методах ввода для китайского языка пользователь вводит звуки — и получает список похожих по звучанию иероглифов:

Иногда метод ввода должен её стилизовать. Это поле иногда называют композиционной областью (composing region), и она часто появляется над подчёркнутым текстом. Например, метод ввода японского языка на Android использует цвет фона для создания области разделения предложений:

(Спасибо Shae за скриншот!)

Давайте не будем об этом думать. Взаимодействуют ли все эти выделения и композиционные области с двунаправленным текстом?

Методы ввода должны работать везде, даже внутри терминала:

Вероятно, вы думаете: «Но как это работает в командном режиме Vim?» Не очень хорошо. Ничего не отправится в Vim, пока не выбран китайский иероглиф из списка. В консоли они смешиваются, вызывая проблемы. Вот почему в интернете ввод текста и нажатия клавиш являются отдельными событиями.

(Не забывайте о методах ввода без клавиатуры, таких как голосовой и рукописный ввод!) К счастью, операционная система предоставляет вам все эти методы. Это всего лишь один пример из множества различных способов ввода текста. Для Windows это те 128 интерфейсов, перечисленные в начале статьи. Но, к сожалению, ваше текстовое поле должно говорить на общем протоколе ввода текста, используемом всеми этими методами. В других ОС интерфейсы попроще, но их всё равно сложно реализовать.

Это фактически параллельный протокол редактирования. Вы также могли заметить, что метод ввода — отдельный процесс, так что в состояние текстового поля могут вносить изменения и метод ввода, и приложение. Хотя удержание блокировки через границы процесса может показаться сомнительным, большинство других платформ для устранения проблем параллелизма пытаются использовать несовершенные эвристики. Windows решает проблему с помощью восьми (8!) видов блокировки. По моему опыту, молитва — не очень эффективный примитив параллелизма. Или просто надеются, что состояние гонки не произойдёт.

Почему всё так сложно??

Джонатан Блоу в лекции о деградации софта упоминает текстовый редактор Кена Томпсона, который он написал за неделю. Большая часть кода в этой статье — случайно привнесённая сложность. Действительно ли Windows нужно 128 интерфейсов и 8 видов блокировок для ввода текста? Ни в коем случае. Являются ли ошибки в TextEdit результатом сложной модели редактирования? Да. Является ли россыпь багов в современных программах чем-то, о чём следует беспокоиться? По крайней мере, для меня это так.

Юникод поддерживает почти все живые языки в мире (их около 7000), и ещё много мёртвых. Однако редактор Кена Томпсона был и намного, намного проще, чем то, что мы ожидаем от современных текстовых редакторов. А ведь он должен ещё и поддерживать программы чтения с экрана. Там разные письменности, направления текста и методы ввода, каждый из которых накладывает сложные (и в некоторых случаях неразрешимые) ограничения на любой редактор.

Это настоящее чудо программирования, что можно просто шлёпнуть <textarea> на веб-странице — и мгновенно обеспечить ввод текста для каждого пользователя интернета по всему миру. Огромная сложность накапливается неизбежно, а в этой статье мы только слегка её тронули.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть