-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, <> ) ), prepend_with_snippet( [], Highlight_name, After_linebreak ), Highlights, <<""/utf8>> ); false -> do_syntax_highlight( Source, Rest, Config, Code_block, Code_row, Highlights, <> ) 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 ).