-module(prequel@internals@report). -compile([no_auto_import, nowarn_unused_vars]). -export([format/1]). -export_type([report_block/0, report/0, pretty_block/0]). -type report_block() :: {context_block, prequel@span:span()} | {error_block, prequel@span:span(), gleam@option:option(prequel@span:span()), binary()}. -type report() :: {report, binary(), binary(), binary(), binary(), integer(), integer(), non_empty_list:non_empty_list(report_block()), gleam@option:option(binary())}. -type pretty_block() :: {pretty_block, gleam@string_builder:string_builder(), integer(), integer()}. -spec error_heading(binary(), binary()) -> gleam@string_builder:string_builder(). error_heading(Error_code, Error_name) -> _pipe = [<<"[ "/utf8>>, Error_code, <<" ] Error: "/utf8>>, Error_name], _pipe@1 = gleam@list:map(_pipe, fun gleam_community@ansi:red/1), gleam@string_builder:from_strings(_pipe@1). -spec highlight_line( {integer(), binary()}, prequel@span:span(), fun((binary()) -> binary()) ) -> {integer(), binary()}. highlight_line(Line, Span, Colour) -> {Line_number, Line@1} = Line, From = erlang:element(4, Span), To = erlang:element(5, Span), _pipe = case prequel@span:is_segment(Span) andalso prequel@span:contains_line( Span, Line_number ) of true -> prequel@internals@extra@string:highlight_from_to( Line@1, From, To, Colour ); false -> case prequel@span:classify_line(Span, Line_number) of first -> prequel@internals@extra@string:highlight_from( Line@1, From, Colour ); last -> prequel@internals@extra@string:highlight_up_to( Line@1, To, Colour ); inside -> Colour(Line@1); outside -> Line@1 end end, gleam@pair:new(Line_number, _pipe). -spec highlight_lines( list({integer(), binary()}), prequel@span:span(), fun((binary()) -> binary()) ) -> list({integer(), binary()}). highlight_lines(Lines, Span, Colour) -> gleam@list:map( Lines, fun(_capture) -> highlight_line(_capture, Span, Colour) end ). -spec left_pad_size(integer()) -> integer(). left_pad_size(Max_line) -> Max_line_digits = prequel@internals@extra@int:count_digits(Max_line), Max_line_digits + 2. -spec pointed_message(binary(), integer(), integer()) -> gleam@string_builder:string_builder(). pointed_message(Message, Column_start, Max_line) -> Left_pad_size = left_pad_size(Max_line), Left_pad = begin _pipe = [gleam@string:repeat(<<" "/utf8>>, Left_pad_size), <<"┆"/utf8>>], gleam@string_builder:from_strings(_pipe) end, Inner_pad_size = Column_start, Inner_pad = gleam@string_builder:from_string( gleam@string:repeat(<<" "/utf8>>, Inner_pad_size) ), Extra_pad = gleam@string_builder:from_string( gleam@string:repeat(<<" "/utf8>>, 3) ), Max_line_length = 55 - ((Left_pad_size + Inner_pad_size) + 4), Pointer = begin _pipe@1 = [<<"╰"/utf8>>, <<"─"/utf8>>, <<" "/utf8>>], _pipe@2 = gleam@list:map(_pipe@1, fun gleam_community@ansi:red/1), gleam@string_builder:from_strings(_pipe@2) end, First_line_prefix = gleam@string_builder:join( [Left_pad, Inner_pad, Pointer], <<""/utf8>> ), Lines_prefix = gleam@string_builder:join( [Left_pad, Inner_pad, Extra_pad], <<""/utf8>> ), _assert_subject = begin _pipe@3 = prequel@internals@extra@string:chunks_of( Message, Max_line_length ), gleam@list:map(_pipe@3, fun gleam_community@ansi:red/1) end, [First_line | Lines] = case _assert_subject of [_ | _] -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"prequel/internals/report"/utf8>>, function => <<"pointed_message"/utf8>>, line => 433}) end, First_line@1 = gleam@string_builder:append(First_line_prefix, First_line), Lines@1 = begin _pipe@4 = Lines, gleam@list:map( _pipe@4, fun(Line) -> gleam@string_builder:append(Lines_prefix, Line) end ) end, _pipe@5 = [First_line@1 | Lines@1], gleam@string_builder:join(_pipe@5, <<"\n"/utf8>>). -spec pointing_message(prequel@span:span(), binary(), integer()) -> gleam@string_builder:string_builder(). pointing_message(Pointed, Comment, Max_line) -> Start = erlang:element(4, Pointed), End = erlang:element(5, Pointed), Underline_size = (End - Start) + 1, Underline = begin _pipe = [gleam@string:repeat(<<" "/utf8>>, Start - 1), gleam_community@ansi:red(<<"┬"/utf8>>), gleam_community@ansi:red( gleam@string:repeat(<<"─"/utf8>>, Underline_size - 1) )], gleam@string_builder:from_strings(_pipe) end, Left_pad = gleam@string:repeat(<<" "/utf8>>, left_pad_size(Max_line)), Prefix = begin _pipe@1 = [Left_pad, <<"┆"/utf8>>, <<" "/utf8>>], gleam@string_builder:from_strings(_pipe@1) end, First_line = gleam@string_builder:join([Prefix, Underline], <<""/utf8>>), _pipe@2 = [First_line, pointed_message(Comment, Start, Max_line)], gleam@string_builder:join(_pipe@2, <<"\n"/utf8>>). -spec left_pad(integer()) -> binary(). left_pad(Max_line) -> gleam@string:repeat(<<" "/utf8>>, left_pad_size(Max_line)). -spec file_heading(binary(), integer(), integer(), integer(), integer()) -> gleam@string_builder:string_builder(). file_heading(File_name, Error_line, Error_column, Max_line, Error_code_length) -> Error_line@1 = gleam@int:to_string(Error_line), Error_column@1 = gleam@int:to_string(Error_column), Line_length = (Error_code_length + 4) - left_pad_size(Max_line), Line = <<"╭"/utf8, (gleam@string:repeat(<<"─"/utf8>>, Line_length - 1))/binary>>, _pipe = [left_pad(Max_line), Line, <<" "/utf8>>, File_name, <<":"/utf8>>, Error_line@1, <<":"/utf8>>, Error_column@1], gleam@string_builder:from_strings(_pipe). -spec connection_from_file_heading_to_blocks(integer(), integer()) -> gleam@string_builder:string_builder(). connection_from_file_heading_to_blocks(Min_line, Max_line) -> Vertical_line = case Min_line of 1 -> <<"│"/utf8>>; _ -> <<"┆"/utf8>> end, _pipe = [left_pad(Max_line), Vertical_line], gleam@string_builder:from_strings(_pipe). -spec dashed_separator(integer()) -> gleam@string_builder:string_builder(). dashed_separator(Max_line) -> gleam@string_builder:from_strings([left_pad(Max_line), <<"┆"/utf8>>]). -spec join_two_pretty_blocks(pretty_block(), pretty_block(), integer()) -> pretty_block(). join_two_pretty_blocks(One, Other, Max_line) -> Joined = begin _pipe = case prequel@internals@extra@int:is_next_to( erlang:element(4, One), erlang:element(3, Other) ) of true -> [erlang:element(2, One), erlang:element(2, Other)]; false -> [erlang:element(2, One), dashed_separator(Max_line), erlang:element(2, Other)] end, gleam@string_builder:join(_pipe, <<"\n"/utf8>>) end, {pretty_block, Joined, erlang:element(3, One), erlang:element(4, Other)}. -spec join_pretty_blocks( non_empty_list:non_empty_list(pretty_block()), integer() ) -> pretty_block(). join_pretty_blocks(Blocks, Max_line) -> _pipe = Blocks, non_empty_list:reduce( _pipe, fun(One, Other) -> join_two_pretty_blocks(One, Other, Max_line) end ). -spec add_line_number(integer(), binary(), integer()) -> gleam@string_builder:string_builder(). add_line_number(Line_number, Line, Max_line) -> Max_line_digits = prequel@internals@extra@int:count_digits(Max_line), Line_digits = prequel@internals@extra@int:count_digits(Line_number), Left_pad_size = ((Max_line_digits + 2) - Line_digits) - 1, Left_pad = gleam@string:repeat(<<" "/utf8>>, Left_pad_size), _pipe = [Left_pad, gleam@int:to_string(Line_number), <<" "/utf8>>, <<"│"/utf8>>, <<" "/utf8>>, Line], gleam@string_builder:from_strings(_pipe). -spec block_to_spans(report_block()) -> non_empty_list:non_empty_list(prequel@span:span()). block_to_spans(Block) -> case Block of {context_block, Span} -> non_empty_list:single(Span); {error_block, Span@1, Context, _} -> case Context of {some, Context_span} -> non_empty_list:new(Span@1, [Context_span]); none -> non_empty_list:single(Span@1) end end. -spec max_line(non_empty_list:non_empty_list(report_block())) -> integer(). max_line(Blocks) -> _pipe = Blocks, _pipe@1 = non_empty_list:flat_map(_pipe, fun block_to_spans/1), _pipe@2 = non_empty_list:flat_map(_pipe@1, fun prequel@span:lines/1), non_empty_list:reduce(_pipe@2, fun gleam@int:max/2). -spec min_line(non_empty_list:non_empty_list(report_block())) -> integer(). min_line(Blocks) -> _pipe = Blocks, _pipe@1 = non_empty_list:flat_map(_pipe, fun block_to_spans/1), _pipe@2 = non_empty_list:flat_map(_pipe@1, fun prequel@span:lines/1), non_empty_list:reduce(_pipe@2, fun gleam@int:min/2). -spec select_lines_range(list({integer(), binary()}), prequel@span:span()) -> list({integer(), binary()}). select_lines_range(Code_lines, Span) -> _pipe = Code_lines, _pipe@1 = gleam@list:drop(_pipe, erlang:element(2, Span) - 1), gleam@list:take( _pipe@1, (erlang:element(3, Span) - erlang:element(2, Span)) + 1 ). -spec context_to_string( prequel@span:span(), list({integer(), binary()}), integer() ) -> gleam@string_builder:string_builder(). context_to_string(Span, Code_lines, Max_line) -> _pipe = select_lines_range(Code_lines, Span), _pipe@1 = gleam@list:map( _pipe, fun(Pair) -> add_line_number( erlang:element(1, Pair), erlang:element(2, Pair), Max_line ) end ), gleam@string_builder:join(_pipe@1, <<"\n"/utf8>>). -spec simple_error_to_string( prequel@span:span(), binary(), list({integer(), binary()}), integer() ) -> gleam@string_builder:string_builder(). simple_error_to_string(Pointed, Comment, Code_lines, Max_line) -> _pipe = select_lines_range(Code_lines, Pointed), _pipe@1 = highlight_lines(_pipe, Pointed, fun gleam_community@ansi:red/1), _pipe@2 = gleam@list:map( _pipe@1, fun(Pair) -> add_line_number( erlang:element(1, Pair), erlang:element(2, Pair), Max_line ) end ), _pipe@3 = gleam@string_builder:join(_pipe@2, <<"\n"/utf8>>), _pipe@4 = gleam@string_builder:append(_pipe@3, <<"\n"/utf8>>), gleam@string_builder:append_builder( _pipe@4, pointing_message(Pointed, Comment, Max_line) ). -spec error_with_context_to_string( prequel@span:span(), prequel@span:span(), binary(), list({integer(), binary()}), integer() ) -> gleam@string_builder:string_builder(). error_with_context_to_string(Pointed, Underlined, Comment, Code_lines, Max_line) -> _pipe = prequel@span:merge(Pointed, Underlined), _pipe@1 = select_lines_range(Code_lines, _pipe), _pipe@2 = highlight_lines(_pipe@1, Pointed, fun gleam_community@ansi:red/1), _pipe@3 = highlight_lines( _pipe@2, Underlined, fun gleam_community@ansi:blue/1 ), _pipe@4 = gleam@list:map( _pipe@3, fun(Pair) -> add_line_number( erlang:element(1, Pair), erlang:element(2, Pair), Max_line ) end ), _pipe@5 = gleam@string_builder:join(_pipe@4, <<"\n"/utf8>>), _pipe@6 = gleam@string_builder:append(_pipe@5, <<"\n"/utf8>>), gleam@string_builder:append_builder( _pipe@6, pointing_message(Pointed, Comment, Max_line) ). -spec error_to_string( prequel@span:span(), gleam@option:option(prequel@span:span()), binary(), list({integer(), binary()}), integer() ) -> gleam@string_builder:string_builder(). error_to_string(Pointed, Underlined, Comment, Code_lines, Max_line) -> case Underlined of none -> simple_error_to_string(Pointed, Comment, Code_lines, Max_line); {some, Underlined@1} -> error_with_context_to_string( Pointed, Underlined@1, Comment, Code_lines, Max_line ) end. -spec block_content_to_string( list({integer(), binary()}), report_block(), integer() ) -> gleam@string_builder:string_builder(). block_content_to_string(Code_lines, Block, Max_line) -> case Block of {context_block, Span} -> context_to_string(Span, Code_lines, Max_line); {error_block, Pointed, Underlined, Comment} -> error_to_string(Pointed, Underlined, Comment, Code_lines, Max_line) end. -spec block_start_line(report_block()) -> integer(). block_start_line(Block) -> min_line(non_empty_list:single(Block)). -spec block_end_line(report_block()) -> integer(). block_end_line(Block) -> max_line(non_empty_list:single(Block)). -spec to_pretty_block(report_block(), list({integer(), binary()}), integer()) -> pretty_block(). to_pretty_block(Block, Code_lines, Max_line) -> Content = block_content_to_string(Code_lines, Block, Max_line), {pretty_block, Content, block_start_line(Block), block_end_line(Block)}. -spec blocks_to_string_builder( binary(), non_empty_list:non_empty_list(report_block()), integer() ) -> gleam@string_builder:string_builder(). blocks_to_string_builder(Source_code, Blocks, Max_line) -> Code_lines = begin _pipe = gleam@string:split(Source_code, <<"\n"/utf8>>), gleam@list:index_map(_pipe, fun(Index, Line) -> {Index + 1, Line} end) end, _pipe@1 = Blocks, _pipe@2 = non_empty_list:map( _pipe@1, fun(_capture) -> to_pretty_block(_capture, Code_lines, Max_line) end ), _pipe@3 = join_pretty_blocks(_pipe@2, Max_line), (fun(Joined) -> erlang:element(2, Joined) end)(_pipe@3). -spec hint_to_string_builder(gleam@option:option(binary())) -> gleam@string_builder:string_builder(). hint_to_string_builder(Hint) -> case Hint of none -> gleam@string_builder:new(); {some, Hint@1} -> gleam@string_builder:from_strings([<<"\n"/utf8>>, Hint@1]) end. -spec format(report()) -> gleam@string_builder:string_builder(). format(Report) -> Error_code_length = gleam@string:length(erlang:element(5, Report)), Max_line = max_line(erlang:element(8, Report)), Min_line = min_line(erlang:element(8, Report)), _pipe = [error_heading(erlang:element(5, Report), erlang:element(4, Report)), file_heading( erlang:element(2, Report), erlang:element(6, Report), erlang:element(7, Report), Max_line, Error_code_length ), connection_from_file_heading_to_blocks(Min_line, Max_line), blocks_to_string_builder( erlang:element(3, Report), erlang:element(8, Report), Max_line ), hint_to_string_builder(erlang:element(9, Report))], _pipe@1 = gleam@list:filter( _pipe, fun(Builder) -> not gleam@string_builder:is_empty(Builder) end ), gleam@string_builder:join(_pipe@1, <<"\n"/utf8>>).