Zen Coding для Geany

Zen Сoding — расширение написаное на Python и JavaScript для большинства текстовых редакторов и IDE, которое позволяет очень быстро писать HTML и CSS. С его помощью можно написав всего лишь html:5 получить готовый каркас HTML5 документа с доктайпом и шапкой. Охват Zen Coding’а — большое количество редакторов, но о текстовом редакторе Geany забыли.

Я занимаюсь веб‐разработкой, поэтому мне необходим Zen Coding для того, чтобы избавиться от рутины. Я пишу всё в Geany так как он очень лёгкий, быстрый, хорошо подсвечивает синтаксис, да и большего мне не нужно.

И после нескольких часов гугления я не нашёл необходимого плагина для Geany. Не долго думая, решил его написать сам.

Ну, началось…

Я скачал версию Zen Coding'а, написанную на питоне, а так же нашёл мануал по написанию плагинов для Geany. После некоторого времени изучения мануала по написанию плагинов я понял, что их необходимо писать на си, и начал думать, как привязать питон к си. На изучение сего вопроса ушло чуть больше суток.

В первую очередь я написал скрипт на питоне, который разворачивает аббревиатуру с помощью Zen Coding в строке. Через некоторое время получилось нечто вроде:

import sys
#пытаемся импортировать библиотеку Zen Coding
try:
    from zencoding import zen_core
    from zencoding.zen_settings import zen_settings
except:
    print "error importing zen-codign library...
"
#объявляем класс для хранения значения позиции курсора
#не знаю почему, но не получилось заюзать просто переменную.
class ZObj:
    def __init__(self):
        self._o = -1
    def set(self, i):
        self._o = i
    def get(self):
        return self._o

zobj = ZObj()
#функция для разворачивания аббревиатур.
#в качестве первого агрумента передаётся вся строка с аббревиатурой
#в качестве второго агрумента передаётся текущая позиция курсора в редакторе
#и в качестве третьего агрумента передаётся mime-типа документа,
#text/html или text/css
def expand(line, currpos, mime):
    try:
#сбрасываем значение переменной.
        zobj.set(-1)
#находим аббревиатуру в строке
        abbr = zen_core.find_abbr_in_line(line, currpos)
        abbrlist = list(abbr)
        if(abbrlist[0] == ''):
            raise
        etype = ''
        if(mime == 'text/css'):
            etype = 'css'
        else:
            etype = 'html'
#разворачиваем аббревиатуру
        expanded = zen_core.expand_abbreviation(abbrlist[0], etype)
        if(expanded == ''):
            raise
#заменяем аббревиатуру в строке и вычисляем необходимую позицию курсора.
        abrlen = abbrlist[0].__len__()
        line = expanded.join([line[:currpos-abrlen], line[currpos:]])
        currentPosition = line.find(expanded, currpos-abrlen) + expanded.__len__()
        dpos = line.find('|')
        if(dpos != -1):
            currentPosition = dpos
            line = line.replace('|', '')
#сохраняем текущую позицию курсора 
        zobj.set(currentPosition)
        return line
    except Exception as inst:
        print inst
        return ''
#функция для получения текущей позиции курсора
def getpos():
    return zobj.get()

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

Дальше была работа, связанная с написанием самого плагина на си. Чуть больше чем через пару дней появился код вида:

#include 
#include 
#include 

GeanyPlugin         *geany_plugin;
GeanyData           *geany_data;
GeanyFunctions      *geany_functions;

PyObject *pName, *pValue, *main_module, *expand, *getpos;

PLUGIN_VERSION_CHECK(147)

PLUGIN_SET_INFO("Zen-Coding", "Zen-Coding for Geany",
                "0.3.1", "POPSuL ");
enum {
    EXPAND_KB,
    COUNT_KB
};

