-module(caffeine_lang@source_snippet). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/caffeine_lang/source_snippet.gleam"). -export([extract_snippet/4]). -export_type([source_snippet/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -type source_snippet() :: {source_snippet, binary()}. -file("src/caffeine_lang/source_snippet.gleam", 66). -spec extract_lines_loop( list(binary()), integer(), integer(), integer(), list({integer(), binary()}) ) -> list({integer(), binary()}). extract_lines_loop(Lines, Current, Start, End, Acc) -> case Lines of [] -> Acc; [Line | Rest] -> case Current > End of true -> Acc; false -> New_acc = case Current >= Start of true -> [{Current, Line} | Acc]; false -> Acc end, extract_lines_loop(Rest, Current + 1, Start, End, New_acc) end end. -file("src/caffeine_lang/source_snippet.gleam", 57). ?DOC(" Extracts lines from start_line to end_line (1-indexed, inclusive).\n"). -spec extract_lines(list(binary()), integer(), integer()) -> list({integer(), binary()}). extract_lines(Lines, Start_line, End_line) -> _pipe = extract_lines_loop(Lines, 1, Start_line, End_line, []), lists:reverse(_pipe). -file("src/caffeine_lang/source_snippet.gleam", 91). ?DOC(" Formats a single line with its line number gutter.\n"). -spec format_line(integer(), binary(), integer()) -> binary(). format_line(Line_num, Content, Gutter_width) -> Num_str = erlang:integer_to_binary(Line_num), Padding = gleam@string:repeat( <<" "/utf8>>, Gutter_width - string:length(Num_str) ), <<<<<>/binary, " | "/utf8>>/binary, Content/binary>>. -file("src/caffeine_lang/source_snippet.gleam", 98). ?DOC(" Formats the marker line with carets under the error position.\n"). -spec format_marker(integer(), integer(), integer()) -> binary(). format_marker(Column, Span_width, Gutter_width) -> Gutter_space = gleam@string:repeat(<<" "/utf8>>, Gutter_width), Col_space = gleam@string:repeat(<<" "/utf8>>, gleam@int:max(0, Column - 1)), Carets = gleam@string:repeat(<<"^"/utf8>>, Span_width), <<<<<>/binary, Col_space/binary>>/binary, Carets/binary>>. -file("src/caffeine_lang/source_snippet.gleam", 116). -spec insert_marker_loop( list(binary()), integer(), binary(), integer(), list(binary()) ) -> list(binary()). insert_marker_loop(Lines, Error_index, Marker, Current, Acc) -> case Lines of [] -> Acc; [Line | Rest] -> New_acc = case Current =:= Error_index of true -> [Marker, Line | Acc]; false -> [Line | Acc] end, insert_marker_loop(Rest, Error_index, Marker, Current + 1, New_acc) end. -file("src/caffeine_lang/source_snippet.gleam", 106). ?DOC(" Inserts the marker line after the error line in the context.\n"). -spec insert_marker(list(binary()), integer(), integer(), binary()) -> list(binary()). insert_marker(Context_lines, Error_line, Start_line, Marker_line) -> _pipe = insert_marker_loop( Context_lines, Error_line - Start_line, Marker_line, 0, [] ), lists:reverse(_pipe). -file("src/caffeine_lang/source_snippet.gleam", 17). ?DOC( " Extracts a source snippet around the given line/column position.\n" " Shows 1 line of context above and below the error line.\n" " Adds a caret (^) marker under the error position.\n" " Line and column are 1-indexed.\n" ). -spec extract_snippet( binary(), integer(), integer(), gleam@option:option(integer()) ) -> source_snippet(). extract_snippet(Source_content, Line, Column, End_column) -> Lines = gleam@string:split(Source_content, <<"\n"/utf8>>), Total_lines = erlang:length(Lines), Start_line = gleam@int:max(1, Line - 1), End_line = gleam@int:min(Total_lines, Line + 1), Gutter_width = string:length(erlang:integer_to_binary(End_line)), Context_lines = begin _pipe = extract_lines(Lines, Start_line, End_line), gleam@list:map( _pipe, fun(Pair) -> {Line_num, Line_content} = Pair, format_line(Line_num, Line_content, Gutter_width) end ) end, Span_width = case End_column of {some, End_col} -> gleam@int:max(1, End_col - Column); none -> 1 end, Marker_line = format_marker(Column, Span_width, Gutter_width), Rendered = begin _pipe@1 = insert_marker(Context_lines, Line, Start_line, Marker_line), gleam@string:join(_pipe@1, <<"\n"/utf8>>) end, {source_snippet, Rendered}.