Chroma — Выделитель синтаксиса общего назначения в чистом Go

Документация по Golang CI Slack-чат

NOTE: Поскольку Chroma только что выпущен, его API все еще находится в процессе разработки. Тем не менее, интерфейс высокого уровня не должен существенно измениться.

Chroma принимает исходный код и другой структурированный текст и преобразует его в HTML с подсветкой синтаксиса, цветной текст ANSI и т.д.

Chroma в значительной степени основана на Pygments, и включает трансляторы для лексеров и стилей Pygments.

Поддерживаемые языки

Префикс Язык
A ABAP, ABNF, ActionScript, ActionScript 3, Ada, Angular2, ANTLR, ApacheConf, APL, AppleScript, Arduino, Awk
B Ballerina, Bash, Batchfile, BibTeX, Bicep, BlitzBasic, BNF, Brainfuck, BQN
C C, C#, C++, Caddyfile, Caddyfile Directives, Cap'n Proto, Cassandra CQL, Ceylon, CFEngine3, cfstatement, ChaiScript, Chapel, Cheetah, Clojure, CMake, COBOL, CoffeeScript, Common Lisp, Coq, Crystal, CSS, Cython
D D, Dart, Diff, Django/Jinja, Docker, DTD, Dylan
E EBNF, Elixir, Elm, EmacsLisp, Erlang
F Factor, Fish, Forth, Fortran, FSharp
G GAS, GDScript, Genshi, Genshi HTML, Genshi Text, Gherkin, GLSL, Gnuplot, Go, Go HTML Template, Go Text Template, GraphQL, Groff, Groovy
H Handlebars, Haskell, Haxe, HCL, Hexdump, HLB, HLSL, HTML, HTTP, Hy
I Idris, Igor, INI, Io
J J, Java, JavaScript, JSON, Julia, Jungle
K Kotlin
L Конфигурационный файл Lighttpd, LLVM, Lua
M Makefile, Mako, markdown, Mason, Mathematica, Matlab, MiniZinc, MLIR, Modula-2, MonkeyC, MorrowindScript, Myghty, MySQL
N NASM, Newspeak, конфигурационный файл Nginx, Nim, Nix
O Objective-C, OCaml, Octave, OnesEnterprise, OpenEdge ABL, OpenSCAD, Org Mode
P PacmanConf, Perl, PHP, PHTML, Pig, PkgConfig, PL/pgSQL, plaintext, Pony, диалект SQL PostgreSQL, PostScript, POVRay, PowerShell, Prolog, PromQL, Properties, Protocol Buffer, PSL, Puppet, Python 2, Python
Q QBasic
R R, Racket, Ragel, Raku, react, ReasonML, reg, reStructuredText, Rexx, Ruby, Rust
S SAS, Sass, Scala, Scheme, Scilab, SCSS, Sed, Smalltalk, Smarty, Snobol, Solidity, SPARQL, SQL, SquidConf, Standard ML, stas, Stylus, Svelte, Swift, SYSTEMD, systemverilog
T TableGen, TASM, Tcl, Tcsh, Termcap, Terminfo, Terraform, TeX, Thrift, TOML, TradingView, Transact-SQL, Turing, Turtle, Twig, TypeScript, TypoScript, TypoScriptCssData, TypoScriptHtmlData
V VB.net, verilog, VHDL, VHS, VimL, vue
W WDTE
X XML, Xorg
Y YAML, YANG
Z Zig

Я постараюсь поддерживать этот раздел в актуальном состоянии, но авторитетный список можно посмотреть по chroma --list.

Попробуйте

Попробуйте различные языки и стили на Chroma Playground.

Использование библиотеки

В Chroma, как и в Pygments, есть понятия лексеры, форматеры и стили.

Лексеры преобразуют исходный текст в поток лексем, стили определяют, как типы лексем отображаются на цвета, а форматоры преобразуют лексемы и стили в форматированный вывод.

Для каждого из них существует пакет, содержащий глобальную переменную Registry со всеми зарегистрированными реализациями. В каждом пакете также есть вспомогательные функции для использования реестра, такие как поиск лексеров по имени или сопоставление имен файлов и т.д.

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

Быстрый запуск

Существует удобная функция, которую можно использовать для простого форматирования исходного текста без каких-либо усилий:

err := quick.Highlight(os.Stdout, someSourceCode, "go", "html", "monokai")

Определение языка

Чтобы выделить код, сначала нужно определить, на каком языке он написан. Есть три основных способа сделать это:

  1. Определить язык по имени файла.

    lexer := lexers.Match("foo.go")
  2. Явно указать язык по его синтаксическому идентификатору Chroma (полный список доступен по ссылке lexers.Names()).

    lexer := lexers.Get("go")
  3. Определить язык по содержимому файла.

    lexer := lexers.Analyse("package main\n\nfunc main()\n{\n}\n")

Во всех случаях будет возвращено nil, если язык не может быть определен.

if lexer == nil {
  lexer = lexers.Fallback
}

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

lexer = chroma.Coalesce(lexer)

Форматирование вывода

После определения языка вам нужно выбрать форматтер и стиль (тему).