PLUGIN_KEY_GROUP(zen_group, COUNT_KB)
//хэндлер вызываемый при нажатии кнопки назначеной для разворачивания
static void expand_handler(G_GNUC_UNUSED guint key_id) {
    GeanyDocument *doc = document_get_current();
    int currl = (int)sci_get_current_line(doc->editor->sci);
    int startpos = sci_get_position_from_line(doc->editor->sci, currl);
    char *cline = (char*)sci_get_line(doc->editor->sci, currl);
    int ccpos = sci_get_current_position(doc->editor->sci);
    int cpos = ccpos - startpos;
    int nlen = strlen(cline);
    char *mime = doc->file_type->mime_type;
    if(nlen > 0) {
        //создаём список агрументов
        pName = PyTuple_New(3);
        PyObject *line = PyString_FromString(cline);
        PyObject *pos = PyInt_FromLong((long) cpos);
        PyObject *type = PyString_FromString(mime);
        PyTuple_SetItem(pName, 0, line);
        PyTuple_SetItem(pName, 1, pos);
        PyTuple_SetItem(pName, 2, type);
        //вызываме функцию expand
        pValue = PyObject_CallObject(expand, pName);
        char *result = PyString_AsString(pValue);
        //получаем позицию курсора
        pName = PyTuple_New(0);
        pValue = PyObject_CallObject(getpos, pName);
        int ncpos = (int) PyLong_AsLong(pValue);
        if(strlen(result) > 0) {
            //заменяем "старую" строку новой и устанавливаем 
            //курсор на нужную позицию
            int endpos = startpos + nlen;
            sci_set_selection_start(doc->editor->sci, startpos);
            sci_set_selection_end(doc->editor->sci, endpos);
            sci_replace_sel(doc->editor->sci, result);

            if(ncpos != -1) {
                endpos = startpos + ncpos;
            }
            sci_set_current_position(doc->editor->sci, endpos, TRUE);
        } else {
            printf("error result, %s", result);
        }
        Py_DECREF(pName);
        Py_DECREF(pValue);
    }
}
//функция для инициализации плагина
void plugin_init(G_GNUC_UNUSED GeanyData *data)
{
    //инициализируем питон
    Py_Initialize();
    //импортируем необходимые пакеты
    PyRun_SimpleString("import os, sys");
    PyRun_SimpleString("sys.path.append(r'/usr/lib/geany-plugins/geanyzen')");
    PyRun_SimpleString("from zencoding.geany_zencoding import expand, getpos");
    //получаем указатели на необходимые модули и функции
    main_module = PyImport_AddModule("__main__");
    expand = PyObject_GetAttrString(main_module, "expand");
    getpos = PyObject_GetAttrString(main_module, "getpos");
    //вешаем хэндлер expand_handler на ctrl+e
    keybindings_set_item(plugin_key_group, EXPAND_KB, expand_handler, GDK_e, GDK_CONTROL_MASK, "expand", "expand", NULL  );
}
//функция для выгрузки всего ненужного из памяти
void plugin_cleanup(void)
{
    Py_DECREF(main_module);
    Py_DECREF(expand);
    Py_DECREF(getpos);
    Py_Finalize();
}

Тут вроде бы всё также предельно ясно. Отсутствие опыта написания программ на питоне и си очень сказалось на красоте (и скорее всего качестве) кода.

Что получилось

В итоге, примерно где‐то через неделю, появился вполне работоспособный плагин для Geany. Тормозов и внезапных падений редактора не было замечено. Даже аббревиатура вида div*100>div*100 разворачивается менее чем за секунду. Это вам не netbeans… :)

Если вы тоже захотели научить свой Geany разворачивать аббревиатуры добро пожаловать на google code.

Для сборки плагина потребуется qmake, make, checkinstall, gcc, geany и libgtk2.0-dev, если у вас всё это уже есть, то для сборки необходимо из папки плагина выполнить последовательно:

qmake
checkinstall -D

и во второй команде нажать три раза Enter. Если всё прошло успешно, и вам не вывалилось в консоль куча ошибок — sudo dpkg -i geanyzen*.deb. Если и тут всё прошло успешно, запускайте Geany, в меню «инструменты» выбирайте пункт «менеджер модулей», включайте модуль Zen Coding. Если же модуля у вас нет в списке, значит не судьба. :)

Для того чтобы было удобнее разворачивать, рекомендую поменять сочетания клавиш на alt+space (сочетания клавиш в меню «Справка» находятся).

P.S. Приведённый здесь код не претендует на самый лучший или что‐то в этом роде, и я так же не несу ответственности за него, так как он распространяется «as is», как есть.

P.S. S. Если у кого то есть желания дополнить или доработать код — добро пожаловать в комментарии.

comments powered by Disqus