Current section
Files
Jump to
Current section
Files
src/glimra.erl
-module(glimra).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([new_syntax_highlighter/0, enable_line_numbers/1, set_trim_source/2, set_theme/2, link_static_stylesheet/0, syntax_highlight/3, codeblock_renderer/1, to_css/1, add_static_stylesheet/2]).
-export_type([config/1, no_theme/0, has_theme/0, syntax_highlighting_error/0, highlight_event/0]).
-opaque config(VVY) :: {config,
boolean(),
boolean(),
gleam@option:option(glimra@theme:theme())} |
{gleam_phantom, VVY}.
-type no_theme() :: any().
-type has_theme() :: any().
-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 new_syntax_highlighter() -> config(no_theme()).
new_syntax_highlighter() ->
{config, false, true, none}.
-spec enable_line_numbers(config(VWK)) -> config(VWK).
enable_line_numbers(Config) ->
erlang:setelement(2, Config, true).
-spec set_trim_source(config(VWN), boolean()) -> config(VWN).
set_trim_source(Config, Trim_source) ->
erlang:setelement(3, Config, Trim_source).
-spec set_theme(config(no_theme()), glimra@theme:theme()) -> config(has_theme()).
set_theme(Config, Theme) ->
{config, Line_numbers, Trim_source, _} = Config,
{config, Line_numbers, Trim_source, {some, Theme}}.
-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 pre_styling(config(has_theme())) -> binary().
pre_styling(Config) ->
{config, _, _, {some, Theme}} = case Config of
{config, _, _, {some, _}} -> Config;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"glimra"/utf8>>,
function => <<"pre_styling"/utf8>>,
line => 418})
end,
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"\npre {\n"/utf8,
" background-color: rgb("/utf8>>/binary,
(gleam@int:to_string(
erlang:element(
2,
erlang:element(
2,
Theme
)
)
))/binary>>/binary,
", "/utf8>>/binary,
(gleam@int:to_string(
erlang:element(
3,
erlang:element(
2,
Theme
)
)
))/binary>>/binary,
", "/utf8>>/binary,
(gleam@int:to_string(
erlang:element(
4,
erlang:element(
2,
Theme
)
)
))/binary>>/binary,
");\n"/utf8>>/binary,
" padding-right: "/utf8>>/binary,
(gleam@float:to_string(
erlang:element(
3,
Theme
)
))/binary>>/binary,
"rem;\n"/utf8>>/binary,
" padding-left: "/utf8>>/binary,
(gleam@float:to_string(
erlang:element(3, Theme)
))/binary>>/binary,
"rem;\n"/utf8>>/binary,
" padding-top: "/utf8>>/binary,
(gleam@float:to_string(
erlang:element(4, Theme)
))/binary>>/binary,
"rem;\n"/utf8>>/binary,
" padding-bottom: "/utf8>>/binary,
(gleam@float:to_string(erlang:element(4, Theme)))/binary>>/binary,
"rem;\n"/utf8>>/binary,
" border-radius: "/utf8>>/binary,
(gleam@float:to_string(erlang:element(5, Theme)))/binary>>/binary,
"rem;\n"/utf8>>/binary,
"}\n"/utf8>>.
-spec code_styling(config(has_theme())) -> binary().
code_styling(Config) ->
{config, Line_numbers, _, _} = Config,
case Line_numbers of
true ->
<<<<<<"\ncode {\n"/utf8, " counter-reset: step;\n"/utf8>>/binary,
" counter-increment: step 0;\n"/utf8>>/binary,
"}\n"/utf8>>;
false ->
<<""/utf8>>
end.
-spec link_static_stylesheet() -> lustre@internals@vdom:element(any()).
link_static_stylesheet() ->
lustre@element@html:link(
[lustre@attribute:href(<<"/glimra.css"/utf8>>),
lustre@attribute:rel(<<"stylesheet"/utf8>>)]
).
-spec prepend_with_linebreak(
list(lustre@internals@vdom:element(nil)),
list(lustre@internals@vdom:element(nil))
) -> list(lustre@internals@vdom:element(nil)).
prepend_with_linebreak(Siblings, Children) ->
[lustre@element@html:span(
[lustre@attribute:class(<<"line"/utf8>>)],
lists:reverse(Children)
) |
Siblings].
-spec do_syntax_highlight(
binary(),
list(highlight_event()),
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, 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,
Code_block,
Code_row,
[Highlight_type | Highlights],
Snippet
);
_ ->
do_syntax_highlight(
Source,
Rest,
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,
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, _}} = 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 => 325})
end,
New_start = (Start + gleam@string:length(
Before_linebreak
))
+ 1,
do_syntax_highlight(
Source,
[{source, New_start, End} | Rest],
prepend_with_linebreak(
Code_block,
prepend_with_snippet(
Code_row,
Highlight_name,
<<Snippet/binary,
Before_linebreak/binary>>
)
),
[],
Highlights,
<<""/utf8>>
);
false ->
do_syntax_highlight(
Source,
Rest,
Code_block,
Code_row,
Highlights,
<<Snippet/binary, New_snippet/binary>>
)
end
end;
[] ->
{ok,
prepend_with_linebreak(
Code_block,
prepend_with_snippet(Code_row, Highlight_name, Snippet)
)}
end.
-spec syntax_highlight(config(any()), binary(), binary()) -> {ok,
lustre@internals@vdom:element(nil)} |
{error, syntax_highlighting_error()}.
syntax_highlight(Config, Source, Language) ->
Language@1 = gleam@string:lowercase(Language),
Language_supported = gleam@list:contains(
libglimra:get_supported_languages(),
Language@1
),
gleam@bool:guard(
not Language_supported,
{error, {unsupported_language, Language@1}},
fun() ->
Source@1 = case erlang:element(3, Config) of
true ->
gleam@string:trim(Source);
false ->
Source
end,
gleam@result:'try'(
begin
_pipe = libglimra:get_highlight_events(Source@1, Language@1),
gleam@result:replace_error(_pipe, tree_sitter_error)
end,
fun(Events) ->
gleam@result:'try'(
do_syntax_highlight(
Source@1,
Events,
[],
[],
[],
<<""/utf8>>
),
fun(Reversed_lines) ->
{ok,
lustre@element@html:code(
[lustre@attribute:attribute(
<<"data-lang"/utf8>>,
Language@1
)],
lists:reverse(Reversed_lines)
)}
end
)
end
)
end
).
-spec codeblock_renderer(config(has_theme())) -> fun((gleam@dict:dict(binary(), binary()), gleam@option:option(binary()), binary()) -> lustre@internals@vdom:element(nil)).
codeblock_renderer(Syntax_highlighter) ->
fun(Attrs, Language, Source) ->
To_attributes = fun(Attrs@1) ->
gleam@dict:fold(
Attrs@1,
[],
fun(Attrs@2, Key, Val) ->
[lustre@attribute:attribute(Key, Val) | Attrs@2]
end
)
end,
Language@1 = gleam@option:unwrap(Language, <<"text"/utf8>>),
lustre@element@html:pre(
To_attributes(Attrs),
[begin
_pipe = Syntax_highlighter,
_pipe@1 = syntax_highlight(_pipe, Source, Language@1),
gleam@result:unwrap(
_pipe@1,
lustre@element@html:code(
[lustre@attribute:attribute(
<<"data-lang"/utf8>>,
Language@1
)],
[lustre@element:text(Source)]
)
)
end]
)
end.
-spec line_styling(config(has_theme())) -> binary().
line_styling(Config) ->
{config, Line_numbers, _, _} = Config,
case Line_numbers of
true ->
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"\n."/utf8, "line"/utf8>>/binary,
" { display: block; }\n"/utf8>>/binary,
"\n."/utf8>>/binary,
"line"/utf8>>/binary,
"::before {\n"/utf8>>/binary,
" content: counter(step);\n"/utf8>>/binary,
" counter-increment: step;\n"/utf8>>/binary,
" width: 1rem;\n"/utf8>>/binary,
" margin-right: 1.5rem;\n"/utf8>>/binary,
" display: inline-block;\n"/utf8>>/binary,
" text-align: right;\n"/utf8>>/binary,
" color: rgba(115,138,148,.4);\n"/utf8>>/binary,
"}\n\n."/utf8>>/binary,
"line"/utf8>>/binary,
":empty::before { content: counter(step) \"\\200b\" }\n"/utf8>>;
false ->
<<<<<<<<<<"\n."/utf8, "line"/utf8>>/binary,
" { display: block; }\n"/utf8>>/binary,
"\n."/utf8>>/binary,
"line"/utf8>>/binary,
":empty::before { content: \"\\200b\"; }\n"/utf8>>
end.
-spec to_css(config(has_theme())) -> binary().
to_css(Config) ->
{config, _, _, {some, Theme}} = case Config of
{config, _, _, {some, _}} -> Config;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"glimra"/utf8>>,
function => <<"to_css"/utf8>>,
line => 114})
end,
_pipe = gleam@string_builder:new(),
_pipe@1 = gleam@string_builder:append(
_pipe,
<<"\n/* generated by glimra */\n"/utf8>>
),
_pipe@2 = gleam@string_builder:append(_pipe@1, pre_styling(Config)),
_pipe@3 = gleam@string_builder:append(_pipe@2, code_styling(Config)),
_pipe@4 = gleam@string_builder:append(_pipe@3, line_styling(Config)),
_pipe@5 = gleam@string_builder:append(_pipe@4, glimra@theme:to_css(Theme)),
gleam@string_builder:to_string(_pipe@5).
-spec add_static_stylesheet(
lustre@ssg:config(VWS, VWT, VWU),
config(has_theme())
) -> lustre@ssg:config(VWS, VWT, VWU).
add_static_stylesheet(Ssg_config, Syntax_highlighter) ->
lustre@ssg:add_static_asset(
Ssg_config,
<<"/glimra.css"/utf8>>,
to_css(Syntax_highlighter)
).