Если вы пользовались редактором исходного кода Geany, то наверняка сталкивались с плохо предсказуемым поведением стандартного автозакрытия скобок и кавычек. Если не пользовались — рекомендую попробовать.
Geany замечательный редактор, но на протяжении всей его истории автозакрытие работало так:
func())
или так:
func()
{
}}
Настало время поставить крест на ручной расстановке скобочек. Встречайте, новый режим автозакрытия в Geany:
Преамбула
В Geany есть «нативный» режим автодополнения и автозакрытия (чтобы не путаться: автодополнение — это для функций и методов, а автозакрытие — для парных скобок и кавычек). Казалось бы, суть алгоритма проста: отслеживаем нажатие одного символа и вставляем соответствующий ему парный. Однако существует целая груда подводных камней, нырнув в которую можно набить много шишек.
Проблема 1: люди привыкли к блокноту. Многие машинально сразу ставят и открывающую скобку и закрывающую, результат не заставляет себя ждать:
f())
f();)
f() {}}
f("abc"");
Решили удалить скобочку при помощи бекспейса? Получайте:
f()) -> f))
Такой режим автозакрытия только оказывает медвежью услугу, поэтому его проще сразу выключить.
Проблема 2: в разных языках скобки и кавычки имеют свой смысл. Например, в C++ фигурные скобки обычно определяют границы блока, а в Bash это может просто быть частью переменной. Geany является многоязыковым редактором, и здесь не мешало бы учитывать особенности каждого языка в отдельности.
Проблема 3: во многих редакторах при выделении куска текста и нажатии на скобку/кавычку выделенный текст «оборачивается» в этот символ. В Geany этот текст просто удаляется.
Проблема 4: возможно вы этого никогда не подозревали, но в Geany при зажатии Shift выделение переходит в блочный режим (Multiline Selection). Стоит ли говорить, что автозакрытие в этом режиме не поддерживается никак. И его довольно сложно реализовать, так как блочное выделение захватывает «виртуальные» пробелы:
А ещё в редакторе есть буфер обмена, буфер отмены/повтора действия, горячие клавиши и всё это как-то должно вместе работать…
Решение
Поскольку такие проблемы совсем не к лицу блокноту 21-го века, было решено реализовать автозакрытие в виде дополнения к Geany. Благо, API позволяет легко это сделать. Сказано — сделано, волевым решением плагин был включён в проект geany-plugins и будет доступен в качестве альтернативного режима автозкарытия в следующей версии Geany (для этого достаточно будет поставить галочку в меню плагинов).
Что он умеет на данный момент? А умеет довольно много:
- разумеется, автозавершение символов { }, [ ], ( ), " ", ' ', < >, ` `
- выключать/отключать автозавершение внутри строк и комментариев
- умное завершение: парная скобка удаляется автоматически, подавляется двойное закрытие
- помещает выделенный текст в скобки, сохраняя выделение
- автоматически завершаются функции:
sin(|);
и структуры/классы:struct {|};
- по нажатию Tab курсор прыгнет к парному символу (как в Eclipse)
- по нажатию Shift+BackSpace удаляется парная скобка, а для фигурных скобок — убирается отступ (как на скриншоте в начале статьи)
Помимо всего прочего учитываются особенности разных языков: для си-подобных, например, достаточно выделить кусок текста и нажать {, чтобы появился новый блок; для Bash включается автозакрытие обратной кавычки; для HTML завершаются символы <> и т. п. Также для си-подобных языков сильно улучшены авто-отступы.
Как оно работает?
Geany основан на Scintilla, которая предоставляет довольно разнообразный API в части работы с буфером текста. Идея как обычно проста: отслеживаем нажатия клавиш и в зависимости от обстоятельство реагируем на окружающую среду. Но не тут-то было: в Scintilla был фатальный недостаток, который позволяет отслеживать все нажатия клавиш, кроме бекспейса. Мелочь? А как прикажете отслеживать событие удаления символа?
В итоге пришлось плюнуть и соорудить мегакостыль обработки событий, минуя Scintilla API и используя чистый GLib. Сложность этого костыля в том, что нужно корректно обрабатывать открытие и закрытие документа (а вкладок может быть мнооого), чтобы ненароком не прицепить несколько обработчиков на одно событие или вообще прицепить не туда. Например, типичный баг в плагине Addons — обработчик прицеплен к главному окну. Теперь события плагина срабатывают везде — даже в терминале. В общем, если когда-нибудь вам придётся писать плагин для Geany, а стандартного Scintilla API будет не хватать, вы всегда сможете найти стабильную реализацию костыля в плагине Autoclose.
Остальное — скучная череда свичей и условий. Из интересного — проверка на валидность указателя на документ — doc. На этом нюансе много плагинов полегло: разработчики по незнанию проверяют аргумент функции как все нормальные люди:
if(NULL == doc)
return; //error!
Проблема Geany в том, что doc строго говоря может быть и не NULL, но при попытке к нему обратиться будет сегфолт. Как так? Оказывается, внутри Geany активно практикует повторное использование указателей. Если звёзды сложатся неудачно и в момент проверки документ будет закрыт, его указатель может стать некорректным или вообще указывать на совершенно другой документ. Чтобы избежать коллизий нужно проверять документ макросом DOC_VALID.
Нужно отметить, что сам Geany наступает на свои грабли: загадочный сегфолт, гадание на ассемблерном дампе и результат — проверка doc на NULL в самой ключевой функции загрузки документов.
Как установить?
На данный момент дополнение находится в git head, то есть оно ещё не было выпущено вместе с релизом Geany. Плагин появится только в версии 1.24, но его можно использовать уже сейчас одним из следующих способов:
— скачать экспериментальный, ещё не выпущенный в релиз Geany 1.24 из так называемых «ночных билдов» — там уже всё есть: nightly.geany.org/
— скачать плагин отдельно и попытаться его собрать под старую версию
— попытаться взять библиотеку autoclose из ночных билдов и подсунуть её в 1.23, но гарантий никаких
— скачать исходники geany-plugins для версии 1.23: www.geany.org/Download/Releases
— скопировать плагин autoclose в версию 1.23 и попытаться собрать плагин внутри исходинков 1.23
Сразу не соберётся, так как нужно прописать плагин во все autotools-скрипты, которые разбросаны не очень очевидным образом.
Пусть вас не пугает использование ночной сборки: все правки в Geany тщательно тестируют и проверяют, более того, в 1.24 исправлено большое количество багов, утечек, добавлено много полезных фич, я бы сказал, что она намного стабильнее «стабильной» 1.23. После установки нужно будет включить плагин Autoclose в меню плагинов и в бой.
Что дальше?
Плагин пока решено оставить плагином. После боевого крещения релизом он перекочует в Geany (Autoclose останется плагином, но будет частью самого Geany, а не проекта geany-plugins), после чего скорее всего будет удалена старая функциональность непосредственно из редактора. Проблему автодополнения он не решает, это тема для отдельной (и довольно большой) работы, в IRC ходят слухи, что к Geany пытаются прикрутить clang, но тссс...:)
А пока, в период предрелизного томления, предлагаю читателям сообщать о багах или высказывать пожелания. В частности, поддержки языков, о которых я имею весьма смутные представления, там нет.
Автор: RPG