Emacs: Динамическая подсветка Elisp-кода

Как вы наверняка знаете, языком программирования Emacs является Elisp. Его поддержка в стандартной поставке Emacs достаточно хороша. Но вот чего мне действительно не хватает – это качественной подсветки символов (идентификаторов), в том числе определяемых мной. Ну что же, давайте ее добавим.

В этом нам поможет Font Lock mode. Основной способ поиска кода для подсветки в Font Lock mode – это регулярные выражения, но увы, они нам не подходят, так как нам нужен способ выделить символы исходя из их семантики. На этот случай Font Lock mode предоставляет возможность использовать вместо регулярного выражения особого вида функцию, которая сама осуществит поиск кода для подсветки.

На такую функцию накладывается несколько ограничений:

  • Она должна принимать в качестве аргумента лимит поиска – точку до которой будет производиться поиск (начиная с текущей точки).
  • В случае успешного поиска функция должна:
    • вернуть не-nil значение;
    • передвинуть текущую точку на позицию после найденной части кода;
    • сохранить найденную часть кода в match-data.

Пример подсветки имен макросов:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(defun highlight-emacs-lisp--matched-symbol ()
  "Возвращает найденный символ (идентификатор)"
  (intern-soft (buffer-substring (match-beginning 0) (match-end 0))))

(defun highlight-emacs-lisp--macro-symbol-p (symbol)
  "Проверка на то, что с символом связан макрос"
  (eq 'macro (car-safe (symbol-function symbol))))

(defun highlight-emacs-lisp-macro (limit)
  "Поиск имен макросов"
  (loop

    ;; Находим все символы (идентификаторы)
    (unless (re-search-forward "[^() \"\t\n',@]+" limit t)
      (return nil))

    (let ((candidate (highlight-emacs-lisp--matched-symbol)))
      (when (highlight-emacs-lisp--macro-symbol-p candidate)
        (return t)))))

(font-lock-add-keywords 'emacs-lisp-mode
 '((highlight-emacs-lisp-macro . 'font-lock-keyword-face)))

Отлично, теперь обобщим этот метод для функций и глобальных переменных, плюс добавим подсветку в IELM:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
(defmacro define-emacs-lisp-symbols-highlighter (name args selector)
 `(defun ,name (limit)
    (loop

      (unless (re-search-forward "[^() \"\t\n',@]+" limit t)
        (return nil))

      (when ((lambda ,args ,selector)
             (intern-soft (buffer-substring (match-beginning 0) (match-end 0))))
        (return t)))))

(define-emacs-lisp-symbols-highlighter highlight-emacs-lisp-macro (symbol)
  (eq 'macro (car-safe (symbol-function symbol))))

(define-emacs-lisp-symbols-highlighter highlight-emacs-lisp-function (symbol)
  (eq 'lambda (car-safe (symbol-function symbol))))

(define-emacs-lisp-symbols-highlighter highlight-emacs-lisp-compiled-or-primitive-function (symbol)
  (or (subrp (symbol-function symbol))
      (eq 'compiled-function (type-of (symbol-function symbol)))))

(define-emacs-lisp-symbols-highlighter highlight-emacs-lisp-global-variable (symbol)
  (and (boundp symbol)
       (not (or (eq symbol t)
                (eq symbol nil)))))

(dolist (mode '(emacs-lisp-mode inferior-emacs-lisp-mode))
  (font-lock-add-keywords mode
    '((highlight-emacs-lisp-macro . 'font-lock-keyword-face)
      (highlight-emacs-lisp-function . 'font-lock-function-name-face)
      (highlight-emacs-lisp-compiled-or-primitive-function . 'font-lock-builtin-face)
      (highlight-emacs-lisp-global-variable . 'font-lock-variable-name-face))
    t))

Код на Github и скриншот результата:

Комментарии

Свиридов Александр © 2015