Підсвітка блоків з кодом в Org-mode за допомогою Pygments
За замовчуванням, Org майже ніяк не підсвічує синтаксис у блоках коду при експорті в HTML. Попри всю потужність Org, це стає серйозним плюсом на користь комбінації Pelican + Markdown для ведення блогу. Не знайшовши ніякого адекватного рішення, яке було хоча б наближено схоже на те, що пропонує Pygments в Pelican я вирішив додати його підтримку і в Org-mode.
Як це повинно працювати?
При експорті в певний формат, org-mode використовує спеціальний бекенд в якому описуються правила для обробки і конвертування усіх елементів. Вирішення мого завдання — створити власний бекенд на основі існуючого і змінити функції які відповідають за обробку блоків коду.
Отже, нам потрібна функція яка виконуватиме наступну shell-команду та віддаватиме вивід в бекенд.
pygmentize -l <синтаксис> -f html <вхідний_файл>
Буде значно простіше писати блоки коду в тимчасові файли, а потім віддавати їх скрипту ніж намагатись екранувати всі можливі символи для вводу через stdin
.
Реалізація
На основі вихідного коду бекенду для експорту в HTML та пишемо власну функцію для обробки коду яка прийматиме на вхід 3 параметри. Нас цікавить параметр code
який являє собою org-element
.
Згідно Org-mode API для об'єкту "Src Block" на потрібно отримати значення властивостей :language
та :value
що відповідають за синтаксис та вихідний текст відповідно.
(require 'org)
(require 'ox)
(require 'ox-html)
;; Шлях до pygments або назва команди
(defvar pygments-path "pygmentize")
(defun pygments-org-html-code (code contents info)
;; Генеруємо шлях для тимчасового файлу
;; Хеш поточного часу і дати ідеально підійде під це завдання.
(setq temp-source-file(format "/tmp/pygmentize-%s.txt"(md5 (current-time-string))))
;; Пишемо вміст блоку в тимчасовий файл
(with-temp-file temp-source-file (insert (org-element-property :value code)))
;; Запускаємо shell-комаду і отримуємо вивід
(shell-command-to-string (format "%s -l \"%s\" -f html %s"
pygments-path
(or (org-element-property :language code)
"")
temp-source-file)))
Тепер оголосимо новий бекенд pelican-html
на основі html
і замінимо в ньому функції для src-block
та example-block
:
(org-export-define-derived-backend 'pelican-html 'html
:translate-alist '((src-block . pygments-org-html-code)
(example-block . pygments-org-html-code)))
Варто зауважити, що не зважаючи на те, що в API властивість :language
для example-block
присутня — на практиці цей об'єкт її не підтримує (схоже це баг в документації), тому задання синтаксису працюватиме лише для src-block
.
Застосування
В моєму випадку цей бекенд використовується для експорту постів через org-reader для Pelican, а не напряму через буфер Emacs. Вихідний код мого блогу і робочий приклад конфігурації для експорту можна знайти тут.
Для детальної інформації по налаштуванню експорту через Emacs та налаштування бекенду перегляньте документацію.