LINUX.ORG.RU

tcl/tk, текст, раскраска внешним лексером

 , , , ,


0

1

Итак, мы делаем на tcl/tk текстовый редактор с раскраской. Раскрашиваем лисп. Принято принципиальное решение, что алгоритм раскраски будет написан на лиспе, а не на tcl/tk. Общение между tcl/tk и лиспом происходит через сокеты и сервер SWANK.

Естественно, есть вопрос о быстродействии раскраски.

Вопрос: как этого добиться? У меня пока только одна идея: перехватывать любое событие, которое меняет содержимое текста, отправлять его в лисп в виде потока сообщений такого рода:

открыт файл blablabla следующего содержимого: 
(начальное содержимое файла асинхронно передаётся по кусочкам)
стёрта буква в позиции 4356
вставлена строка asdfjkl в позиции 123

На лисповой стороне эти команды воспроизводятся, буфер воссоздаётся и работает собственно алгоритм раскраски. Как он работает - не суть важно в данный момент. Дальше соответствие позиций с цветами передаётся обратно в tcl тоже в виде команд:

раскрась текст с 1 до 10 в зелёный
раскрась текст с 12 до 20 в красный

Всё это было бы круто, но проблема в задержке: пока команды передаются туда и обратно, пользователь продолжает редактировать текст. Поэтому наши команды к моменту своего прихода устаревают.

Как вообще такие задачи решаются в нормальной жизни? В tcl/tk есть маркеры, прилипшие к тексту, с помощью которых можно частично отслеживать движение блоков текста. Можно передавать маркеры в лисп, если это чему-то поможет.

У меня есть некоторые идеи, как решить задачу, но в XXI веке такие задачи желательно не решать, а брать в уже решённом виде. Лучше бы найти какую-нибудь статью, где расписан алгоритм, а ещё лучше, воплощение в виде кода.

Этот алгоритм не должен тормозить где-то до 300 кб исходника. БОльшие по размеру исходники встречаются не так часто, для начала можно забить на это.

★★★★★

З.Ы. и чтобы два раза не вставать - подскажите годный редактор, написанный на tcl, или хорошо встроенный в tcl.

den73 ★★★★★
() автор топика

Как насчёт разбивать текст на куски и в первую очередь подсвечивать активный кусок? 1 кусок внешний лексер должен обработать за незаметное время. Потом может последовать команда «раскрась в неактивном куске такую-то фигню т.к. тут она определилась», но неактивный нас меньше интересует и, в целом, может чуть подождать.

И да, обработку неактивных кусков можно слегка debounce'ить. Чтобы не обрабатывать весь документ по каждому событию ввода.

x3al ★★★★★
()
Последнее исправление: x3al (всего исправлений: 1)
Ответ на: комментарий от x3al

Согласен. Разбивать на куски не абы как, а по открывающем скобкам в первой позиции строки - в лиспе является полустандартом, что форма должна начинаться с первой позиции строки, а других открывающих скобок в первой позиции строки не должно быть.

Но это тоже не так просто - чтобы правильно подсветить, файл нужно читать сначала. Т.е. нужно как-то кешировать результаты прошлых действий.

Хотелось бы какие-то уже готовые куски, чтобы не писать всё это с нуля, тем более половину нужно писать на tcl, а это для меня нелегко. Или может кто-нибудь возьмётся запилить :)

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

Но это тоже не так просто - чтобы правильно подсветить, файл нужно читать сначала.

Ага. И потом периодически перестраивать это. В фоне, на низком приоритете. А во время редактирования активного куска — тупо считать, что результат парсинга всего документа константен и текущий кусок никак на это не влияет.

Кстати, tcl-коду всё это знать не то, чтобы обязательно: если он будет сообщать позицию курсора, то лиспокод сам определит, какой из кусков активен.

Можно сделать dfn кусок=выражение, либо что-нибудь крупнее, если очень хочется.

x3al ★★★★★
()
Ответ на: комментарий от x3al

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

