73 lines
2.6 KiB
Elixir
73 lines
2.6 KiB
Elixir
defmodule BDS.UI.CodeEditor.Highlight do
|
|
@moduledoc """
|
|
Syntax highlighting for the TUI code editor (issue #32).
|
|
|
|
Wraps `ExRatatui.CodeBlock.highlight/3` with the editor's language
|
|
set: base lexing via syntect, plus pure-Elixir overlays for the
|
|
syntaxes syntect does not know — post macros (`[[gallery names="x"]]`)
|
|
over the markdown base and Liquid tags (`{{ … }}` / `{% … %}`) over
|
|
the HTML base. Mirrors the GUI Monaco language registrations in
|
|
`assets/js/monaco/languages.js`.
|
|
"""
|
|
|
|
alias BDS.UI.CodeEditor.Overlay
|
|
alias ExRatatui.Text.Line
|
|
alias ExRatatui.Widgets.CodeBlock
|
|
|
|
@type language :: :markdown_macros | :liquid | :lua | :text
|
|
|
|
@doc """
|
|
Highlights `content` for `language` using `theme`, returning one
|
|
`%ExRatatui.Text.Line{}` per source line.
|
|
|
|
The returned line count always matches `String.split(content, "\\n")`
|
|
(syntect drops trailing empty lines, but the textarea's line count is
|
|
authoritative for the cursor row, so they are padded back). Span
|
|
contents never contain newline characters.
|
|
"""
|
|
@spec highlight(String.t(), language(), CodeBlock.theme()) :: [Line.t()]
|
|
def highlight(content, language, theme) when is_binary(content) do
|
|
content
|
|
|> base_highlight(language, theme)
|
|
|> sanitize_lines()
|
|
|> pad_lines(source_line_count(content))
|
|
|> apply_overlay(language)
|
|
end
|
|
|
|
defp base_highlight(content, :markdown_macros, theme),
|
|
do: ExRatatui.CodeBlock.highlight(content, "markdown", theme)
|
|
|
|
defp base_highlight(content, :liquid, theme),
|
|
do: ExRatatui.CodeBlock.highlight(content, "html", theme)
|
|
|
|
defp base_highlight(content, :lua, theme),
|
|
do: ExRatatui.CodeBlock.highlight(content, "lua", theme)
|
|
|
|
defp base_highlight(content, :text, theme),
|
|
do: ExRatatui.CodeBlock.highlight(content, nil, theme)
|
|
|
|
# syntect appends the line terminator to the last span of a line;
|
|
# spans fed to `ExRatatui.Widgets.Paragraph` must not contain "\n".
|
|
defp sanitize_lines(lines) do
|
|
Enum.map(lines, fn %Line{spans: spans} = line ->
|
|
spans =
|
|
spans
|
|
|> Enum.map(&%{&1 | content: String.replace(&1.content, ~r/[\r\n]/, "")})
|
|
|> Enum.reject(&(&1.content == ""))
|
|
|
|
%{line | spans: spans}
|
|
end)
|
|
end
|
|
|
|
defp source_line_count(content), do: content |> String.split("\n") |> length()
|
|
|
|
defp pad_lines(lines, count) when length(lines) >= count, do: lines
|
|
|
|
defp pad_lines(lines, count),
|
|
do: lines ++ List.duplicate(%Line{}, count - length(lines))
|
|
|
|
defp apply_overlay(lines, :markdown_macros), do: Enum.map(lines, &Overlay.markdown_macros/1)
|
|
defp apply_overlay(lines, :liquid), do: Enum.map(lines, &Overlay.liquid/1)
|
|
defp apply_overlay(lines, _language), do: lines
|
|
end
|