Pull to refresh

Автозакрытие скобок в Geany

Reading time5 min
Views6.3K
Если вы пользовались редактором исходного кода Geany, то наверняка сталкивались с плохо предсказуемым поведением стандартного автозакрытия скобок и кавычек. Если не пользовались — рекомендую попробовать.

Geany замечательный редактор, но на протяжении всей его истории автозакрытие работало так:

func())

или так:

func()
{
    }}

Настало время поставить крест на ручной расстановке скобочек. Встречайте, новый режим автозакрытия в Geany:

image

Преамбула


В 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 был фатальный недостаток, из-за которого API позволяет отслеживать все нажатия клавиш, кроме бекспейса. Мелочь? А как прикажете отслеживать событие удаления символа?

В итоге пришлось плюнуть и соорудить мегакостыль обработки событий, минуя 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 из гита: github.com/scriptum/geany-plugins
— скачать исходники 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, но тссс...:)

А пока, в период предрелизного томления, предлагаю читателям сообщать о багах или высказывать пожелания. В частности, поддержки языков, о которых я имею весьма смутные представления, там нет.
Tags:
Hubs:
+15
Comments9

Articles