что такое символ в текстовом редакторе
Что такое символ для текстового редактора?
Что такое символ для текстового редактора?
В зависимости от кодировки символ представляется в виде набора байт или бит.
Например, в кодировке Unicode символ представляется четверкой шестнадцатеричного кода, например как 02F7.
Причём тут биты и текстовый редактор.
В текстовом редакторе (не важно, какая кодировка используется) понимают любой символ, который выводится на экран и на печать.
Это может быть буква, цифра, знак препинания, знак арифметического действия, скобка, пробел, символ перехода на новую строку или символ табуляции.
Скажите пожалуйста, что такое текстовый документ?
Скажите пожалуйста, что такое текстовый документ?
А я учусь во втором классе.
В ТПО работа со словарём.
А ещё что такое текстовый редактор?
Первая строка окна текстового редактора называется ________________________________________________?
Первая строка окна текстового редактора называется ________________________________________________.
Под ней располагается _____________ _______.
Большую часть окна текстового редактора занимает _________________ ________.
Слева и над рабочим полем располагаются_________________________.
Снизу и справа от рабочего поля находится ________________________ _________________, которые позволяют просматривать весь текст, если его размеры больше размеров окна текстового редактора.
То место, куда будет вводится следующий символ, указывает ___________________.
Для изменения размера символа или типа символа существует соответствующее изображение инструмента, которое находится на _ ________________________ ________________________.
Для ввода в текстовый документ некоторых знаков математических операций, букв греческого алфавита, денежных знаков и многих других символов используют таблицы символов?
Для ввода в текстовый документ некоторых знаков математических операций, букв греческого алфавита, денежных знаков и многих других символов используют таблицы символов.
В текстовом редакторе WORD таблицу символов можно вызвать следующей командой.
Что такое символ для текстового редактора?
Что такое символ для текстового редактора?
Помогите пожалуйста Запишите ответы на следующие вопросы?
Помогите пожалуйста Запишите ответы на следующие вопросы.
1. Что такое слово для текстового редактора как исполнителя работы с текстами?
2. Что такое абзац для текстового редактора как исполнителя работы с текстами?
3. Что такое строка для текстового редактора как исполнителя работы с текстами?
4. Назовите основной режим работы текстового редактора.
5. Что такое редактирование?
6. Перечислите основные операции с текстовыми файлами.
Что такое текстовый редактор?
Что такое текстовый редактор.
Помогите пожалуйста Запишите ответы на следующие вопросы?
Помогите пожалуйста Запишите ответы на следующие вопросы.
1. Что такое слово для текстового редактора как исполнителя работы с текстами?
2. Что такое абзац для текстового редактора как исполнителя работы с текстами?
3. Что такое строка для текстового редактора как исполнителя работы с текстами?
4. Назовите основной режим работы текстового редактора.
5. Что такое редактирование?
6. Перечислите основные операции с текстовыми файлами.
1. Что такое текстовый редактор?
1. Что такое текстовый редактор?
2. Чем текстовый процессор отличается от текстового редактора?
3. Назовите основные структурные единицы текста.
4. Назовите стандартные компоненты среды текстового редактора.
Чтобы можно было мотать вверх и вниз и вправо и влево если текст слишком большой.
Если отношение предмета к предмету то потому что разные предметы по разному реагируют друг на друга или я хз.
32 байта в кодировке ANSII.
Если 512 Кбит / с то это значит скорость закачки 64 кбайт в секунду, и тогда гдето через 5 часов.
Что такое символ в текстовом редакторе
Часто интерактивные текстовые редакторы содержат дополнительную функциональность, призванную автоматизировать действия по редактированию, или отображают текстовые данные специальным образом (например, с подсветкой синтаксиса ).
Также нужно упомянуть удобный интерфейс, позволяющий быстро освоить приложение. Казалось бы, зачем искать что-то еще, но… есть одно «но». Microsoft Word – не бесплатное приложение. Конечно, тем, для кого работа на дому в интернете, к примеру, по набору текста стала источником стабильного и достаточно высокого дохода, имеет смысл купить этот редактор. Но, если человек использует подобное ПО достаточно редко, можно выбрать что-то похожее, только бесплатно.
Текстовый редактор LibreOffice Writer.
LibreOffice Writer – на данный момент это самый мощный среди бесплатных текстовых редакторов. Он позволяет работать с документами Microsoft Word, RTF, создавать HTML документы. В нем также можно вставлять в тексты таблицы, картинки, мультимедийные объекты и другие элементы. В LibreOffice Writer имеется редактируемый словарь и функция проверки орфографии. Интерфейс программы напоминает ранние версии Word, поэтому освоить его несложно. Тем более что есть русская версия приложения. Одним словом, этот редактор можно смело назвать бесплатным аналогом или упрощенной версией Microsoft Word. Есть и другие бесплатные приложения (AbiWord, OpenOffice), но, судя по отзывам пользователей, им далеко до LibreOffice Writer.
Текстовый редактор Блокнот.
Блокнот – это самый простой текстовый редактор, который входит в стандартный пакет установки системы Windows. Он работает с расширением TXT, но может открывать файлы INF, INI, LOG.
Тем не менее, Блокнот полезен не только начинающим, но и опытным пользователям, как простой и удобный вспомогательный инструмент. Вот лишь некоторые возможности этой программы:
Редактор текста Google, позволяющий печатать текст онлайн бесплатно.
По своим функциональным возможностям редактор текста Google – это что-то среднее между Microsoft Word и Блокнотом. Он поддерживает несколько текстовых форматов (DOCX, RTF, TXT), а также HTML, PDF. В нем можно форматировать тексты, использовать разные шрифты и стили, менять цвет текста, вставлять таблицы, рисунки, формулы, ссылки, специальные символы, номера страниц, сноски и комментарии, осуществлять поиск и проверку орфографии (редактор подчеркивает слова с ошибками и предлагает варианты их написания). Еще одна уникальная функция – это перевод текста на разные языки. Переведенный текст открывается в новом окне, что позволяет сравнить его с оригиналом.
Все документы автоматически сохраняются в разделе «Мой диск», где их можно оставить, если тексты еще нужны, или скачать на компьютер. Кстати, все это можно делать с мобильного телефона.
Бесплатный текстовый редактор Notepad для программистов и веб-мастеров.
Есть еще один редактор, о котором хотелось бы упомянуть, так как сам им пользуюсь. Это Notepad, который является аналогом блокнота и ориентирован на работу с исходным кодом PHP и Html. Он является незаменимым инструментом для блогеров и тех, кого интересует создание сайтов самостоятельно, и кто уже сталкивался с проблемой чистки и редактирования кода.
Приложение распространяется бесплатно, скачать текстовой редактор Notepad можно на сайте разработчиков. Программа очень легкая и обеспечивает максимальную скорость работы. К сожалению, подробно рассказать о редакторе в этом материале не получится, отмечу лишь некоторые особенности:
По статистике, редактором Notepad Plus пользуются до 70% Web-мастеров.
Пожалуй, на этом можно и завершить краткий обзор самых популярных текстовых редакторов. Желаю всем удачи и успехов!
ТЕМА 3.2. ФОРМАТИРОВАНИЕ ОБЪЕКТОВ ТЕКСТА
Оглавление
3.2.1. Символ
Текст составляет основу большинства текстовых документов. Любой текст является информационным объектом, состоящим из совокупности символов. Из символов составляются слова. Слова располагаются по строкам, образуя абзацы и страницы. Все эти объекты имеют параметры и для них используются соответствующие технологические приемы форматирования. При подготовке текста очень важно понимать, с каким объектом вы работаете, и какие параметры имеет этот объект. Тогда можно более продуктивно использовать приемы автоматизации подготовки документа.
Среда Word распознает отдельные элементы текста – символ, слово, строка, абзац, список. Из них символ, абзац и список являются объектами, которые имеют определенный набор параметров форматирования.
Символ – элементарная неделимая единица текста. Для ввода символов используется клавиатура.
Символы разделяются на следующие группы:
Символы набираются либо простым нажатием соответствующей клавиши, либо при одновременном с ней нажатии клавиши Shift. Так, например, строчные символы и цифры, а также некоторые знаки набираются простым нажатием соответствующей клавиши клавиатуры. А для набора прописных букв и знаков, расположенных на цифровых клавишах, надо дополнительно нажать клавишу Shift.
На клавишах клавиатуры обозначено по два (и даже по три) символа. Одни символы набираются при переключении раскладки клавиатуры на режим «Русский», другие – при переключении на английскую раскладку клавиатуры. Некоторые символы (например, цифры и некоторые знаки) имеют одну и туже клавишу в обеих раскладках.
На клавиатуре располагается также отдельная группа цифровых клавиш и арифметических знаков. Эти клавиши дублируют назначение соответствующих клавиш основной клавиатуры. Чтобы использовать клавиши этой группы, надо включить режим «NumLock», нажав на одноименную клавишу, расположенную в этой группе.
Специальные символы
Основная клавиатура имеет 47 клавиш, которые позволяют ввести в общей сложности 167 различных символов в русской и английской раскладках. Но, возможно, вам приходилось встречать в текстовых документах символы, которые не отображены на клавишах клавиатуры.
Например, в тексте могут встречаться слова немецкого, шведского, финского и других языков, содержащие буквы с надстрочными (или подстрочными) знаками, которые используются в национальных алфавитах – , U, G, Ã и др. Надстрочные и подстрочные знаки, используемые у некоторых символов национальных алфавитов, называются диакритическими знаками.
Есть символы, обозначающие денежные единицы других государств – £ (английский фунт), ¥ (японская иена), € (евро), (итальянская лира) и др.
Есть экономические, юридические символы – © (авторское право), ™ (торговая марка), ® (охраняемый знак) и др.
Есть грамматические символы, которые в тексте имеют специальное назначение. Рассмотрим грамматические знаки «дефис» и «тире». Дефис – символ, связывающий две части слова или два слова, которые читаются как одно, например: кто-то, темно-синий, из-за и др. Дефис не отделяется от соседних букв пробелами. Если слово с дефисом не будет полностью помещаться на одной строке, то среда текстового процессора разобьет его по дефису на две части. И тогда при чтении дефис будет восприниматься как перенос. Годы, разделенные чертой, например, 1941-1945, также плохо будут восприниматься при чтении, если окажутся на разных строках. Чтобы исключить нежелательный перенос в словах используется специальный символ «неразрывный дефис».
Тире — это знак, разделяющий две части предложения. В отличие от дефиса тире обязательно отделяется пробелами от соседних слов. В печатных изданиях дефис обозначается короткой горизонтальной чертой, а тире – длинной. Но на компьютерной клавиатуре есть только одна клавиша, которая может использоваться и как знак «минус», и как «дефис», и как «тире». Поэтому для написания тире используется специальный символ «длинное тире».
То, что слова отделяются друг от друга пробелом, знают все. Среда текстового процессора распределяет слова по строкам. Если слово полностью не помещается на одной строке, то оно переносится на следующую строку. Однако существуют текстовые фразы, для которых нежелательно, чтобы слова располагались на разных строках. Например, плохо воспринимается текст, если буквы имени и отчества останутся на одной строке, а фамилия перейдет на следующую (А.С. Пушкин). Так же плохо будет смотреться фраза «2004 г.», если буква «г» окажется на новой строке. Чтобы избежать таких ситуаций существует специальный символ «неразрывный пробел».
Для того чтобы ввести в текст эти и другие специальные символы, которые не отображены на клавишах, используется команда Вставка/Символ (Рис. 1).
На Рис. 1 изображено диалоговое окно команды. На вкладке Символы можно просмотреть все символы, допускаемые для ввода в среде Word. В раскрывающемся списке Шрифт можно выбрать конкретный тип шрифта символа. Если в списке выбрать пункт (основной текст), то символ будет набран тем шрифтом, которым набирается текст. В центральном окне находится 1170 различных символов. Они распределены по группам, которые можно просмотреть в раскрывающемся списке Набор, например: Основная латиница, Денежные единицы, Математические символы и т.д.
На вкладке Специальные символы приведены некоторые символы, о которых говорилось выше: неразрывный пробел, неразрывный дефис, длинное тире, авторское право, торговая марка и другие.
При выделении символа в нижней части окна появляется его описание на английском языке. По нему можно узнать о назначении того или иного неизвестного символа. Некоторые виды шрифтов, могут не поддерживать все 1170 знаков.
Рис. 1. Вставка символов
Большинство шрифтов являются символьными. Иначе говоря, представляют собой разработанные дизайнерами в едином стиле изображения символов. Шрифт – это как почерк у человека. Красивый почерк легко читается. Просмотрите наборы символов различных шрифтов, и вы увидите, что в каждом шрифте между символами существует нечто общее, их объединяющее. У всех символов есть элементы (закругления, выступы, утолщения) выполненные одинаково. Если слово написано одним шрифтом, то и читать его удобнее чем, если бы буквы были написаны разными шрифтами.
Рис. 2. Декоративные шрифты
Кроме видимых символов, существуют также непечатаемые знаки. Это служебные символы, по которым среда текстового процессора отличает информационные объекты. Например, пробел – непечатаемый символ, служит для отделения слов друг от друга. В дальнейшем мы узнаем назначение многих непечатаемых символов.
Непечатаемые символы несут в себе важную информацию, необходимую для правильного форматирования документа. Поэтому при работе желательно включить режим отображения непечатаемых символов. Для этого на панели Стандартная найдите кнопку (Непечатаемые знаки) и нажмите ее. При включении этого режима пробелы между словами будут изображаться точками. И сразу можно увидеть, сколько пробелов стоит между словами.
Параметры символа
Символы как элементы текста обладают набором параметров – характеристик, которые позволяют отличать их между собой. В документе, как правило, основной текст набирается символами, имеющими одинаковые значения параметров. Для выделения заголовков, отдельных слов или фраз достаточно изменить параметры шрифта этих фрагментов.
Знание всех возможностей для установления параметров шрифта позволяет сделать текст уникальным, авторским.
Чтобы просмотреть текущие параметры символа и установить новые используется команда Формат/Шрифт. В диалоговом окне Шрифт (Рис. 3) три вкладки. Наиболее часто используемые параметры шрифта расположены на вкладке Шрифт. В Таблице 1 приведено описание всех параметров символов.
Рис. 3. Параметры шрифта
Таблица 1. Параметры символов (Формат /Шрифт)
Текстовый редактор — это вам не высшая математика, тут думать надо
Современные текстовые редакторы умеют не только бибикать и не давать выйти из программы. Оказывается, внутри них кипит очень сложный метаболизм. Хотите узнать, какие ухищрения предпринимаются для быстрого пересчета координат, как к тексту приделываются стили, фолдинги и софтврапы и как это всё обновляется, при чем тут функциональные структуры данных и очереди с приоритетами, а также как обманывать пользователя — добро пожаловать под кат!
В основе статьи — доклад Алексея Кудрявцева с Joker 2017. Алексей уже лет 10 пишет Intellij IDEA в JetBrains. Под катом вы найдете видео и текстовую расшифровку доклада.
Структуры данных внутри текстовых редакторов
Чтобы понять, как устроен редактор, давайте его напишем.
Всё, наш простейший редактор готов.
Внутри редактора текст легче всего хранить в массиве символов, ну или, что то же самое (в смысле организации памяти), в Java-классе StringBuffer. Чтобы получить какой-нибудь символ по смещению, мы вызываем метод StringBuffer.charAt(i). А чтобы вставить в него символ, который мы напечатали на клавиатуре, вызовем метод StringBuffer.insert(), который вставит символ куда-то в середину.
Что самое интересное, несмотря на всю простоту и идиотичность этого редактора, — это самое хорошее представление, которое только можно выдумать. Оно и просто и почти всегда быстро.
К сожалению, с этим редактором возникает проблема масштаба. Представим, что мы напечатали в нём очень много текста и собираемся вставить в середину еще одну букву. Произойдет следующее. Нам срочно нужно освободить для этой буквы какое-то пространство, сдвинув все остальные буквы на один символ вперёд. Для этого мы сдвигаем эту букву на одну позицию, потом следующую и так далее, до самого конца текста.
Вот как это будет выглядеть в памяти:
Сдвигать все эти многочисленные мегабайты не очень хорошо: это медленно. Конечно, для современного компьютера это плёвое дело — какие-то жалкие мегабайты подвигать туда-сюда. Но для очень активного изменения текста это может быть заметно.
Чтобы решить эту проблему вставки символа в середину, давным-давно придумали обходной манёвр под названием «Gap Buffer».
Gap Buffer
Gap — это зазор. Buffer — это, как вы догадываетесь, буфер. Структура данных «Gap Buffer» — это такой пустой буфер, который мы храним посередине нашего текста, на всякий случай. Если нам потребовалось что-то напечатать, мы используем этот текстовый маленький буфер для быстрого впечатывания.
Структура данных у нас немножко поменялась — массив остался на месте, но появились два указателя: на начало буфера и на его конец. Чтобы взять символ из редактора по какому-то смещению, нам нужно понять, находится ли он до или после этого буфера и немножечко подправить смещение. А чтобы вставить символ, нам нужно сначала пододвинуть Gap Buffer к этому месту и заполнить его этими символами. И, конечно, если мы вышли за пределы нашего буфера, его как-то пересоздать. Вот как это выглядит на картинке.
Как видно, мы сначала долго двигаем на маленький gap-buffer (синенький прямоугольник) к месту редактирования (просто меняя местами символы с его левого и правого края по очереди). Потом мы используем этот буфер, впечатывая туда символы.
Как можно заметить, никакого передвижения мегабайт символов нет, вставка происходит очень быстро, за константу времени, и вроде все счастливы. Кажется, всё хорошо, но если у нас процессор очень медленный, то на перемещение туда-сюда gap-buffer и текста тратится довольно заметное время. Особенно это было заметно во времена очень маленьких мегагерц.
Piece Table
Как раз в это время корпорация под названием Microsoft писала текстовый редактор Word. Они решили применить у себя другую идею для ускорения редактирования под названием «Piece Table», то есть «Таблица Кусочков». И они предложили текст редактора сохранять в этом же самом простейшем массиве символов, который меняться не будет, а все его изменения складывать в отдельную таблицу из этих самых отредактированных кусочков.
Таким образом, если нам нужно найти какой-то символ по смещению, нам нужно найти этот кусочек, который мы поредактировали, и извлечь из него этот символ, а если его там нет, то пойти в оригинальный текст. Вставка символа становится легче, нам всего лишь нужно этот новый кусочек создать и добавить в таблицу. Вот как это выглядит на картинке:
Здесь мы захотели удалить пробел по смещению 5. Для этого мы добавляем в таблицу кусочков два новых кусочка: один указывает на первый фрагмент («Облом»), а второй указывает на фрагмент после редактирования («овцы»). Получается, что пробел из них исчезает, эти два кусочка склеиваются, и у нас получается новый текст уже без пробела: «Обломовцы». Потом добавляем новый текст («страдающие обломовщиной») в конец. Используем дополнительный буфер и добавляем в таблицу кусочков (piece table) новый кусочек, который указывает на этот самый новый добавленный текст.
Как видите, никаких перемещений туда-сюда не происходит, весь текст остаётся на месте. Плохо то, что становится сложнее добраться до символа, потому что перебирать все эти куски довольно тяжело.
Что хорошо в Piece Table:
NetBeans, Eclipse и Emacs используют Gap Buffer — молодцы! Vi не заморачивается и использует просто список строчек. Word использует Piece Table (недавно они выложили свои древние сорцы и там даже можно что-то понять).
С Atom интереснее. До недавнего времени они не заморачивались и использовали JavaScript-список строчек. А потом решили всё переписать на C++ и наворотили довольно сложную структуру, которая вроде как похожа на Piece Table. Но вот эти кусочки у них хранятся не в списке, а в дереве, причём в так называемом splay tree — это дерево, которое саморегулируется при вставке в него, чтобы недавние вставки были быстрее. Очень сложную штуку они наворотили.
Что использует Intellij IDEA?
Нет, не gap-buffer. Нет, вы тоже не правы, не piece table.
Да, совершенно верно, свой собственный велосипед.
Дело в том, что у IDE требования к хранению текста немного другие, нежели в обычном текстовом редакторе. Для IDE нужна поддержка разных хитрых вещей вроде конкуррентности, то есть параллельного доступа к тексту из редактора. Например, чтобы много разных испекшенов могли его читать и что-то делать. (Инспекшн — маленький кусочек кода, анализирующий программу тем или иным образом, — например, ищущий места, выбрасывающие NullPointerException). Также для IDE нужна поддержка версий редактируемого текста. Несколько версий одновременно лежат в памяти, пока вы работаете с документом, с тем, чтобы эти долгие процессы продолжали анализировать старую версию.
Проблемы
Конкуррентность / Версионность
Для того, чтобы поддержать параллельность, операции с текстом обычно оборачивают в «synchronized», или в Read-/Write-локи. К сожалению, это всё не очень хорошо масштабируется. Другой подход — Immutable Text, то есть неизменяемое хранилище текста.
Вот как выглядит редактор с неизменяемым документом в качестве поддерживающей структуры данных.
Как устроена сама структура данных?
Вместо массива символов у нас будет новый объект типа ImmutableText, хранящий текст в виде дерева, где в листах хранятся маленькие подстрочки. При обращении по какому-то смещению он пытается в этом дереве дойти до самого нижнего листа, и у него уже спросить символ, к которому мы обращались. А при вставке текста он создает новое дерево и сохраняет его в старом месте.
К примеру, у нас есть документ с текстом «Бескалорийный». Он реализован как дерево с двумя листами из подстрочек «Бес» и «калорийный». Когда мы хотим вставить в середину строчку «довольно», то создается новая версия нашего документа. И именно, создается новый корень, к которому привязываются уже три листа: «Бес», «довольно» и «калорийный». Причём два из этих новых листов могут ссылаться на первую версию нашего документа. А для листа, в который мы вставили строчку «довольно», отводится новая вершина. Тут и первая версия, и вторая версии доступны одновременно и они все иммутабельные, неизменяемые. Всё выглядит хорошо.
Кто же использует какие хитрые структуры?
Вот, например, в GNOME какой-то их стандартный виджет использует структуру под названием Rope. Xi-Editor — это новый блестящий редактор от Рафа Левиена — использует Persistent Rope. А Intellij IDEA использует этот самый Immutable Tree. За этими всеми названиями, на самом деле, скрывается более-менее одна и та же структура данных с древовидным представлением текста. За исключением того, что GtkTextBuffer использует Mutable Rope, то есть дерево с изменяемыми вершинами, а Intellij IDEA и Xi-Editor — Immutable.
Следующая вещь, которую нужно учитывать при разработке хранилища символов в современных IDE, называется «мультикаретки». Эта фича позволяет печатать сразу в несколько мест, используя несколько кареток.
Мы можем что-то печатать и одновременно в нескольких местах документа у нас вставляется то, что мы туда напечатали. Если мы посмотрим, как наши структуры данных, которые мы рассмотрели, реагируют на мультикаретки, мы увидим кое-что интересное.
Если мы вставляем символ в наш самый первый примитивный редактор, для этого потребуется, естественно, линейное количество времени, чтобы сдвинуть туда-сюда кучу символов. Это записывается в виде O(N). Для редактора на основе Gap Buffer’а, в свою очередь, требуется уже константное время, для этого он и был придуман.
Для immutable-дерева время зависит логарифмически от размера, потому что надо сначала пройти от вершины дерева до его листа — это логарифм, а потом для всех вершин на пути создать новые вершины для нового дерева — это опять логарифм. Для Piece Table тоже требуется константа.
Но все немножко меняется, если мы попробуем померять время вставки символа в редактор с мульти-каретками, то есть, вставки одновременно в несколько мест. С первого взгляда, время вроде как должно пропорционально увеличиться в C раз — число мест, куда вставляется символ. Всё так и происходит, за исключением Gap Buffer’а. В его случае время, вместо C раз, неожиданно увеличивается какое-то непонятное C*L раз, где L — среднее расстояние между каретками. Почему так происходит?
Представим, что нам надо вставить строчку «, на» в два места в нашем документе.
Вот что происходит в редакторе в это время.
Да, получается, что из-за этих многочисленных телодвижений буфера туда-сюда наше общее время увеличивается. Честно говоря, не то чтобы оно прямо ужас как увеличилось, — подвинуть жалкие мега-, гигабайты туда-сюда для современного компьютера не проблема, но всё равно интересно, что эта структура данных в случае мультикареток работает радикально по-другому.
Слишком много строчек? LineSet!
Какие еще есть проблемы в обычном текстовом редакторе? Самая сложная проблема — это скроллинг, то есть перерисовка редактора во время передвигания каретки на следующую строчку.
Когда редактор скроллится, мы должны понять, с какой строчки, с какого символа нам нужно начинать отрисовывать текст в нашем маленьком окошке. Для этого нам нужно быстро понять, какой строчке какое смещение соответствует.
Для этого есть очевидный интерфейс, когда мы по номеру строчки должны понять её смещение в тексте. И наоборот, по смещению в тексте понять, в какой строчке оно находится. Как это можно быстро сделать?
Организовать эти строчки в дерево и каждую вершину этого дерева разметить смещением начала строки и смещением конца строки. И тогда, чтобы по смещению понять, в какой строке оно находится, просто нужно запустить логарифмический поиск в этом дереве и найти его.
Другой способ ещё проще.
Записать в таблицу смещение начала строчек и конца строчек. И тогда, чтобы по номеру строки найти смещение начала и конца, нужно будет обратиться по индексу.
Что интересно, в реальном мире используется и тот, и другой способ.
Например, Eclipse использует такую деревянную структуру, которая, как видно, работает за логарифмическое время и на чтение, и на обновление. А IDEA использует табличную структуру, для которой чтение — это быстрая константа, — это обращение по индексу в таблице, но зато перестроение — довольно медленное, потому что нужно всю таблицу перестроить при изменении длины какой-то строчки.
Всё ещё слишком много строчек? Foldings!
Что ещё есть плохого, на что натыкаются в текстовых редакторах? Например, фолдинги. Это кусочки текста, которые можно «схлопнуть» и показать вместо них что-то другое.
Вот эти вот многоточия на зелёном фоне на картинке скрывают позади себя много символов, но, если нам их смотреть неинтересно (как в случае, например, длиннейших скучных Java-doc’ов или импорт-листов), мы их скрываем, схлопываем в это самое многоточие.
И тут опять нужно понять, когда заканчивается и когда начинается регион, который нам нужно показать, и как это всё быстро обновлять? Как это организовано, расскажу чуть позже.
Слишком длинные строчки? Soft wrap!
Также современные редакторы не могут жить без Soft wrap. На картинке видно, что разработчик открыл JavaScript-файл после минимизации и сразу об этом пожалел. Вот эта огроменнейшая JavaScript-строка, когда мы её пытаемся показать в редакторе, ни в какой экран не влезет. Поэтому soft wrap ее принудительно разрывает на несколько строчек и впихивает в экран.
Как это организовано — позже.
Слишком мало красоты
И, наконец, хочется ещё в текстовых редакторах навести красоту. Например, подсветить некоторые слова. На картинке выше ключевые слова подсвечены синим жирным, какие-то static методы италиком, какие-то аннотации — тоже другим цветом.
Так как же все-таки хранить и обрабатывать фолдинги, софт-врапы и хайлайтинги?
Оказывается, что всё это, в принципе, — одна и та же задача.
Слишком мало красоты? Range highlighters!
Чтобы поддержать все эти фичи, всё, что нам нужно уметь — это по данному смещению в тексте прилепить какие-то текстовые атрибуты, например, цвет, шрифт или текст для фолдинга. Причем эти текстовые атрибуты надо всё время обновлять на этом месте, чтобы они переживали всякие вставки и удаления.
Как это обычно реализуется? Естественно, в виде дерева.
Проблема: слишком много красоты? Interval tree!
Вот, например, у нас тут есть несколько жёлтых подсветок, которые мы хотим сохранить в тексте. Мы складываем интервалы этих хайлайтингов в дерево поиска, так называемое интервальное дерево. Это то же самое дерево поиска, но немного хитрее, потому что нам надо хранить интервалы вместо числа.
А так как есть как здоровые, так и маленькие интервалы, то как их сортировать, друг с другом сравнивать и складывать в дерево — довольно нетривиальная задача. Хотя и очень широко известная в computer science. Посмотрите потом как-нибудь на досуге, как оно устроено. Так вот, берём и складываем все наши интервалы в дерево, и потом каждое изменение текста где-нибудь посередине приводит к логарифмическому изменению этого дерева. Например, вставка символа должна привести в обновлению всех интервалов справа от этого символа. Для этого мы находим все доминантные вершины для этого символа и указываем, что все их подвершины надо отодвинуть на один символ вправо.
Ещё хочется красоты? Ligatures!
Ещё есть такая страшная штуковина — лигатуры, которую тоже бы хотелось поддержать. Это разные красоты вроде того, как знак «!=» рисуется в виде большого глифа «не равно» и так далее. К счастью, тут мы рассчитываем на свинговый механизм поддержки этих лигатур. И, по нашему опыту, он, видимо, работает наипростейшим образом. Внутри шрифта хранится список всех этих пар символов, которые, соединяясь вместе, образуют какую-то хитрую лигатуру. Потом, при отрисовке строчки, Swing просто перебирает все эти пары, находит нужные и рисует их соответствующим образом. Если у вас в шрифте очень много лигатур, то, видимо, отображение его будет пропорционально тормозить.
Тормозит тайпинг
И самое главное — ещё одна проблема, которая встречается, в современных сложных редакторах — это оптимизация тайпинга, то есть, нажатия на клавиши и отображения результата.
Если залезть внутрь Intellij IDEA и посмотреть, что происходит при нажатии какой-то кнопки, то там происходит, оказывается, следующий ужас:
Черт, нет, это не всё. Удалить символ, если наш буфер переполнился. Например, в консоли вызвать listener для того, чтобы все знали, что что-то поменялось. Отскроллить editor view. Вызвать какие-то ещё дурацкие listener’ы.
И что теперь происходит в редакторе, когда он узнал, что документ-то у нас поменялся и вызвался DocumentListener?
В Editor.documentChanged() происходит вот что:
То есть где-то через полчаса подойдет очередь обработки нашего события, вызовется метод repaint у соответствующей компоненты, который будет делать следующее:
Вызывается куча разных paint-методов, которые рисуют всё, что только возможно на свете в этом случае.
Ну что, может, соптимизируем это всё?
Это всё, мягко говоря, довольно сложно. Поэтому мы в Intellij IDEA решили обмануть пользователя.
Перед всякими этими ужасами, которые что-то пересчитывают и что-то записывают, мы вызываем маленький метод, который рисует эту несчастную букву прямо на том месте, куда пользователь её впечатывает. И всё! Пользователь счастлив, потому что он думает, что уже всё поменялось, а на самом-то деле — нет! Под капотом всё ещё только начинается, но буковка-то уже перед ним горит. И поэтому все довольны. Эта фича у нас называется «Zero latency typing».
Коллаборативные редакторы
Сейчас есть такая модная штука — так называемые коллаборативные редакторы.
Что это такое? Это когда один пользователь сидит в Индии, другой — в Японии, они пытаются в один и тот же Google Docs что-то напечатать и хотят какого-то предсказуемого результата.
И из-за этого обычно в коллаборативных редакторах используют новомодные вещи вроде иммутабельности. И придумали разные вещи, как убедиться, что всё работает как нужно. Это несколько критериев. Первый критерий — это сохранение интенции, «intention preservation». Это значит, что если кто-то впечатал символ, то рано или поздно символ из Индии приплывёт в Японию, и японец увидит именно то, что задумал индиец. И второй критерий — конвергенция. Это значит, что символы из Индии и Японии рано или поздно преобразуются во что-то одно и то же и для Японии, и для Индии.
Operation transformation
Первый алгоритм, который был придуман для поддержки этой штуковины, называется «operation transformation». Он работает так. Сидят индиец и японец, что-то печатают: один удаляет с конца букву, другой пририсовывает букву в начало. Фреймворк Operation transformation посылает эти операции во все другие места. Он должен понять, как нужно смержить операции, приходящие к нему, чтобы получилось хоть что-нибудь здравомыслящее. Например, когда у вас одновременно буква удалилась и появилась. Он должен и там, и там отработать более-менее консистентно и прийти к одной и той же строчке. К сожалению, как видно из моего путаного объяснения, это дело довольно сложное.
Когда стали появляться первые реализации этого фреймворка, изумленные разработчики обнаружили, что есть универсальный пример, который все ломает. Этот злополучный пример назвали «TP2 puzzle».
Один пользователь пририсовывает в начало строчки какие-то символы, другой всё это удаляет, а третий пририсовывает в конец. После того, как это всё Operation transformation пытается слить в одно и то же, то должна, по идее, получиться вот эта строчка («ДАНА»). Однако некоторые имплементации делали вот такую («НАДА»). Потому что непонятно, куда вставлять нужно. На картинке выше видно, на каком уровне находится вся эта наука про Operation transformation, если из-за такого примитивного примера у них всё ломалось.
Но несмотря на это некоторые люди всё равно это делают, вроде Google Docs, Google Wave и какого-то распределённого редактора Etherpad. Они используют Operation transformation несмотря на перечисленные проблемы.
Conflict-free replicated data type
Люди тут пораскинули мозгами и решили: «Давайте сделаем проще, чем OT!» Число всяких хитрых сочетаний операций, которые нужно обрабатывать и сливать вместе, растет квадратично. Поэтому, вместо того, чтобы обрабатывать все сочетания, мы будем просто отправлять своё состояние вместе с операцией всем другим хостам таким образом, чтобы можно было с гарантией 100% это всё слить в один и тот же текст. Это называется «CRDT» (Conflict-free replicated data type).
Чтобы это работало, нужно состояние и функция, которая из двух состояний вместе с операцией делает новое состояние. Причем, эта функция должна быть коммутативной, ассоциативной, идемпотентной и монотонной. И тогда понятно, что всё будет работать просто и железобетонно. Из-за этих драконовских ограничений на функцию нам теперь не страшны нарушения порядка (функция ж коммутативна), приоритета (ассоциативна) и потери пакетов в сети (идемпотентность и монотонность).
Есть ли в природе такие функции и как их можно применить?
Да. Например, для случая так называемых G-counter’ов, то есть счетчиков, которые только растут. Можно написать для этого счетчика функцию, которая действительно будет монотонной и так далее. Потому что если у нас есть операция «+1» из Японии, другая «+1» из Индии, понятно, как из них сделать новое состояние — просто прибавить «2». Но оказывается, что таким же образом можно делать ещё и произвольный счетчик, который можно инкрементировать и декрементировать. Для этого достаточно взять один G-counter, который растёт всё время вверх, и все операции инкремента применять к нему. А все декременты применять к другому G-counterу, который будет расти вниз. Чтобы получить актуальное состояние, нужно просто вычесть их содержимое, и у нас получится та самая монотонность. Это всё возможно распространить на произвольные множества. Но самое главное — на произвольные строки. Да, редактирование произвольных строк тоже можно сделать CRDT. Оказывается, это довольно легко.
Conflict-free replicated inserts
Сначала, поименуем все символы в документе, чтобы всегда были однозначно были идентифицируемы, даже после всяких редактирований. Ну, например, вместе с каждой буквой будем хранить уникальное число.
Теперь вместо того, чтобы отправлять всем людям информацию, что мы вставляем по какому-то смещению какой-то символ, мы, вместо этого, будем говорить, между какими именно символами мы будем это вставлять. И тогда понятно, что никаких разночтений быть не может, куда бы мы ни вставляли, мы точно будем знать место, даже после всяких других операций. Вот, например, вместо того, чтобы посылать операцию о том, что мы по смещению 2 мы хотим вставить «РЖУ», мы будем посылать информацию, что между этой «У» и этой «Й» мы вставляем «РЖУ».
Conflict-free replicated deletes
Также можно реализовать и удаление символов. Так как у нас все символы уникально переименованы, мы просто говорим точно, какой именно символ нужно удалить, вместо каких-то смещений. Но вместо того, чтобы взять и физически удалить эти символы, мы будем их помечать удалёнными. Для того, чтобы последующие параллельные вставки или удаления знали, если они затронут эти удалённые только что символы, куда именно им обращаться.
И получается, что эта вся новомодная наука работает.
Conflict-free replicated edits
И, на самом деле, CRDT даже где-то реализовано, например, в Xi-Editor, который будет вставлен в их новомодную секретную операционную систему Fuchsia. Честно говоря, других примеров я не знаю, но это точно работает.
Zipper
Ещё хотел бы рассказать о штуке, которая используется в этом новом иммутабельном мире под названием «Zipper». После того, как мы свои структуры сделали неизменяемыми, появились некоторые нюансы работы с ними. Вот, к примеру, у нас есть наше иммутабельное дерево с текстом. Нам хочется его поменять, (под «поменять» здесь, как вы понимаете, я имею в виду «создать новую версию»). Причем, нам хочется его менять в каком-то определённом месте и довольно активно. В редакторах это довольно часто встречается, когда в месте курсора мы постоянно что-то печатаем, вставляем и удаляем. Для этого функциональщики придумали структуру под названием Zipper.
Она имеет понятие так называемого курсора или текущего места для редактирования, сохраняя при этом полную иммутабельность. Вот как это делается.
Создаём Zipper для нашего документа, который содержит строку для редактирования («Ну надо же»). Вызываем операцию на этом Zipper’е для перемещения по строке в место редактирования. В нашем случае — спуститься на вершину вправо вниз. Для этого мы создаем новую вершину (красненькую), соответствующую текущей вершине, прикрепляем к ней ссылки на дочерние вершины из нашего дерева. Теперь, чтобы переместить курсор Zipperа, мы спускаемся вправо-вниз и создаем также создаем новую вершину вместо той, на которой стояли. При этом, добавляем ссылку на вершину вверх, чтобы не забыть, откуда пришли (красные стрелки). Дойдя таким образом до места редактирования, мы создаем новый лист взамен поредактированного текста (красный прямоугольник). Теперь возвращаемся назад, идя по красным стрелкам вверх и заменяя их по пути на правильные ссылки на дочерние вершины. Когда дойдем до вершины, у нас получится новое дерево с отредактированным листом.
Обратите внимание, как курсор помогает нам редактировать текущий кусочек дерева.
Какие выводы я хотел до вас донести? Во-первых, как ни странно, в теме текстовых редакторов, несмотря на её затхлость, существуют всякие интересные вещи. Более того, в этой же теме появляются иногда новые, иногда неожиданные открытия. И в-третьих, иногда они настолько новые, что не работают с первого раза. Зато интересно и можно согреться. Спасибо.
Ссылки
После доклада прошло много времени, и Микрософт успел вспомнить о своих корнях и переписать Visual Studio Code editor на Piece Table.
И вообще, многих людей почему-то потянуло на эксперименты.
Хотите ещё больше мощных докладов, в том числе и о Java 11? Тогда ждём вас на Joker 2018. В этом году выступят: Джош Лонг, Джон МакКлин, Маркус Хирт, Роберт Шольте и другие не менее крутые спикеры. До конференции осталось 17 дней. Билеты на сайте.