Для чего нужен текстовый редактор программисту
Текстовые редакторы. Для чего нужны и какие бывают
Текстовый редактор – это незаменимая программа для людей, которые работают с текстом. Работа такой программы заключается в следующем: создание и изменение текстовых файлов.
Существует 2 типа текстовых редакторов: редакторы кода и текстовые процессоры.
Что такое редактор кода?
Если же говорить о редакторе кода, который предназначен для написания исходных кодов для программ. Они обеспечивает расширенную функциональность и дают дополнительные возможности при работе с кодом. К особенностям данной группы редакторов можно отнести основные возможности.
Подсветка синтаксиса
Данная функция выделяет каждый элемент кода своим цветом. Например, ключевые слова в коде она выделяет зеленым. Текст в кавычках он выделяет другим цветом, а если программист допустил ошибку, она высветит её красным цветом.
Гибкость
Данная функция позволяет вам писать в одном и том же редакторе, на разных языках программирования. Редактор автоматически распознает язык программирования по расширению файла. То есть, если вы запустили файл с именем fail.cc – это означает для редактора что вы пишите на языке С++. В том случае если вы начали свою работу с файлом под именем dfk.html – это означает для редактора, что он должен изменить свои правила под язык HTML. Одним словом, он подстраивается под вас.
Автоматические отступы
Данная функция предназначена для автоматического отступа. То есть редактор сам считает сколько пробелов ему необходимо поставить. Это значительно упрощает работу.
Улучшенная система навигации
Позволяет вам легко перемещаться в тот элемент кода где допущена ошибка. Например, программа проверки выявила ошибку в строке 304, и вы просто вводите номер строки и перемещаетесь туда. Очень важная функция особенно при написание большого кода.
Текстовые процессоры
Текстовые процессоры. Предназначен для создания, а также редактирования различных видов текстовых документов. Будь это письмо или официальный договор. Самый распространенный из них – это Microsoft Word.
Если подводить итог, то можно сказать, что у каждой программы свое назначение. И определённый набор функций для осуществления поставленной перед ней задачей.
Текстовые редакторы vs IDE
В последнее время наблюдается тенденция бессмысленных, с моей точки зрения, дискуссий относительно того, что лучше, — текстовый редактор или IDE. При этом, в темах, где обсуждается данный вопрос, зачастую 400 и более комментариев. Значит, людей этот вопрос интересует. Значит, надо писать статью.
Итак, какие цели статьи?
1. Что же лучше для программирования: текстовый редактор или IDE
2. Vim и Emacs — не текстовые редакторы
1. Что же лучше для программирования: текстовый редактор или IDE
Начнем с того, чем является программирование. Программирование — процесс написание лексических, синтаксических, семантических правил, оговоренных в спецификации языка программирования (далее ЯП) с последующим его тестированием и получением удовлетворительного конечного результата.
Итак, программирование — это:
1. Написание правил, оговоренных в спецификации ЯП
2. Тестирование написанных правил
Вся суть споров возникает, ввиду того, что для реализации каждого пункта нет четкого набора правил. И у каждого человека, исходя из его личных или командных потребностей применяется определенный инструментарий. Иными словами, выбор инструментария для реализации полного цикла программирования, носит субъективный характер. И если два и более человек, которые не понимают этого, начинается спор или дискуссия, которая не несет в себе никакого результата, кроме обоюдной неприязни. Предлагаю разобрать каждый пункт по отдельности, чтобы отделить мух от котлет, иначе смысловая составляющая будет стремиться или равна нулю.
1. Написание правил, оговоренных в спецификации ЯП
Говоря более простым языком, программирование — это написание текста. Которое подразумевает под собой ввод, редактирование и навигацию, а так же подсветка синтаксиса (последнее и отделяет простой набор и редактирование текста, от программирования). Есть еще один пункт, но он будет рассмотен ниже. Эти действия являются достаточными для того, чтобы реализовать этот пункт. Все эти простейшие действия есть в любом текстовом редакторе или IDE. Вопрос только в
том, какие методы применяются и сколь велика их эффективность. На текущий момент есть всего три основных варианта реализации:
а) классический универальный
б) модальный
в) классический усовершенствованный
Классический метод ввода применяется практически во всех IDE и некоторых редакторах базового уровня (ярким представителем которого можно назвать mcedit, nano), так и текстовых редакторах, которые являются IDE базового уровня (которые маскируются под определение текстовый редактор для программирования). Ярким представителем которого является kate, geany.
Модальный метод ввода подразумевает под собой изменение поведения редактора в зависимости от режимов. Одним из самых ярких представителей является Vim. И форки этого одноименного продукта.
Классический усовершенствованный метод ввода в той или иной степени представлены во всех IDE и текстовым редактором для программиста, коим является Emacs.
И тут сразу начинаются споры от том, какой метод ввода, редактирования и навигации лучше в контексте программирования. И споры обычно руководствуются всего двумя пунктами:
1. Метод ввода, редактирования и навигации не принципиален
2. Метод ввода, редактирования и навигации принципиален
Обычно, когда встречается два и более человека, которые имеют диаметрально противоположный взгляд на этот вопрос, спор или диспут превращается в хаотичный монолог с двух сторон. Результат которого стремится или равен нулю.
У каждого есть своя правда, а истина — одна. И истина в данном случае заключается в том, что второй тип людей правы на 99.9(9)%. Эффективность важна в любом ремесле или его составляющем. Будь то вязание веников или написание текста. Почему? Потому, что повышение эффективности любого составляющего, неизбежно приводит к увеличению эффективности всего процесса в целом. Кто этого не понимает — тот не понимает и объяснить почему вряд ли получится. К сожалению или к радости, зависит от целей и стремлений оппонента\ов.
Итак, существует 100% времени, из которых определенный процент времени тратится на ввод, редактирование и навигацию по написанному коду. Увеличение эффективности составляющих приводят к увеличению продуктивности всего процесса программирования.
а) классический универальный
Особо сказать нечего. Этот способ является достаточным для «Написание правил оговоренных в спецификации ЯП». Больше сказать об этом методе управления текстом нечего. Все его преимущества и недостатки объяснять смысла нет т.к если бы недостатков было меньше, чем преимуществ, тогда бы не появились два альтернативных варианта управления текстом.
б) модальный
Ввод текста осуществляется в одном из режимов. Редактирование же и навигация — в другом\их режимах. Приемуществом этого способа является то, что при этих двух режимах не тратится время на перемещение рук программиста за пределы блока символов. Это означает то, что программисту не нужно перемещать руку на мышь или на стрелки влево\право\вверх\вниз и блок выше(insert\delete\home\end\page up\page down). С одной стороны — это увеличивает продуктивность, с другой, — избавляет от заболеваний суставов кистей.
в) классический усовершенствованный
Данный вид управления текстом появился ввиду недостака классического управления текстом. Далее излагаю свой, субъективный, взгляд на этот способ управления. Приемущества данного способа в том, что некоторые операции редактирования и навигации вынесены на определенные горячие клавиши. С одной стороны — это повышает продуктивность, с другой, при неудачном выборе горячих клавиш приводит к переутомлению мышц и\или к несвойственному положению рук или кистей и несвойственном движениям пальцев, что в свою очередь приводит к появлению такой болезни, как туннельный синдром.
Если сравнивать приемущество этого способа с модальным, а именно двумя яркими представителями двух способов управления — Vim и Emacs, то мало кто понимает то, что Emacs является тоже модальным, но у него модальность косвенная. И эта косвенная модальность — причина того, что многие выбирают Vim, нежели Emacs. Почему? Этот вопрос вы должны задать самому себе. Если не получится на него дать ответ, то скорей всего оно вам и не надо.
2. Тестирование написанных правил
Тестирование написанных правил осуществляется с помощью двух методов:
1. Встроенных в ЯП
2. Внешних инструментах (скрипты, cli инструменты)
И первый и второй пункт с реализуется как в текстовых редакторах, так и в IDE. Вопрос лишь в компетентности, трудоёмкости и целесообразности затраченных часов на их реализацию. При этом в продуктах Vim и Emacs, которые НЕ являются текстовыми редакторами, в базовой поставке реализованных методов или нет или они находятся в плачевном состоянии.
2. Vim и Emacs — не текстовые редакторы
Данные продукты не являются текстовыми редакторами. Это мощнейшие фреймворки, для построения высокоэффективных IDE. Т.к и один и второй — полностью программируемы. Начиная от изменения поведения, до интеграции инструментов обработки, с целью тестирования написанных правил.
Отсутствие функций, которые есть в классических IDE, говорит о том, что эти фреймворки используются людьми, которые применяют другие, отличные, методы для тестирования написанных правил, и этим людям\командам реализация оных не нужна. Единственным весомым аргументом из мира классических IDE является контекстное редактирование правил, которого нет в двух вышеуказанных фреймворках. И его нет по всё той же причине, которая была указана выше.
Вопрос: И всё же, что лучше, IDE или текстовые редакторы?
Ответ: Программируемые фрейморки, для построения высокоэффективных IDE! С одной оговоркой: если это необходимо.
Для чего нужен текстовый редактор программисту
Часто интерактивные текстовые редакторы содержат дополнительную функциональность, призванную автоматизировать действия по редактированию, или отображают текстовые данные специальным образом (например, с подсветкой синтаксиса ).
Также нужно упомянуть удобный интерфейс, позволяющий быстро освоить приложение. Казалось бы, зачем искать что-то еще, но… есть одно «но». 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-мастеров.
Пожалуй, на этом можно и завершить краткий обзор самых популярных текстовых редакторов. Желаю всем удачи и успехов!
Текстовый редактор — это вам не высшая математика, тут думать надо
Современные текстовые редакторы умеют не только бибикать и не давать выйти из программы. Оказывается, внутри них кипит очень сложный метаболизм. Хотите узнать, какие ухищрения предпринимаются для быстрого пересчета координат, как к тексту приделываются стили, фолдинги и софтврапы и как это всё обновляется, при чем тут функциональные структуры данных и очереди с приоритетами, а также как обманывать пользователя — добро пожаловать под кат!
В основе статьи — доклад Алексея Кудрявцева с 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 дней. Билеты на сайте.