style := styles.Get("swapoff")
if style == nil {
  style = styles.Fallback
}
formatter := formatters.Get("html")
if formatter == nil {
  formatter = formatters.Fallback
}

Затем получить итератор по лексемам:

contents, err := ioutil.ReadAll(r)
iterator, err := lexer.Tokenise(nil, string(contents))

И, наконец, отформатировать лексемы из итератора:

err := formatter.Format(w, style, iterator)

HTML-форматер

По умолчанию html зарегистрированный форматтер генерирует отдельный HTML со встроенным CSS. Более гибкие возможности доступны с помощью пакета formatters/html.

Во-первых, вывод, генерируемый форматером, можно настроить с помощью следующих опций конструктора:

  • Standalone() - генерировать отдельный HTML со встроенным CSS.
  • WithClasses() - использовать классы, а не встроенные атрибуты стиля.
  • ClassPrefix(prefix) - префикс каждого сгенерированного класса CSS.
  • TabWidth(width) - установить ширину отображаемой вкладки в символах.
  • WithLineNumbers() - Вернуть номера строк (стиль с LineNumbers).
  • WithLinkableLineNumbers() - Сделать номера строк соединяемыми и ссылкой на самих себя.
  • HighlightLines(ranges) - Выделите строки в этих диапазонах (стиль с LineHighlight).
  • LineNumbersInTable() - Используйте таблицу для форматирования номеров строк и кода, а не диапазонов.

Если используется WithClasses(), соответствующий CSS может быть получен из форматера с:

formatter := html.New(html.WithClasses(true))
err := formatter.WriteCSS(w, style)

Подробнее

Лексеры

Подробности о реализации лексеров смотрите в документации Pygments. Большинство концепций применимо непосредственно к Chroma, но реальные примеры смотрите в существующих реализациях лексеров.

Во многих случаях лексеры могут быть автоматически преобразованы непосредственно из Pygments с помощью прилагаемого скрипта Python 3 pygments2chroma_xml.py. Я использую что-то вроде следующего:

python3 _tools/pygments2chroma_xml.py \
  pygments.lexers.jvm.KotlinLexer \
  > lexers/embedded/kotlin.xml

Список лексеров и заметки о некоторых проблемах их импорта см. в pygments-lexers.txt.

Форматировщики

Chroma поддерживает вывод HTML, а также терминальный вывод в 8, 256 и true-colour цветах.

Включен форматтер noop, выводящий только текст лексемы, и форматтер tokens, выводящий необработанные лексемы. Последний полезен для отладки лексеров.

Стили

Стили Chroma определяются в XML. Записи стилей используют тот же синтаксис, что и Pygments.

Все стили Pygments были преобразованы в Chroma с помощью скрипта _tools/style.py.

Когда вы работаете с одним из стилей Chroma, знайте, что тип лексемы Background обеспечивает стиль по умолчанию для лексем. Для этого он определяет цвет переднего плана и цвет фона.

Например, здесь для каждого имени токена, не определенного в стиле, по умолчанию используется цвет #f8f8f8, а для фона выделенного блока кода - #000000:

<entry type="Background" style="#f8f8f2 bg:#000000"/>

Кроме того, типы лексем в файле стилей иерархичны. Например, когда CommentSpecial не определен, Chroma использует стиль лексемы из Comment. Таким образом, если несколько маркеров комментариев используют один и тот же цвет, вам нужно будет только определить Comment и переопределить тот, который имеет другой цвет.

Для краткого обзора доступных стилей и их внешнего вида посмотрите Chroma Style Gallery.

Интерфейс командной строки

В комплект поставки Chroma входит интерфейс командной строки.

Бинарные файлы доступны для установки на странице релизов.

CLI можно использовать в качестве препроцессора для раскраски вывода less(1), см. документацию для переменной окружения LESSOPEN.

Флаг --fail можно использовать для подавления вывода и возврата со статусом выхода 1, чтобы облегчить возврат к другому препроцессору в случае, если chroma не определит конкретный лексер для данного файла. Например:

export LESSOPEN='| p() { chroma --fail "$1" || cat "$1"; }; p "%s"'

Замените cat на ваш любимый резервный препроцессор.

При вызове .lessfilter автоматически включается флаг --fail для простой интеграции с lesspipe shipping with Debian and derivatives; для этой установки исполняемый файл chroma может быть просто симлинкован на ~/.lessfilter.

Чего не хватает по сравнению с Pygments?

  • Довольно много лексеров, по разным причинам (pull-requests welcome):
    • Лексеры Pygments для сложных языков часто включают пользовательский код для обработки определенных аспектов, таких как возможность Raku вложить код в регулярные выражения. Это требует времени и усилий для преобразования.
    • Я в основном преобразовывал только те языки, о которых слышал, чтобы снизить стоимость переноса.
  • Некоторые более эзотерические особенности Pygments опущены для простоты.
  • Хотя Chroma API поддерживает обнаружение содержимого, очень немногие языки поддерживают их. У меня есть планы реализовать статистический анализатор в какой-то момент, но не хватает времени.