-module(magic_string).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/magic_string.gleam").
-export([describe_error/1, new/1, overwrite/4, remove/3, append_left/3, append_right/3, prepend/2, append/2, to_string/1, generate_map/3, default_map_options/0, bundle/0, add_source/4, bundle_to_string/1, bundle_generate_map/2]).
-export_type([edit/0, magic_string/0, piece/0, edit_error/0, map_options/0, bundle_source/0, bundle/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.
?MODULEDOC(
" String editing by byte offset, with a source map as a byproduct.\n"
"\n"
" Keep the original source, record overwrite/remove/insert edits keyed by\n"
" UTF-8 byte offset, then `to_string` and `generate_map` replay the log.\n"
" Surviving slices of the original produce mapping segments; inserted text\n"
" does not.\n"
).
-type edit() :: {edit, integer(), integer(), binary()}.
-opaque magic_string() :: {magic_string,
binary(),
binary(),
binary(),
gleam@dict:dict(integer(), edit()),
gleam@dict:dict(integer(), binary()),
gleam@dict:dict(integer(), binary())}.
-type piece() :: {piece, binary(), gleam@option:option(integer())}.
-type edit_error() :: {overlap, {integer(), integer()}, {integer(), integer()}} |
{swallowed_insert, integer(), {integer(), integer()}} |
{out_of_bounds, integer(), integer()} |
{inverted_range, integer(), integer()}.
-type map_options() :: {map_options,
gleam@option:option(binary()),
gleam@option:option(binary()),
boolean()}.
-type bundle_source() :: {bundle_source, binary(), binary(), magic_string()}.
-opaque bundle() :: {bundle, list(bundle_source())}.
-file("src/magic_string.gleam", 93).
-spec range_str({integer(), integer()}) -> binary().
range_str(R) ->
<<<<<<<<"["/utf8, (erlang:integer_to_binary(erlang:element(1, R)))/binary>>/binary,
", "/utf8>>/binary,
(erlang:integer_to_binary(erlang:element(2, R)))/binary>>/binary,
")"/utf8>>.
-file("src/magic_string.gleam", 66).
?DOC(" Human-readable description of an `EditError`.\n").
-spec describe_error(edit_error()) -> binary().
describe_error(Err) ->
case Err of
{overlap, Existing, Attempted} ->
<<<<<<"edit "/utf8, (range_str(Attempted))/binary>>/binary,
" overlaps existing edit "/utf8>>/binary,
(range_str(Existing))/binary>>;
{swallowed_insert, At, Range} ->
<<<<<<"insert at offset "/utf8,
(erlang:integer_to_binary(At))/binary>>/binary,
" falls inside overwrite/remove range "/utf8>>/binary,
(range_str(Range))/binary>>;
{out_of_bounds, Offset, Source_length} ->
<<<<<<<<"offset "/utf8, (erlang:integer_to_binary(Offset))/binary>>/binary,
" is out of bounds (source is "/utf8>>/binary,
(erlang:integer_to_binary(Source_length))/binary>>/binary,
" bytes)"/utf8>>;
{inverted_range, Start, End} ->
<<<<<<<<"range ["/utf8, (erlang:integer_to_binary(Start))/binary>>/binary,
", "/utf8>>/binary,
(erlang:integer_to_binary(End))/binary>>/binary,
") has end < start"/utf8>>
end.
-file("src/magic_string.gleam", 98).
?DOC(" Create an editor over `source` with no edits.\n").
-spec new(binary()) -> magic_string().
new(Source) ->
{magic_string,
Source,
<<""/utf8>>,
<<""/utf8>>,
maps:new(),
maps:new(),
maps:new()}.
-file("src/magic_string.gleam", 228).
?DOC(
" A new range swallows an insert if any recorded `append_left`/`append_right`\n"
" index lies strictly between `start` and `end`.\n"
).
-spec check_no_swallowed_inserts(magic_string(), integer(), integer()) -> {ok,
nil} |
{error, edit_error()}.
check_no_swallowed_inserts(Ms, Start, End) ->
Swallowed = begin
_pipe = lists:append(
maps:keys(erlang:element(6, Ms)),
maps:keys(erlang:element(7, Ms))
),
gleam@list:find(_pipe, fun(At) -> (At > Start) andalso (At < End) end)
end,
case Swallowed of
{ok, At@1} ->
{error, {swallowed_insert, At@1, {Start, End}}};
{error, nil} ->
{ok, nil}
end.
-file("src/magic_string.gleam", 207).
?DOC(
" `[start, end)` overlaps an existing edit if any recorded range shares at\n"
" least one byte with it. An edit at the SAME start is last-write-wins, not\n"
" an overlap.\n"
).
-spec check_no_overlap(magic_string(), integer(), integer()) -> {ok, nil} |
{error, edit_error()}.
check_no_overlap(Ms, Start, End) ->
Conflict = begin
_pipe = maps:values(erlang:element(5, Ms)),
gleam@list:find(
_pipe,
fun(E) ->
((erlang:element(2, E) /= Start) andalso (erlang:element(2, E) < End))
andalso (Start < erlang:element(3, E))
end
)
end,
case Conflict of
{ok, E@1} ->
{error,
{overlap,
{erlang:element(2, E@1), erlang:element(3, E@1)},
{Start, End}}};
{error, nil} ->
{ok, nil}
end.
-file("src/magic_string.gleam", 197).
-spec check_bound(integer(), integer()) -> {ok, nil} | {error, edit_error()}.
check_bound(Offset, Len) ->
case (Offset < 0) orelse (Offset > Len) of
true ->
{error, {out_of_bounds, Offset, Len}};
false ->
{ok, nil}
end.
-file("src/magic_string.gleam", 188).
-spec check_range(integer(), integer(), integer()) -> {ok, nil} |
{error, edit_error()}.
check_range(Start, End, Len) ->
gleam@result:'try'(
check_bound(Start, Len),
fun(_use0) ->
nil = _use0,
gleam@result:'try'(
check_bound(End, Len),
fun(_use0@1) ->
nil = _use0@1,
case End < Start of
true ->
{error, {inverted_range, Start, End}};
false ->
{ok, nil}
end
end
)
end
).
-file("src/magic_string.gleam", 115).
?DOC(
" Replace the original byte range `[start, end)` with `with`. The\n"
" replacement is inserted text and produces no mapping segment.\n"
"\n"
" Returns `Error` if `[start, end)` overlaps an existing range, would\n"
" swallow an existing insert, or is out of bounds. Same-start is\n"
" last-write-wins; adjacent ranges that only share a boundary are fine.\n"
).
-spec overwrite(magic_string(), integer(), integer(), binary()) -> {ok,
magic_string()} |
{error, edit_error()}.
overwrite(Ms, Start, End, Content) ->
Len = erlang:byte_size(erlang:element(2, Ms)),
gleam@result:'try'(
check_range(Start, End, Len),
fun(_use0) ->
nil = _use0,
gleam@result:'try'(
check_no_overlap(Ms, Start, End),
fun(_use0@1) ->
nil = _use0@1,
gleam@result:'try'(
check_no_swallowed_inserts(Ms, Start, End),
fun(_use0@2) ->
nil = _use0@2,
{ok,
{magic_string,
erlang:element(2, Ms),
erlang:element(3, Ms),
erlang:element(4, Ms),
gleam@dict:insert(
erlang:element(5, Ms),
Start,
{edit, Start, End, Content}
),
erlang:element(6, Ms),
erlang:element(7, Ms)}}
end
)
end
)
end
).
-file("src/magic_string.gleam", 135).
?DOC(
" Delete the original byte range `[start, end)`. Returns `Error` for the\n"
" same reasons as `overwrite` (it is `overwrite` with an empty replacement).\n"
).
-spec remove(magic_string(), integer(), integer()) -> {ok, magic_string()} |
{error, edit_error()}.
remove(Ms, Start, End) ->
overwrite(Ms, Start, End, <<""/utf8>>).
-file("src/magic_string.gleam", 244).
?DOC(
" An insert at `index` is rejected if it lies strictly inside an existing\n"
" overwrite/remove range, or is out of bounds.\n"
).
-spec check_insert_at(magic_string(), integer()) -> {ok, nil} |
{error, edit_error()}.
check_insert_at(Ms, Index) ->
gleam@result:'try'(
check_bound(Index, erlang:byte_size(erlang:element(2, Ms))),
fun(_use0) ->
nil = _use0,
Enclosing = begin
_pipe = maps:values(erlang:element(5, Ms)),
gleam@list:find(
_pipe,
fun(E) ->
(Index > erlang:element(2, E)) andalso (Index < erlang:element(
3,
E
))
end
)
end,
case Enclosing of
{ok, E@1} ->
{error,
{swallowed_insert,
Index,
{erlang:element(2, E@1), erlang:element(3, E@1)}}};
{error, nil} ->
{ok, nil}
end
end
).
-file("src/magic_string.gleam", 151).
?DOC(
" Insert `content` just LEFT of `index`: after the content that ends at\n"
" `index`, before anything inserted to the right of `index`. Repeated calls\n"
" at the same index append in call order.\n"
"\n"
" Returns `Error(SwallowedInsert)` if `index` falls strictly inside an\n"
" existing overwrite/remove range (the insert would never be visited and so\n"
" would be silently dropped), or `Error(OutOfBounds)` if `index` is outside\n"
" the source.\n"
).
-spec append_left(magic_string(), integer(), binary()) -> {ok, magic_string()} |
{error, edit_error()}.
append_left(Ms, Index, Content) ->
gleam@result:'try'(
check_insert_at(Ms, Index),
fun(_use0) ->
nil = _use0,
Existing = begin
_pipe = gleam_stdlib:map_get(erlang:element(6, Ms), Index),
gleam@result:unwrap(_pipe, <<""/utf8>>)
end,
{ok,
{magic_string,
erlang:element(2, Ms),
erlang:element(3, Ms),
erlang:element(4, Ms),
erlang:element(5, Ms),
gleam@dict:insert(
erlang:element(6, Ms),
Index,
<<Existing/binary, Content/binary>>
),
erlang:element(7, Ms)}}
end
).
-file("src/magic_string.gleam", 171).
?DOC(
" Insert `content` just RIGHT of `index`: before the content that starts at\n"
" `index`, after anything inserted to the left of `index`. Repeated calls at\n"
" the same index append in call order.\n"
"\n"
" Returns `Error` for the same reasons as `append_left`.\n"
).
-spec append_right(magic_string(), integer(), binary()) -> {ok, magic_string()} |
{error, edit_error()}.
append_right(Ms, Index, Content) ->
gleam@result:'try'(
check_insert_at(Ms, Index),
fun(_use0) ->
nil = _use0,
Existing = begin
_pipe = gleam_stdlib:map_get(erlang:element(7, Ms), Index),
gleam@result:unwrap(_pipe, <<""/utf8>>)
end,
{ok,
{magic_string,
erlang:element(2, Ms),
erlang:element(3, Ms),
erlang:element(4, Ms),
erlang:element(5, Ms),
erlang:element(6, Ms),
gleam@dict:insert(
erlang:element(7, Ms),
Index,
<<Existing/binary, Content/binary>>
)}}
end
).
-file("src/magic_string.gleam", 257).
?DOC(
" Prepend `content` to the very start of the output. Repeated calls stack so\n"
" the most recent prepend ends up first.\n"
).
-spec prepend(magic_string(), binary()) -> magic_string().
prepend(Ms, Content) ->
{magic_string,
erlang:element(2, Ms),
<<Content/binary, (erlang:element(3, Ms))/binary>>,
erlang:element(4, Ms),
erlang:element(5, Ms),
erlang:element(6, Ms),
erlang:element(7, Ms)}.
-file("src/magic_string.gleam", 262).
?DOC(" Append `content` to the very end of the output.\n").
-spec append(magic_string(), binary()) -> magic_string().
append(Ms, Content) ->
{magic_string,
erlang:element(2, Ms),
erlang:element(3, Ms),
<<(erlang:element(4, Ms))/binary, Content/binary>>,
erlang:element(5, Ms),
erlang:element(6, Ms),
erlang:element(7, Ms)}.
-file("src/magic_string.gleam", 391).
?DOC(
" Slice `[start, start + len)` bytes of `bytes` back into a String. The range\n"
" always lands on UTF-8 boundaries (offsets come from edit/insert positions and\n"
" byte lengths), so a slice/decode failure can only mean an out-of-range\n"
" request, for which the empty string is the safe fragment.\n"
).
-spec slice_bytes(bitstring(), integer(), integer()) -> binary().
slice_bytes(Bytes, Start, Len) ->
case gleam_stdlib:bit_array_slice(Bytes, Start, Len) of
{ok, Slice} ->
case gleam@bit_array:to_string(Slice) of
{ok, Text} ->
Text;
{error, nil} ->
<<""/utf8>>
end;
{error, nil} ->
<<""/utf8>>
end.
-file("src/magic_string.gleam", 380).
-spec next_boundary(list(integer()), integer(), integer()) -> integer().
next_boundary(Boundaries, Pos, Byte_len) ->
_pipe = Boundaries,
_pipe@1 = gleam@list:filter(_pipe, fun(B) -> B > Pos end),
_pipe@2 = gleam@list:first(_pipe@1),
gleam@result:unwrap(_pipe@2, Byte_len).
-file("src/magic_string.gleam", 357).
?DOC(" Helper so the end-of-source check reads as an early return without nesting.\n").
-spec bool_at_end(boolean(), list(piece()), fun(() -> list(piece()))) -> list(piece()).
bool_at_end(At_end, Acc, Cont) ->
case At_end of
true ->
Acc;
false ->
Cont()
end.
-file("src/magic_string.gleam", 368).
-spec push_insert(
list(piece()),
gleam@dict:dict(integer(), binary()),
integer()
) -> list(piece()).
push_insert(Acc, Inserts, Pos) ->
case gleam_stdlib:map_get(Inserts, Pos) of
{ok, <<""/utf8>>} ->
Acc;
{ok, Text} ->
[{piece, Text, none} | Acc];
{error, nil} ->
Acc
end.
-file("src/magic_string.gleam", 343).
?DOC(
" Emit the surviving original slice from `pos` to the next boundary, carrying\n"
" `pos` as the mapping offset, then continue the walk.\n"
).
-spec emit_slice(
magic_string(),
bitstring(),
integer(),
list(integer()),
integer(),
list(piece())
) -> list(piece()).
emit_slice(Ms, Bytes, Byte_len, Boundaries, Pos, Acc) ->
Stop = next_boundary(Boundaries, Pos, Byte_len),
Text = slice_bytes(Bytes, Pos, Stop - Pos),
walk(
Ms,
Bytes,
Byte_len,
Boundaries,
Stop,
[{piece, Text, {some, Pos}} | Acc]
).
-file("src/magic_string.gleam", 314).
?DOC(" Tail-recursive walk building the body piece list in reverse.\n").
-spec walk(
magic_string(),
bitstring(),
integer(),
list(integer()),
integer(),
list(piece())
) -> list(piece()).
walk(Ms, Bytes, Byte_len, Boundaries, Pos, Acc) ->
Acc@1 = push_insert(Acc, erlang:element(6, Ms), Pos),
Acc@2 = push_insert(Acc@1, erlang:element(7, Ms), Pos),
bool_at_end(
Pos >= Byte_len,
Acc@2,
fun() -> case gleam_stdlib:map_get(erlang:element(5, Ms), Pos) of
{ok, Edit} ->
case erlang:element(3, Edit) > Pos of
true ->
walk(
Ms,
Bytes,
Byte_len,
Boundaries,
erlang:element(3, Edit),
[{piece, erlang:element(4, Edit), none} | Acc@2]
);
false ->
emit_slice(
Ms,
Bytes,
Byte_len,
Boundaries,
Pos,
Acc@2
)
end;
{error, nil} ->
emit_slice(Ms, Bytes, Byte_len, Boundaries, Pos, Acc@2)
end end
).
-file("src/magic_string.gleam", 302).
?DOC(
" Sorted, de-duplicated set of offsets at which a surviving slice must stop:\n"
" every edit start and every insert index, plus the end of the source.\n"
).
-spec compute_boundaries(magic_string(), integer()) -> list(integer()).
compute_boundaries(Ms, Byte_len) ->
_pipe = lists:append(
[maps:keys(erlang:element(5, Ms)),
maps:keys(erlang:element(6, Ms)),
maps:keys(erlang:element(7, Ms)),
[Byte_len]]
),
_pipe@1 = gleam@list:unique(_pipe),
gleam@list:sort(_pipe@1, fun gleam@int:compare/2).
-file("src/magic_string.gleam", 281).
?DOC(
" Replay the edit log into an ordered list of output pieces.\n"
"\n"
" Walks the original source from offset 0: at each position it emits any\n"
" left-then-right inserts bound to that offset, then either the replacement of\n"
" an edit beginning there (advancing past the edit's end) or the surviving\n"
" original slice up to the next boundary (carrying its original offset). `intro`\n"
" and `outro` wrap the result.\n"
).
-spec materialize(magic_string()) -> list(piece()).
materialize(Ms) ->
Bytes = gleam_stdlib:identity(erlang:element(2, Ms)),
Byte_len = erlang:byte_size(Bytes),
Boundaries = compute_boundaries(Ms, Byte_len),
Body = walk(Ms, Bytes, Byte_len, Boundaries, 0, []),
Intro_pieces = case erlang:element(3, Ms) of
<<""/utf8>> ->
[];
Text ->
[{piece, Text, none}]
end,
Outro_pieces = case erlang:element(4, Ms) of
<<""/utf8>> ->
[];
Text@1 ->
[{piece, Text@1, none}]
end,
lists:append([Intro_pieces, lists:reverse(Body), Outro_pieces]).
-file("src/magic_string.gleam", 267).
?DOC(" Materialize the edited output as a string.\n").
-spec to_string(magic_string()) -> binary().
to_string(Ms) ->
_pipe = materialize(Ms),
gleam@list:fold(
_pipe,
<<""/utf8>>,
fun(Acc, Piece) -> <<Acc/binary, (erlang:element(2, Piece))/binary>> end
).
-file("src/magic_string.gleam", 489).
?DOC(
" Length of `text` in UTF-16 code units: astral codepoints (>= U+10000) count\n"
" as 2 (a surrogate pair), everything else as 1.\n"
).
-spec utf16_len(binary()) -> integer().
utf16_len(Text) ->
_pipe = Text,
_pipe@1 = gleam@string:to_utf_codepoints(_pipe),
gleam@list:fold(
_pipe@1,
0,
fun(Acc, Cp) -> case gleam_stdlib:identity(Cp) >= 16#10000 of
true ->
Acc + 2;
false ->
Acc + 1
end end
).
-file("src/magic_string.gleam", 471).
?DOC(
" Advance a generated `(line, col)` position past `text`. Columns are UTF-16\n"
" code units; lines increment per `\\n`.\n"
).
-spec advance(integer(), integer(), binary()) -> {integer(), integer()}.
advance(Gen_line, Gen_col, Text) ->
case gleam@string:split(Text, <<"\n"/utf8>>) of
[] ->
{Gen_line, Gen_col};
[Single] ->
{Gen_line, Gen_col + utf16_len(Single)};
Lines ->
Newlines = erlang:length(Lines) - 1,
Last = begin
_pipe = gleam@list:last(Lines),
gleam@result:unwrap(_pipe, <<""/utf8>>)
end,
{Gen_line + Newlines, utf16_len(Last)}
end.
-file("src/magic_string.gleam", 483).
-spec byte_size(binary()) -> integer().
byte_size(Text) ->
erlang:byte_size(gleam_stdlib:identity(Text)).
-file("src/magic_string.gleam", 437).
?DOC(
" Produce one segment per generated line covered by a surviving slice. The\n"
" first line maps the slice's start (at the current generated column); each\n"
" subsequent line (after a newline in the slice) maps the corresponding\n"
" original line start at generated column 0. `hires` is false, so there is one\n"
" segment per line, not per token.\n"
).
-spec slice_segments(
binary(),
integer(),
magic_string@position_index:position_index(),
integer(),
integer(),
integer()
) -> list(magic_string@codec:segment()).
slice_segments(Text, Offset, Index, Source_idx, Gen_line, Gen_col) ->
Lines = gleam@string:split(Text, <<"\n"/utf8>>),
{Segs, _} = gleam@list:index_fold(
Lines,
{[], 0},
fun(State, Line, I) ->
{Acc, Byte_off} = State,
Pos = magic_string@position_index:lookup(Index, Offset + Byte_off),
Seg_gen_col = case I of
0 ->
Gen_col;
_ ->
0
end,
Seg = {segment,
Gen_line + I,
Seg_gen_col,
Source_idx,
erlang:element(2, Pos),
erlang:element(3, Pos)},
Next_off = (Byte_off + byte_size(Line)) + 1,
{[Seg | Acc], Next_off}
end
),
Segs.
-file("src/magic_string.gleam", 408).
?DOC(
" Convert a piece list into mapping segments, threading the running generated\n"
" position. `gen_line` / `gen_col` accumulate across the whole materialized\n"
" output (and, for bundles, across sources). Returns the produced segments plus\n"
" the generated position just past the last piece.\n"
).
-spec pieces_to_segments(
list(piece()),
magic_string@position_index:position_index(),
integer(),
integer(),
integer()
) -> {list(magic_string@codec:segment()), integer(), integer()}.
pieces_to_segments(Pieces, Index, Source_idx, Gen_line, Gen_col) ->
gleam@list:fold(
Pieces,
{[], Gen_line, Gen_col},
fun(Acc, Piece) ->
{Segs, Gline, Gcol} = Acc,
case erlang:element(3, Piece) of
none ->
{Next_line, Next_col} = advance(
Gline,
Gcol,
erlang:element(2, Piece)
),
{Segs, Next_line, Next_col};
{some, Offset} ->
New_segs = slice_segments(
erlang:element(2, Piece),
Offset,
Index,
Source_idx,
Gline,
Gcol
),
{Next_line@1, Next_col@1} = advance(
Gline,
Gcol,
erlang:element(2, Piece)
),
{lists:append(New_segs, Segs), Next_line@1, Next_col@1}
end
end
).
-file("src/magic_string.gleam", 505).
?DOC(
" Generate a Source Map v3 for a single edited source. `filename` is recorded\n"
" as the sole entry of `sources`; `include_content` controls whether the\n"
" original text is embedded in `sourcesContent`.\n"
).
-spec generate_map(magic_string(), binary(), map_options()) -> magic_string@codec:source_map().
generate_map(Ms, Filename, Opts) ->
Index = magic_string@position_index:new(erlang:element(2, Ms)),
Pieces = materialize(Ms),
{Segments, _, _} = pieces_to_segments(Pieces, Index, 0, 0, 0),
Sources_content = case erlang:element(4, Opts) of
true ->
[{some, erlang:element(2, Ms)}];
false ->
[none]
end,
{source_map,
3,
erlang:element(2, Opts),
erlang:element(3, Opts),
[Filename],
Sources_content,
[],
magic_string@codec:generate_mappings(Segments)}.
-file("src/magic_string.gleam", 538).
?DOC(" Sensible defaults: no `file`, no `sourceRoot`, embed original content.\n").
-spec default_map_options() -> map_options().
default_map_options() ->
{map_options, none, none, true}.
-file("src/magic_string.gleam", 558).
?DOC(" An empty bundle.\n").
-spec bundle() -> bundle().
bundle() ->
{bundle, []}.
-file("src/magic_string.gleam", 564).
?DOC(
" Append a source to the bundle. `filename` is the name recorded in the map's\n"
" `sources`; `content` is the original text; `ms` is its edit log.\n"
).
-spec add_source(bundle(), binary(), binary(), magic_string()) -> bundle().
add_source(B, Filename, Content, Ms) ->
{bundle, [{bundle_source, Filename, Content, Ms} | erlang:element(2, B)]}.
-file("src/magic_string.gleam", 573).
-spec bundle_sources(bundle()) -> list(bundle_source()).
bundle_sources(B) ->
lists:reverse(erlang:element(2, B)).
-file("src/magic_string.gleam", 579).
?DOC(
" Materialize the bundle output: each source's edited text, joined by newlines\n"
" (one line break between sources).\n"
).
-spec bundle_to_string(bundle()) -> binary().
bundle_to_string(B) ->
_pipe = bundle_sources(B),
_pipe@1 = gleam@list:map(
_pipe,
fun(Src) -> to_string(erlang:element(4, Src)) end
),
gleam@string:join(_pipe@1, <<"\n"/utf8>>).
-file("src/magic_string.gleam", 591).
?DOC(
" Generate one Source Map v3 spanning every source in output order.\n"
"\n"
" Walks each source's pieces, offsetting their segments into chunk-space: the\n"
" generated line/column accumulates across the whole output, and the newline\n"
" joining each source to the next advances the generated line by one (and\n"
" resets the column). Each source is assigned its index in `sources`.\n"
).
-spec bundle_generate_map(bundle(), map_options()) -> magic_string@codec:source_map().
bundle_generate_map(B, Opts) ->
Srcs = bundle_sources(B),
{Segments, _, _, _} = gleam@list:fold(
Srcs,
{[], 0, 0, 0},
fun(Acc, Src) ->
{Segs, Gen_line, _, Source_idx} = Acc,
Index = magic_string@position_index:new(erlang:element(3, Src)),
Pieces = materialize(erlang:element(4, Src)),
{New_segs, End_line, _} = pieces_to_segments(
Pieces,
Index,
Source_idx,
Gen_line,
0
),
{lists:append(New_segs, Segs), End_line + 1, 0, Source_idx + 1}
end
),
Sources = gleam@list:map(Srcs, fun(Src@1) -> erlang:element(2, Src@1) end),
Sources_content = gleam@list:map(
Srcs,
fun(Src@2) -> case erlang:element(4, Opts) of
true ->
{some, erlang:element(3, Src@2)};
false ->
none
end end
),
{source_map,
3,
erlang:element(2, Opts),
erlang:element(3, Opts),
Sources,
Sources_content,
[],
magic_string@codec:generate_mappings(Segments)}.