Skip to main content

src/magic_string.erl

-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)}.