Packages

Zero runtime syntax highlighter for lustre/ssg.

Current section

Files

Jump to
glimra src glimra.erl
Raw

src/glimra.erl

-module(glimra).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([syntax_highlighter/1, line_class/2, block_class/2, syntax_highlight/2]).
-export_type([config/0, syntax_highlighting_error/0, highlight_event/0]).
-opaque config() :: {config, binary(), binary(), binary()}.
-type syntax_highlighting_error() :: {unsupported_language, binary()} |
tree_sitter_error |
unmatched_highlight_events.
-type highlight_event() :: {highlight_start, integer()} |
{source, integer(), integer()} |
highlight_end.
-spec syntax_highlighter(binary()) -> config().
syntax_highlighter(Language) ->
{config, Language, <<"line"/utf8>>, <<""/utf8>>}.
-spec line_class(config(), binary()) -> config().
line_class(Config, Class) ->
erlang:setelement(3, Config, Class).
-spec block_class(config(), binary()) -> config().
block_class(Config, Class) ->
erlang:setelement(4, Config, Class).
-spec parse_highlights(list(integer())) -> {binary(), list(integer())}.
parse_highlights(Highlights) ->
case Highlights of
[Highlight | Rest_of_highlights] ->
{libglimra:get_highlight_name(Highlight), Rest_of_highlights};
[] ->
{<<""/utf8>>, []}
end.
-spec prepend_with_snippet(
list(lustre@internals@vdom:element(nil)),
binary(),
binary()
) -> list(lustre@internals@vdom:element(nil)).
prepend_with_snippet(Siblings, Highlight_name, Snippet) ->
case gleam@string:is_empty(Snippet) of
true ->
Siblings;
false ->
[lustre@element@html:span(
[lustre@attribute:class(Highlight_name)],
[lustre@element@html:text(Snippet)]
) |
Siblings]
end.
-spec prepend_with_linebreak(
config(),
list(lustre@internals@vdom:element(nil)),
list(lustre@internals@vdom:element(nil))
) -> list(lustre@internals@vdom:element(nil)).
prepend_with_linebreak(Config, Siblings, Children) ->
[lustre@element@html:span(
[lustre@attribute:class(erlang:element(3, Config))],
lists:reverse(Children)
) |
Siblings].
-spec do_syntax_highlight(
binary(),
list(highlight_event()),
config(),
list(lustre@internals@vdom:element(nil)),
list(lustre@internals@vdom:element(nil)),
list(integer()),
binary()
) -> {ok, list(lustre@internals@vdom:element(nil))} |
{error, syntax_highlighting_error()}.
do_syntax_highlight(
Source,
Events,
Config,
Code_block,
Code_row,
Highlights,
Snippet
) ->
{Highlight_name, Rest_of_highlights} = parse_highlights(Highlights),
case Events of
[Event | Rest] ->
case Event of
{highlight_start, Highlight_type} ->
case Snippet of
<<""/utf8>> ->
do_syntax_highlight(
Source,
Rest,
Config,
Code_block,
Code_row,
[Highlight_type | Highlights],
Snippet
);
_ ->
do_syntax_highlight(
Source,
Rest,
Config,
Code_block,
prepend_with_snippet(
Code_row,
Highlight_name,
Snippet
),
[Highlight_type | Highlights],
<<""/utf8>>
)
end;
highlight_end ->
case gleam@string:is_empty(Highlight_name) andalso gleam@list:is_empty(
Rest_of_highlights
) of
true ->
{error, unmatched_highlight_events};
false ->
do_syntax_highlight(
Source,
Rest,
Config,
Code_block,
prepend_with_snippet(
Code_row,
Highlight_name,
Snippet
),
Rest_of_highlights,
<<""/utf8>>
)
end;
{source, Start, End} ->
New_snippet = gleam@string:slice(Source, Start, End - Start),
case gleam_stdlib:contains_string(
New_snippet,
<<"\n"/utf8>>
) of
true ->
_assert_subject = gleam@string:split_once(
New_snippet,
<<"\n"/utf8>>
),
{ok, {Before_linebreak, After_linebreak}} = case _assert_subject of
{ok, {_, _}} -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"glimra"/utf8>>,
function => <<"do_syntax_highlight"/utf8>>,
line => 183})
end,
do_syntax_highlight(
Source,
Rest,
Config,
prepend_with_linebreak(
Config,
Code_block,
prepend_with_snippet(
Code_row,
Highlight_name,
<<Snippet/binary,
Before_linebreak/binary>>
)
),
prepend_with_snippet(
[],
Highlight_name,
After_linebreak
),
Highlights,
<<""/utf8>>
);
false ->
do_syntax_highlight(
Source,
Rest,
Config,
Code_block,
Code_row,
Highlights,
<<Snippet/binary, New_snippet/binary>>
)
end
end;
[] ->
{ok,
prepend_with_linebreak(
Config,
Code_block,
prepend_with_snippet(Code_row, Highlight_name, Snippet)
)}
end.
-spec syntax_highlight(config(), binary()) -> {ok,
lustre@internals@vdom:element(nil)} |
{error, syntax_highlighting_error()}.
syntax_highlight(Config, Source) ->
Language = gleam@string:lowercase(erlang:element(2, Config)),
Language_supported = gleam@list:contains(
libglimra:get_supported_languages(),
Language
),
gleam@bool:guard(
not Language_supported,
{error, {unsupported_language, Language}},
fun() ->
gleam@result:'try'(
begin
_pipe = libglimra:get_highlight_events(Source, Language),
gleam@result:replace_error(_pipe, tree_sitter_error)
end,
fun(Events) ->
gleam@result:'try'(
do_syntax_highlight(
Source,
Events,
Config,
[],
[],
[],
<<""/utf8>>
),
fun(Reversed_lines) ->
{ok,
lustre@element@html:code(
[lustre@attribute:class(
erlang:element(4, Config)
)],
lists:reverse(Reversed_lines)
)}
end
)
end
)
end
).