Насчёт в фоне, на низком приоритете - вопрос опять же, как потом это из фона заливать. Ищу готовое аналогичное решение на любом языке и в любой среде, главное, чтобы можно было понять, как оно работает.

Или предлагается текущий кусок раскрашивать синхронно?

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

http://codemirror.net/1/story.html если очень хочется на любом языке, но там не о том.

Насчёт в фоне, на низком приоритете - вопрос опять же, как потом это из фона заливать

Лиспопарсер работает в 2 потока, 1 — фоновый, 1 — слушает сообщения + парсит текущий кусок + передаёт команду «распарсь всё» фоновому + принимает от него результат и сообщает tcl-редактору.

x3al ★★★★★
()
Ответ на: комментарий от x3al

Re-parsing all the code the user has typed in every time he hits a key is obviously not feasible. So how does one combine on-the-fly highlighting with a serious parser? One option would be to split the code into top-level statements (functions, variable definitions, etc.) and parse these separately. This is horribly clunky though, especially considering the fact that modern JavaScripters often put all the code in a file in a single big object or function to prevent namespace pollution.

I have always liked continuation-passing style and generators. So the idea I came up with is this: An interruptable, resumable parser. This is a parser that does not run through a whole document at once, but parses on-demand, a little bit at a time. At any moment you can create a copy of its current state, which can be resumed later. You start parsing at the top of the code, and keep going as long as you like, but throughout the document, for example at every end of line, you store a copy of the current parser state. Later on, when line 106 changes, you grab the interrupted parser that was stored at the end of line 105, and use it to re-parse line 106. It still knows exactly what the context was at that point, which local variables were defined, which unfinished statements were encountered, and so on.

x3al ★★★★★
()
Ответ на: комментарий от den73

про виджет text уже сказали :-)

есть ещё ctext - расширение подсвечивающие синтаксис и показывающие всякие номера строк, метки и подобное

где-то кто-то прикручивал scintilla - не вспомню ссылку - но это несложно найти.

ps/ раскрашивать синтаксис через внешний tcp сервер - это кривь и кось :-)

MKuznetsov ★★★★★
()
Ответ на: комментарий от jtootf

Спасибо всем! Есть на что посмотреть.

den73 ★★★★★
() автор топика
Ответ на: комментарий от MKuznetsov

Нда. ScintillaTk требует версию 8.5 и ничто иное. Жаль, там уже есть раскраска под лисп. Учитывая, что она «дармовая», сгодилась бы. Скачал SciTe - она мгновенно раскрасила 3-мегабайтный файл на tcl. Лисп не смотрел, но не думаю, что будет существенно отличаться.

Кто знает, там какие-то существенные несовместимости с 8.6 или просто надо пересобрать?

Во всяком случае, подсмотреть алгоритмы можно. Где-то прочитал, что в Scintilla достаточно продвинутый алгоритм раскраски.

den73 ★★★★★
() автор топика
Ответ на: комментарий от jtootf

tloona заметно тупанула при открытии 3-мб файла. Хотя может быть стоит посмотреть её алгоритм. Уже нашёл разделяемую библиотеку. Т.е. есть вероятность, что парсер при этом ещё и написан на С.

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

завтра надо будет посмотреть, как tkcon открывает файл - у него раскраска сделана через ctext.

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

tloona и tkcon открывают 3-мегабайтный файл за время порядка 10 секунд. Их раскраска сделана через классы подсветки ctext, для лиспа не подходит.

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

oduvanchik (aka portable hemlock) открывал 4-мегабайтный лисповый файл несколько минут.

den73 ★★★★★
() автор топика
Ответ на: комментарий от most-fucktum

Более качественная, чем через ctext.

tkcon открывает тот же файл за 23 секунды на виртмашине линукса. Раскраска через ctext, кривоватая (ключевые слова в комментариях подсвечены, строки кривые)

http://fastbase.co.nz/edit/index.html в виртмашине линукса открывает 3-мб файл 10 секунд. Раскраска у него самописная, написана на tcl, редактор text. Раскрашивает более качественно (строки и комментарии нормально)

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

