среда, 24 февраля 2016 г.

Развитие моего плагина для Emacs: multi-compile

Я как-то писал про свой небольшой плагин для Emacs - multi-compile. Он неожиданно для меня даже получил какую-то популярность, несколько человек попросили добавить туда несколько новых возможностей. В итоге я всё же поборол свою лень и наконец реализовал то, что просили. Сейчас постараюсь изложить, что появилось нового.


Сначала кратко напомню, зачем плагин вообще нужен. Стандартный модуль compile, позволяет настроить команду компиляции для каждого режима отдельно. Но проблема в том, что такая команда может быть только одна, а хочется иметь отдельную команду для компиляции release, debug, запуска тестов и т.д. Мой плагин как раз позволяет создавать меню для набора таких команд. Выглядит это примерно вот так:



Для настройка меню, как на картинке достаточно установить плагин из melpa и добавить в конфиг:
(require 'multi-compile)

(setq multi-compile-alist '(
    (rust-mode . (("rust-debug" . "cargo run")
                  ("rust-release" . "cargo run --release")
                  ("rust-test" . "cargo test")))
    ...
    ))
Тут своеобразным "триггером" выступает любой файл в режиме rust-mode. Открыв файл в этом режиме и вызвав "M-x multi-compile-run" вы получите меню как на картинке.

Так же триггером сейчас может выступать регулярное выражение на имя файла или буфера, т.е. можно написать вот так:
(setq multi-compile-alist '(
    ("\\.txt\\'" . (("print-filename" . "echo %file-name")))
    ("*scratch*" . (("print-hello" . "echo 'hello'")))
    ("\\.*" . (("item-for-all" . "echo 'I am item for all'")))
    ...
    ))
Теперь при открытии любого файла с расширением "txt" мы получим пункт меню "print-filename", который выводит имя файла. Для scratch-буфера (который не привязан к файлу) пункт меню "print-hello". И вообще для любого файла добавится пункт "item-for-all".

Иногда даже такой настройки бывает мало, тогда в качестве триггера можно использовать elisp-выражение. Если оно возвратит t, то пункты меню ему соответствующие, покажутся, если nil - то нет. Совсем упрощённый пример, который добавляет пункт меню для для любого файла лежащего внутри директории "home":
(defun string/starts-with (string prefix)
    "Return t if STRING starts with prefix."
    (and (stringp string) (string-match (rx-to-string `(: bos ,prefix) t) string)))

(setq multi-compile-alist '(
    ((string/starts-with buffer-file-name "/home/") . (("item-for-home" . "echo %file-name")))
    ...
    ))
Это позволяет реализовывать любые сценарии настройки. Ну например проверять, что если выше по иерархии файлов лежит "Makefile", то считать, что мы открыли C++ проект и показывать для него соответствующие пункты меню.

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

Например попробуем сделать настройку для компиляции golang проекта. Для этого нужно выполнить команду "go build -v" в корне проекта. Официального способа найти корень для сего чудного языка лично я не нашёл. Но если исходить из того, что сейчас без системы контроля версий не разрабатывают, то корнем проекта можно считать директорию, в которой находится поддиректория ".git". Найти её уже не составляет труда:
(locate-dominating-file buffer-file-name ".git")
Это выражение проверит всю иерархию директорий относительно текущего открытого файла и найдёт ту, в которой есть поддиректория ".git". Теперь собираем всё вышеописанное вместе и получаем:
(setq multi-compile-alist '(
    (go-mode . (("go-build" "go build -v"
                 (locate-dominating-file buffer-file-name ".git"))
                ("go-build-and-run" "go build -v && echo 'build finish' && eval ./${PWD##*/}"
                 (locate-dominating-file buffer-file-name ".git"))))
    ...
    ))
Магическая bash-команда "${PWD##*/}" - возвращает имя текущей директории (не полный путь, а именно имя), дело в том, что golang при компиляции создаёт бинарник совпадающий с именем текущей директории и таким хитрым способом мы его находим и запускаем.

Ещё пожалуй напомню, что в командах компиляции можно использовать шаблоны, я их выше в примерах довольно активно использовал. Вот полный список поддерживаемых на текущий момент:
  • "%path" - "/tmp/prj/file.rs" - полный путь к открытому файлу.
  • "%dir" - "tmp/prj" - директория, где он лежит.
  • "%file-name" - "file.rs" - имя текущего файла.
  • "%file-sans" - "file" - имя без расширения.
  • "%file-ext" - "rs" - расширение файла.
  • "%make-dir" - "tmp" - просматривается вся иерархия директорий текущего файла и возвращается та, в которой лежит "Makefile".

Комментариев нет:

Отправить комментарий