-module(textmetrics@diff).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/textmetrics/diff.gleam").
-export([unified_options/2, with_context_lines/2, with_old_name/2, with_new_name/2, old_name/1, new_name/1, context_lines/1, myers/2, to_unified/2, patience/2]).
-export_type([unified_options_error/0, unified_options/0, myers_result/1, line_event/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(
" Diff algorithms over arbitrary lists.\n"
"\n"
" [`myers`](#myers) implements the O(ND) algorithm of Myers (1986)\n"
" and produces an optimal edit script.\n"
" [`patience`](#patience) implements Bram Cohen's patience diff over\n"
" `List(String)`, which often yields more readable diffs for source\n"
" code with moved blocks.\n"
" [`to_unified`](#to_unified) renders an edit script of strings in\n"
" the POSIX unified-diff format.\n"
).
-type unified_options_error() :: {context_lines_negative, integer()}.
-opaque unified_options() :: {unified_options, binary(), binary(), integer()}.
-type myers_result(DOV) :: {myers_result,
list(gleam@dict:dict(integer(), integer())),
integer(),
integer()} |
{gleam_phantom, DOV}.
-type line_event() :: {context_event, binary(), integer(), integer()} |
{delete_event, binary(), integer(), integer()} |
{insert_event, binary(), integer(), integer()}.
-file("src/textmetrics/diff.gleam", 37).
?DOC(
" Default constructor: `context_lines = 3`, matching POSIX\n"
" `diff -u3`.\n"
).
-spec unified_options(binary(), binary()) -> unified_options().
unified_options(Old_name, New_name) ->
{unified_options, Old_name, New_name, 3}.
-file("src/textmetrics/diff.gleam", 46).
?DOC(
" Override the context-line count. Returns\n"
" `Error(ContextLinesNegative(n))` when `n < 0`.\n"
).
-spec with_context_lines(unified_options(), integer()) -> {ok,
unified_options()} |
{error, unified_options_error()}.
with_context_lines(Options, N) ->
case N < 0 of
true ->
{error, {context_lines_negative, N}};
false ->
{ok,
{unified_options,
erlang:element(2, Options),
erlang:element(3, Options),
N}}
end.
-file("src/textmetrics/diff.gleam", 62).
?DOC(" Override the old-file label.\n").
-spec with_old_name(unified_options(), binary()) -> unified_options().
with_old_name(Options, Name) ->
{unified_options,
Name,
erlang:element(3, Options),
erlang:element(4, Options)}.
-file("src/textmetrics/diff.gleam", 71).
?DOC(" Override the new-file label.\n").
-spec with_new_name(unified_options(), binary()) -> unified_options().
with_new_name(Options, Name) ->
{unified_options,
erlang:element(2, Options),
Name,
erlang:element(4, Options)}.
-file("src/textmetrics/diff.gleam", 80).
?DOC(" Read the old-file label.\n").
-spec old_name(unified_options()) -> binary().
old_name(Options) ->
erlang:element(2, Options).
-file("src/textmetrics/diff.gleam", 85).
?DOC(" Read the new-file label.\n").
-spec new_name(unified_options()) -> binary().
new_name(Options) ->
erlang:element(3, Options).
-file("src/textmetrics/diff.gleam", 90).
?DOC(" Read the context-line count.\n").
-spec context_lines(unified_options()) -> integer().
context_lines(Options) ->
erlang:element(4, Options).
-file("src/textmetrics/diff.gleam", 186).
-spec follow_snake(
gleam@dict:dict(integer(), DQA),
gleam@dict:dict(integer(), DQA),
integer(),
integer(),
integer(),
integer()
) -> {integer(), integer()}.
follow_snake(Old_arr, New_arr, M, N, X, Y) ->
case (X < M) andalso (Y < N) of
false ->
{X, Y};
true ->
case {gleam_stdlib:map_get(Old_arr, X),
gleam_stdlib:map_get(New_arr, Y)} of
{{ok, Ax}, {ok, By}} ->
case Ax =:= By of
true ->
follow_snake(Old_arr, New_arr, M, N, X + 1, Y + 1);
false ->
{X, Y}
end;
{_, _} ->
{X, Y}
end
end.
-file("src/textmetrics/diff.gleam", 156).
-spec myers_d_step(
gleam@dict:dict(integer(), DPN),
gleam@dict:dict(integer(), DPN),
integer(),
integer(),
integer(),
integer(),
gleam@dict:dict(integer(), integer())
) -> {ok, {gleam@dict:dict(integer(), integer()), integer(), integer()}} |
{error, gleam@dict:dict(integer(), integer())}.
myers_d_step(Old_arr, New_arr, M, N, D, K, V) ->
case K > D of
true ->
{error, V};
false ->
V_minus = gleam@result:unwrap(gleam_stdlib:map_get(V, K - 1), -1),
V_plus = gleam@result:unwrap(gleam_stdlib:map_get(V, K + 1), -1),
From_above = (K =:= - D) orelse ((K /= D) andalso (V_minus < V_plus)),
X = case From_above of
true ->
V_plus;
false ->
V_minus + 1
end,
Y = X - K,
{Fx, Fy} = follow_snake(Old_arr, New_arr, M, N, X, Y),
New_v = gleam@dict:insert(V, K, Fx),
case (Fx >= M) andalso (Fy >= N) of
true ->
{ok, {New_v, Fx, Fy}};
false ->
myers_d_step(Old_arr, New_arr, M, N, D, K + 2, New_v)
end
end.
-file("src/textmetrics/diff.gleam", 125).
-spec myers_search(
gleam@dict:dict(integer(), DPC),
gleam@dict:dict(integer(), DPC),
integer(),
integer(),
integer(),
integer(),
gleam@dict:dict(integer(), integer()),
list(gleam@dict:dict(integer(), integer()))
) -> myers_result(DPC).
myers_search(Old_arr, New_arr, M, N, Max_d, D, V, Trace_acc) ->
Trace_acc_2 = [V | Trace_acc],
case D > Max_d of
true ->
{myers_result, Trace_acc_2, M, N};
false ->
case myers_d_step(Old_arr, New_arr, M, N, D, - D, V) of
{ok, {_, Fx, Fy}} ->
{myers_result, Trace_acc_2, Fx, Fy};
{error, Updated_v} ->
myers_search(
Old_arr,
New_arr,
M,
N,
Max_d,
D + 1,
Updated_v,
Trace_acc_2
)
end
end.
-file("src/textmetrics/diff.gleam", 254).
-spec walk_diag_back(
gleam@dict:dict(integer(), DQP),
integer(),
integer(),
integer(),
integer(),
list(textmetrics@edit:edit(DQP))
) -> {integer(), integer(), list(textmetrics@edit:edit(DQP))}.
walk_diag_back(Old_arr, X, Y, Lo_x, Lo_y, Acc) ->
case (X > Lo_x) andalso (Y > Lo_y) of
false ->
{X, Y, Acc};
true ->
case gleam_stdlib:map_get(Old_arr, X - 1) of
{ok, Item} ->
walk_diag_back(
Old_arr,
X - 1,
Y - 1,
Lo_x,
Lo_y,
[{equal, Item} | Acc]
);
{error, _} ->
{X, Y, Acc}
end
end.
-file("src/textmetrics/diff.gleam", 273).
-spec walk_origin_back(
gleam@dict:dict(integer(), DQU),
integer(),
integer(),
list(textmetrics@edit:edit(DQU))
) -> list(textmetrics@edit:edit(DQU)).
walk_origin_back(Old_arr, X, Y, Acc) ->
case (X > 0) andalso (Y > 0) of
false ->
Acc;
true ->
case gleam_stdlib:map_get(Old_arr, X - 1) of
{ok, Item} ->
walk_origin_back(
Old_arr,
X - 1,
Y - 1,
[{equal, Item} | Acc]
);
{error, _} ->
Acc
end
end.
-file("src/textmetrics/diff.gleam", 208).
-spec myers_backtrack(
gleam@dict:dict(integer(), DQF),
gleam@dict:dict(integer(), DQF),
list(gleam@dict:dict(integer(), integer())),
integer(),
integer(),
integer(),
list(textmetrics@edit:edit(DQF))
) -> list(textmetrics@edit:edit(DQF)).
myers_backtrack(Old_arr, New_arr, Trace, D, X, Y, Acc) ->
case D of
0 ->
walk_origin_back(Old_arr, X, Y, Acc);
_ ->
case Trace of
[V | Rest] ->
K = X - Y,
V_minus = gleam@result:unwrap(
gleam_stdlib:map_get(V, K - 1),
-1
),
V_plus = gleam@result:unwrap(
gleam_stdlib:map_get(V, K + 1),
-1
),
From_above = (K =:= - D) orelse ((K /= D) andalso (V_minus < V_plus)),
Prev_k = case From_above of
true ->
K + 1;
false ->
K - 1
end,
Prev_x = gleam@result:unwrap(
gleam_stdlib:map_get(V, Prev_k),
0
),
Prev_y = Prev_x - Prev_k,
{X2, Y2, Acc2} = walk_diag_back(
Old_arr,
X,
Y,
Prev_x,
Prev_y,
Acc
),
{Next_x, Next_y, Acc3} = case {X2 > Prev_x, Y2 > Prev_y} of
{true, _} ->
case gleam_stdlib:map_get(Old_arr, X2 - 1) of
{ok, Item} ->
{X2 - 1, Y2, [{delete, Item} | Acc2]};
{error, _} ->
{Prev_x, Prev_y, Acc2}
end;
{false, true} ->
case gleam_stdlib:map_get(New_arr, Y2 - 1) of
{ok, Item@1} ->
{X2, Y2 - 1, [{insert, Item@1} | Acc2]};
{error, _} ->
{Prev_x, Prev_y, Acc2}
end;
{false, false} ->
{Prev_x, Prev_y, Acc2}
end,
myers_backtrack(
Old_arr,
New_arr,
Rest,
D - 1,
Next_x,
Next_y,
Acc3
);
[] ->
walk_origin_back(Old_arr, X, Y, Acc)
end
end.
-file("src/textmetrics/diff.gleam", 371).
-spec count_occurrences(list(binary())) -> gleam@dict:dict(binary(), integer()).
count_occurrences(Lines) ->
gleam@list:fold(
Lines,
maps:new(),
fun(Acc, Line) -> gleam@dict:upsert(Acc, Line, fun(Opt) -> case Opt of
{some, C} ->
C + 1;
none ->
1
end end) end
).
-file("src/textmetrics/diff.gleam", 382).
-spec first_index_of(list(binary())) -> gleam@dict:dict(binary(), integer()).
first_index_of(Lines) ->
gleam@list:index_fold(
Lines,
maps:new(),
fun(Acc, Line, I) -> case gleam@dict:has_key(Acc, Line) of
true ->
Acc;
false ->
gleam@dict:insert(Acc, Line, I)
end end
).
-file("src/textmetrics/diff.gleam", 354).
-spec find_unique_pairs(list(binary()), list(binary())) -> list({integer(),
integer()}).
find_unique_pairs(Old, New) ->
Old_counts = count_occurrences(Old),
New_counts = count_occurrences(New),
New_index_of = first_index_of(New),
_pipe = gleam@list:index_fold(
Old,
[],
fun(Acc, Line, Oi) ->
case {gleam_stdlib:map_get(Old_counts, Line),
gleam_stdlib:map_get(New_counts, Line),
gleam_stdlib:map_get(New_index_of, Line)} of
{{ok, 1}, {ok, 1}, {ok, Ni}} ->
[{Oi, Ni} | Acc];
{_, _, _} ->
Acc
end
end
),
lists:reverse(_pipe).
-file("src/textmetrics/diff.gleam", 422).
-spec scan_predecessors(
gleam@dict:dict(integer(), {integer(), integer()}),
gleam@dict:dict(integer(), integer()),
integer(),
integer(),
integer(),
integer()
) -> {integer(), integer()}.
scan_predecessors(Arr, Dp, I, J, Best_len, Best_prev) ->
case J >= I of
true ->
{Best_len, Best_prev};
false ->
Pi = case gleam_stdlib:map_get(Arr, I) of
{ok, P} ->
P;
_ ->
{0, 0}
end,
Pj = case gleam_stdlib:map_get(Arr, J) of
{ok, P@1} ->
P@1;
_ ->
{0, 0}
end,
{_, Ni} = Pi,
{_, Nj} = Pj,
Dp_j = gleam@result:unwrap(gleam_stdlib:map_get(Dp, J), 0),
Candidate = Dp_j + 1,
case (Nj < Ni) andalso (Candidate > Best_len) of
true ->
scan_predecessors(Arr, Dp, I, J + 1, Candidate, J);
false ->
scan_predecessors(Arr, Dp, I, J + 1, Best_len, Best_prev)
end
end.
-file("src/textmetrics/diff.gleam", 404).
-spec build_lis_tables(
gleam@dict:dict(integer(), {integer(), integer()}),
integer(),
integer(),
gleam@dict:dict(integer(), integer()),
gleam@dict:dict(integer(), integer())
) -> {gleam@dict:dict(integer(), integer()),
gleam@dict:dict(integer(), integer())}.
build_lis_tables(Arr, N, I, Dp, Prev) ->
case I >= N of
true ->
{Dp, Prev};
false ->
{Best_len, Best_prev} = scan_predecessors(Arr, Dp, I, 0, 1, -1),
Dp2 = gleam@dict:insert(Dp, I, Best_len),
Prev2 = gleam@dict:insert(Prev, I, Best_prev),
build_lis_tables(Arr, N, I + 1, Dp2, Prev2)
end.
-file("src/textmetrics/diff.gleam", 453).
-spec find_max_dp(
gleam@dict:dict(integer(), integer()),
integer(),
integer(),
integer(),
integer()
) -> integer().
find_max_dp(Dp, N, I, Best_len, Best_idx) ->
case I >= N of
true ->
Best_idx;
false ->
V = gleam@result:unwrap(gleam_stdlib:map_get(Dp, I), 0),
case V > Best_len of
true ->
find_max_dp(Dp, N, I + 1, V, I);
false ->
find_max_dp(Dp, N, I + 1, Best_len, Best_idx)
end
end.
-file("src/textmetrics/diff.gleam", 472).
-spec reconstruct_lis(
gleam@dict:dict(integer(), {integer(), integer()}),
gleam@dict:dict(integer(), integer()),
integer(),
list({integer(), integer()})
) -> list({integer(), integer()}).
reconstruct_lis(Arr, Prev, Idx, Acc) ->
case Idx < 0 of
true ->
Acc;
false ->
Pair = case gleam_stdlib:map_get(Arr, Idx) of
{ok, P} ->
P;
_ ->
{0, 0}
end,
Prev_idx = gleam@result:unwrap(gleam_stdlib:map_get(Prev, Idx), -1),
reconstruct_lis(Arr, Prev, Prev_idx, [Pair | Acc])
end.
-file("src/textmetrics/diff.gleam", 509).
-spec has_changes(list(textmetrics@edit:edit(binary()))) -> boolean().
has_changes(Script) ->
case Script of
[] ->
false;
[{equal, _} | Rest] ->
has_changes(Rest);
_ ->
true
end.
-file("src/textmetrics/diff.gleam", 523).
-spec script_to_events(
list(textmetrics@edit:edit(binary())),
integer(),
integer(),
list(line_event())
) -> list(line_event()).
script_to_events(Script, Old_pos, New_pos, Acc) ->
case Script of
[] ->
lists:reverse(Acc);
[{equal, Line} | Rest] ->
script_to_events(
Rest,
Old_pos + 1,
New_pos + 1,
[{context_event, Line, Old_pos, New_pos} | Acc]
);
[{delete, Line@1} | Rest@1] ->
script_to_events(
Rest@1,
Old_pos + 1,
New_pos,
[{delete_event, Line@1, Old_pos, New_pos} | Acc]
);
[{insert, Line@2} | Rest@2] ->
script_to_events(
Rest@2,
Old_pos,
New_pos + 1,
[{insert_event, Line@2, Old_pos, New_pos} | Acc]
)
end.
-file("src/textmetrics/diff.gleam", 572).
-spec find_change_indices(
gleam@dict:dict(integer(), line_event()),
integer(),
integer(),
list(integer())
) -> list(integer()).
find_change_indices(Events_arr, N, I, Acc) ->
case I >= N of
true ->
Acc;
false ->
New_acc = case gleam_stdlib:map_get(Events_arr, I) of
{ok, {insert_event, _, _, _}} ->
[I | Acc];
{ok, {delete_event, _, _, _}} ->
[I | Acc];
_ ->
Acc
end,
find_change_indices(Events_arr, N, I + 1, New_acc)
end.
-file("src/textmetrics/diff.gleam", 598).
-spec merge_ranges_loop(
list({integer(), integer()}),
{integer(), integer()},
list({integer(), integer()})
) -> list({integer(), integer()}).
merge_ranges_loop(Remaining, Current, Acc) ->
case Remaining of
[] ->
lists:reverse([Current | Acc]);
[{Lo, Hi} | Rest] ->
{C_lo, C_hi} = Current,
case Lo =< (C_hi + 1) of
true ->
merge_ranges_loop(
Rest,
{C_lo, gleam@int:max(C_hi, Hi)},
Acc
);
false ->
merge_ranges_loop(Rest, {Lo, Hi}, [Current | Acc])
end
end.
-file("src/textmetrics/diff.gleam", 591).
-spec merge_ranges(list({integer(), integer()})) -> list({integer(), integer()}).
merge_ranges(Ranges) ->
case Ranges of
[] ->
[];
[First | Rest] ->
merge_ranges_loop(Rest, First, [])
end.
-file("src/textmetrics/diff.gleam", 615).
-spec slice_events(
gleam@dict:dict(integer(), line_event()),
integer(),
integer(),
list(line_event())
) -> list(line_event()).
slice_events(Events_arr, I, End, Acc) ->
case I >= End of
true ->
lists:reverse(Acc);
false ->
case gleam_stdlib:map_get(Events_arr, I) of
{ok, E} ->
slice_events(Events_arr, I + 1, End, [E | Acc]);
{error, _} ->
lists:reverse(Acc)
end
end.
-file("src/textmetrics/diff.gleam", 639).
-spec format_range(integer(), integer()) -> binary().
format_range(Start, Count) ->
case Count of
1 ->
erlang:integer_to_binary(Start);
_ ->
<<<<(erlang:integer_to_binary(Start))/binary, ","/utf8>>/binary,
(erlang:integer_to_binary(Count))/binary>>
end.
-file("src/textmetrics/diff.gleam", 652).
-spec old_start_count(list(line_event())) -> {integer(), integer()}.
old_start_count(Events) ->
Folded = gleam@list:fold(
Events,
{-1, 0},
fun(Acc, E) ->
{Start, Count} = Acc,
case E of
{context_event, _, Op, _} ->
case Start of
-1 ->
{Op, Count + 1};
_ ->
{Start, Count + 1}
end;
{delete_event, _, Op@1, _} ->
case Start of
-1 ->
{Op@1, Count + 1};
_ ->
{Start, Count + 1}
end;
{insert_event, _, _, _} ->
Acc
end
end
),
{S, C} = Folded,
case S of
-1 ->
case Events of
[{insert_event, _, Op@2, _} | _] ->
{gleam@int:max(0, Op@2 - 1), 0};
_ ->
{0, 0}
end;
_ ->
{S, C}
end.
-file("src/textmetrics/diff.gleam", 681).
-spec new_start_count(list(line_event())) -> {integer(), integer()}.
new_start_count(Events) ->
Folded = gleam@list:fold(
Events,
{-1, 0},
fun(Acc, E) ->
{Start, Count} = Acc,
case E of
{context_event, _, _, Np} ->
case Start of
-1 ->
{Np, Count + 1};
_ ->
{Start, Count + 1}
end;
{insert_event, _, _, Np@1} ->
case Start of
-1 ->
{Np@1, Count + 1};
_ ->
{Start, Count + 1}
end;
{delete_event, _, _, _} ->
Acc
end
end
),
{S, C} = Folded,
case S of
-1 ->
case Events of
[{delete_event, _, _, Np@2} | _] ->
{gleam@int:max(0, Np@2 - 1), 0};
_ ->
{0, 0}
end;
_ ->
{S, C}
end.
-file("src/textmetrics/diff.gleam", 646).
-spec compute_hunk_meta(list(line_event())) -> {integer(),
integer(),
integer(),
integer()}.
compute_hunk_meta(Events) ->
{Os, Oc} = old_start_count(Events),
{Ns, Nc} = new_start_count(Events),
{Os, Oc, Ns, Nc}.
-file("src/textmetrics/diff.gleam", 710).
-spec event_to_line(line_event()) -> binary().
event_to_line(E) ->
case E of
{context_event, Line, _, _} ->
<<<<" "/utf8, Line/binary>>/binary, "\n"/utf8>>;
{delete_event, Line@1, _, _} ->
<<<<"-"/utf8, Line@1/binary>>/binary, "\n"/utf8>>;
{insert_event, Line@2, _, _} ->
<<<<"+"/utf8, Line@2/binary>>/binary, "\n"/utf8>>
end.
-file("src/textmetrics/diff.gleam", 631).
-spec emit_hunk(list(line_event())) -> binary().
emit_hunk(Events) ->
{Os, Oc, Ns, Nc} = compute_hunk_meta(Events),
Header = <<<<<<<<"@@ -"/utf8, (format_range(Os, Oc))/binary>>/binary,
" +"/utf8>>/binary,
(format_range(Ns, Nc))/binary>>/binary,
" @@\n"/utf8>>,
Body = begin
_pipe = gleam@list:map(Events, fun event_to_line/1),
erlang:list_to_binary(_pipe)
end,
<<Header/binary, Body/binary>>.
-file("src/textmetrics/diff.gleam", 722).
-spec list_to_dict_loop(list(DTS), integer(), gleam@dict:dict(integer(), DTS)) -> gleam@dict:dict(integer(), DTS).
list_to_dict_loop(Items, I, Acc) ->
case Items of
[] ->
Acc;
[X | Rest] ->
list_to_dict_loop(Rest, I + 1, gleam@dict:insert(Acc, I, X))
end.
-file("src/textmetrics/diff.gleam", 718).
-spec list_to_dict(list(DTO)) -> gleam@dict:dict(integer(), DTO).
list_to_dict(Items) ->
list_to_dict_loop(Items, 0, maps:new()).
-file("src/textmetrics/diff.gleam", 101).
?DOC(
" Optimal edit script transforming `old` into `new`, computed by the\n"
" Myers (1986) O(ND) algorithm.\n"
"\n"
" The script's `cost` (Insert + Delete) equals `levenshtein_list`\n"
" of `old` vs `new` with substitution counted as one insert plus\n"
" one delete. Tie-breaking prefers earlier deletions over insertions,\n"
" matching GNU `diff(1)`.\n"
).
-spec myers(list(DOY), list(DOY)) -> list(textmetrics@edit:edit(DOY)).
myers(Old, New) ->
M = erlang:length(Old),
N = erlang:length(New),
case {M, N} of
{0, 0} ->
[];
{0, _} ->
gleam@list:map(New, fun(Item) -> {insert, Item} end);
{_, 0} ->
gleam@list:map(Old, fun(Item@1) -> {delete, Item@1} end);
{_, _} ->
Old_arr = list_to_dict(Old),
New_arr = list_to_dict(New),
Initial_v = gleam@dict:insert(maps:new(), 1, 0),
Max_d = M + N,
{myers_result, Trace, Fx, Fy} = myers_search(
Old_arr,
New_arr,
M,
N,
Max_d,
0,
Initial_v,
[]
),
Trace_count = erlang:length(Trace),
myers_backtrack(
Old_arr,
New_arr,
Trace,
Trace_count - 1,
Fx,
Fy,
[]
)
end.
-file("src/textmetrics/diff.gleam", 391).
-spec longest_increasing_subseq(list({integer(), integer()})) -> list({integer(),
integer()}).
longest_increasing_subseq(Pairs) ->
N = erlang:length(Pairs),
case N of
0 ->
[];
_ ->
Arr = list_to_dict(Pairs),
{Dp, Prev} = build_lis_tables(Arr, N, 0, maps:new(), maps:new()),
Best_idx = find_max_dp(Dp, N, 0, 0, 0),
reconstruct_lis(Arr, Prev, Best_idx, [])
end.
-file("src/textmetrics/diff.gleam", 549).
-spec build_hunks(list(line_event()), integer()) -> list(list(line_event())).
build_hunks(Events, Context_lines) ->
Events_arr = list_to_dict(Events),
N = erlang:length(Events),
Change_indices = begin
_pipe = find_change_indices(Events_arr, N, 0, []),
lists:reverse(_pipe)
end,
case Change_indices of
[] ->
[];
_ ->
Ranges = gleam@list:map(
Change_indices,
fun(I) ->
{gleam@int:max(0, I - Context_lines),
gleam@int:min(N - 1, I + Context_lines)}
end
),
Merged = merge_ranges(Ranges),
gleam@list:map(
Merged,
fun(Range) ->
{Lo, Hi} = Range,
slice_events(Events_arr, Lo, Hi + 1, [])
end
)
end.
-file("src/textmetrics/diff.gleam", 495).
?DOC(
" Render an edit script of strings in POSIX unified-diff format.\n"
"\n"
" When the script contains no `Insert` or `Delete` steps the output\n"
" is exactly the empty string.\n"
).
-spec to_unified(list(textmetrics@edit:edit(binary())), unified_options()) -> binary().
to_unified(Script, Options) ->
case has_changes(Script) of
false ->
<<""/utf8>>;
true ->
Header = <<<<<<<<"--- "/utf8, (erlang:element(2, Options))/binary>>/binary,
"\n+++ "/utf8>>/binary,
(erlang:element(3, Options))/binary>>/binary,
"\n"/utf8>>,
Events = script_to_events(Script, 1, 1, []),
Hunks = build_hunks(Events, erlang:element(4, Options)),
Body = begin
_pipe = gleam@list:map(Hunks, fun emit_hunk/1),
erlang:list_to_binary(_pipe)
end,
<<Header/binary, Body/binary>>
end.
-file("src/textmetrics/diff.gleam", 316).
-spec patience_segments(
list(binary()),
list(binary()),
list({integer(), integer()}),
list(list(textmetrics@edit:edit(binary())))
) -> list(textmetrics@edit:edit(binary())).
patience_segments(Old, New, Anchors, Acc) ->
case Anchors of
[] ->
Sub = case {Old, New} of
{[], []} ->
[];
{_, _} ->
patience(Old, New)
end,
lists:append(lists:reverse([Sub | Acc]));
[{Oi, Ni} | Rest] ->
{Seg_old, Anchor_and_after_old} = gleam@list:split(Old, Oi),
{Seg_new, Anchor_and_after_new} = gleam@list:split(New, Ni),
Sub@1 = patience(Seg_old, Seg_new),
case {Anchor_and_after_old, Anchor_and_after_new} of
{[Anchor_old | Rest_old], [_ | Rest_new]} ->
Anchor_step = [{equal, Anchor_old}],
New_anchors = gleam@list:map(
Rest,
fun(P) ->
{O, N} = P,
{(O - Oi) - 1, (N - Ni) - 1}
end
),
patience_segments(
Rest_old,
Rest_new,
New_anchors,
[Anchor_step, Sub@1 | Acc]
);
{_, _} ->
lists:append(lists:reverse([Sub@1 | Acc]))
end
end.
-file("src/textmetrics/diff.gleam", 295).
?DOC(
" Patience diff (Bram Cohen). Identifies anchor lines that are unique\n"
" in both inputs, computes the longest increasing subsequence of\n"
" those anchors, and recursively diffs the surrounding segments.\n"
" Falls back to [`myers`](#myers) at leaves where no unique anchors\n"
" exist. Operates on `List(String)`.\n"
).
-spec patience(list(binary()), list(binary())) -> list(textmetrics@edit:edit(binary())).
patience(Old, New) ->
case {Old, New} of
{[], []} ->
[];
{[], _} ->
gleam@list:map(New, fun(Item) -> {insert, Item} end);
{_, []} ->
gleam@list:map(Old, fun(Item@1) -> {delete, Item@1} end);
{_, _} ->
Pairs = find_unique_pairs(Old, New),
case Pairs of
[] ->
myers(Old, New);
_ ->
Anchors = longest_increasing_subseq(Pairs),
case Anchors of
[] ->
myers(Old, New);
_ ->
patience_segments(Old, New, Anchors, [])
end
end
end.