Подводим итоги соревнования:

scintilla порвала всех - качественная раскраска и притом самая быстрая.

на втором месте «ml» - http://fastbase.co.nz/edit/index.html

oduvanchik - phemlock раскрашивает хорошо, но наиболее медленно, хотя весь из себя в нативном коде и на биндингах через ffi. Отсюда вывод, что ни нативность кода, ни отсутствие ipc не помогут от кривых рук. Хотя на фоне атома может быть и ничего будет смотреться - я не сравнивал.

Остальные решения раскрашивают не качественно, такое не надо нам. ctext раскрашивает медленнее, чем самописный лексер на tcl, а раскраска ale - able через ltk (через пайп) - ещё медленнее.

Ни одна рассмотренная программа, кроме сцинтиллы, не пытается использовать ленивые алгоритмы раскрашивания при открытии файла. Про scintill-у не знаю - не смотрел.

Disclaimer - часть результатов получена на разных платформах. На родной win7 ActiveTcl работает несколько быстрее, чем под дебианом на вирт. машине. Ну и я мог накосячить, конечно. Так что не верьте ничему из написанного :)

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 2)
Ответ на: комментарий от den73

А теперь выводы: для файлов до 200 кб, видимо, не так уж и важен способ реализации раскраски. Полная раскраска в любом случае происходит за время не более секунды-двух. Большинство файлов, с которыми приходится работать, меньше этого размера. Для более крупных раскраску можно отключить.

Таким образом, начальная раскраска не столь важна. Важна раскраска по ходу редактирования. Также можно забить на файлы более что можно снизить накал страстей и подумать, как сделать раскраску качественно для файлов до 200кб (лучше до 350кб). Файлы большего размера можно вообще не раскрашивать - их доля мала. Или раскрашивать принудительно по команде пользователя. При редактировании можно поступать так:

- определяем область редактирования. Допустим, можно положить область редактирования = видимая область +/- 5000 букв

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

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

Можно ругать.

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

проще на каждую строку хранить elide тег(тег нерисуемый НИКОГДА) сохраняющий инкрементное состояние парсера. При изменении текста - быстро перезапустить парсер с предыдущего elide.

MKuznetsov ★★★★★
()
Ответ на: комментарий от MKuznetsov

Может и проще, но по памяти будет дороже на порядок-другой, если не переписывать парсер с нуля. Переписывать парсер нет ни малейшего желания.

den73 ★★★★★
() автор топика
21 октября 2015 г.

Наконец посмотрел, как сделана раскраска в одуванчике. Там, ни много ни мало, реализован микроязык и виртуальная машина для его выполнения. Парсеры, работающие методом рекурсивного спуска, пишутся на этом микроязыке (естественно, примитивы для него могут быть написаны и на лиспе). Всё это для того, чтобы можно было запомнить состояние парсера. В качестве состояния, как я понял, берётся состояние вирт. машины.

Код здесь:

https://bitbucket.org/budden/oduvanchik/src/default/src/exp-syntax.lisp?at=default&fileviewer=file-view-default

Как написано в начале файла, экономия вычислений при раскраске достигается за счёт трёх вещей:

1. Не раскрашиваются строчки, которые не видны на экране. 2. запоминается состояние парсера в конце каждой строки (для этого и нужна вирт. машина). Если состояние в конце данной строки не изменилось и последующие строки не изменились, то и раскрашивать дальше не надо. 3. Открывающая скобка в первой позиции строки - это начало новой формы, парсер раскраски перзапускается.

Почему одуванчик при этом всё равно тормозит на открытии файла - я не понимаю. Возможно, проблема где-то в другом месте.

В clcon уже реализована ленивая передача раскраски из одуванчика. 4-мегабайтный файл открывается на счёт «раз», а до конца файла (100 тыс. строк) раскраска добегает примерно через минуту. Кстати, и в одуванчике он тоже быстро открылся, я не понял, почему в прошлый раз у меня получились другие результаты.

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

Но пока что раскраска у меня в clcon глючит, вот что главное.

den73 ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.