diff --git a/Userland/Libraries/LibDiff/Format.cpp b/Userland/Libraries/LibDiff/Format.cpp index 7951a92d11..6417f6f9a5 100644 --- a/Userland/Libraries/LibDiff/Format.cpp +++ b/Userland/Libraries/LibDiff/Format.cpp @@ -7,6 +7,7 @@ */ #include "Format.h" +#include #include #include #include @@ -94,4 +95,119 @@ ErrorOr write_normal(Hunk const& hunk, Stream& stream, ColorOutput color_o return {}; } +struct SplitLines { + Vector old_lines; + Vector new_lines; +}; + +static ErrorOr split_hunk_into_old_and_new_lines(Hunk const& hunk) +{ + size_t new_lines_last_context = 0; + size_t old_lines_last_context = 0; + SplitLines lines; + + auto operation = Line::Operation::Context; + + bool is_all_insertions = true; + bool is_all_deletions = true; + + auto check_if_line_is_a_change = [&](Line::Operation op) { + if (operation != op) { + // We've switched from additions to removals or vice-versa. + // All lines starting from the last context line we saw must be changes. + operation = Line::Operation::Change; + for (size_t i = new_lines_last_context; i < lines.new_lines.size(); ++i) + lines.new_lines[i].operation = Line::Operation::Change; + for (size_t i = old_lines_last_context; i < lines.old_lines.size(); ++i) + lines.old_lines[i].operation = Line::Operation::Change; + } + }; + + for (auto const& line : hunk.lines) { + switch (line.operation) { + case Line::Operation::Context: + VERIFY(lines.old_lines.size() < hunk.location.old_range.number_of_lines); + VERIFY(lines.new_lines.size() < hunk.location.new_range.number_of_lines); + + operation = Line::Operation::Context; + TRY(lines.new_lines.try_append(Line { operation, line.content })); + TRY(lines.old_lines.try_append(Line { operation, line.content })); + new_lines_last_context = lines.new_lines.size(); + old_lines_last_context = lines.old_lines.size(); + break; + case Line::Operation::Addition: + VERIFY(lines.new_lines.size() < hunk.location.new_range.number_of_lines); + + if (operation != Line::Operation::Context) + check_if_line_is_a_change(Line::Operation::Addition); + else + operation = Line::Operation::Addition; + + TRY(lines.new_lines.try_append(Line { operation, line.content })); + is_all_deletions = false; + break; + case Line::Operation::Removal: + VERIFY(lines.old_lines.size() < hunk.location.old_range.number_of_lines); + + if (operation != Line::Operation::Context) + check_if_line_is_a_change(Line::Operation::Removal); + else + operation = Line::Operation::Removal; + + TRY(lines.old_lines.try_append(Line { operation, line.content })); + is_all_insertions = false; + break; + default: + VERIFY_NOT_REACHED(); + } + } + + VERIFY(lines.new_lines.size() == hunk.location.new_range.number_of_lines && lines.old_lines.size() == hunk.location.old_range.number_of_lines); + + if (is_all_insertions) + lines.old_lines.clear(); + else if (is_all_deletions) + lines.new_lines.clear(); + + return lines; +} + +static ErrorOr write_hunk_as_context(Vector const& old_lines, Vector const& new_lines, HunkLocation const& location, Stream& stream, ColorOutput color_output) +{ + TRY(stream.write_formatted("*** {}", location.old_range.start_line)); + + if (location.old_range.number_of_lines > 1) + TRY(stream.write_formatted(",{}", location.old_range.start_line + location.old_range.number_of_lines - 1)); + + TRY(stream.write_formatted(" ****\n")); + + for (auto const& line : old_lines) { + if (color_output == ColorOutput::Yes && (line.operation == Line::Operation::Removal || line.operation == Line::Operation::Change)) + TRY(stream.write_formatted("\033[31;1m{} {}\033[0m\n", line.operation, line.content)); + else + TRY(stream.write_formatted("{} {}\n", line.operation, line.content)); + } + + TRY(stream.write_formatted("--- {}", location.new_range.start_line)); + if (location.new_range.number_of_lines > 1) + TRY(stream.write_formatted(",{}", location.new_range.start_line + location.new_range.number_of_lines - 1)); + + TRY(stream.write_formatted(" ----\n")); + + for (auto const& line : new_lines) { + if (color_output == ColorOutput::Yes && (line.operation == Line::Operation::Addition || line.operation == Line::Operation::Change)) + TRY(stream.write_formatted("\033[32;1m{} {}\033[0m\n", line.operation, line.content)); + else + TRY(stream.write_formatted("{} {}\n", line.operation, line.content)); + } + + return {}; +} + +ErrorOr write_context(Hunk const& hunk, Stream& stream, ColorOutput color_output) +{ + auto const split_lines = TRY(split_hunk_into_old_and_new_lines(hunk)); + return write_hunk_as_context(split_lines.old_lines, split_lines.new_lines, hunk.location, stream, color_output); +} + } diff --git a/Userland/Libraries/LibDiff/Format.h b/Userland/Libraries/LibDiff/Format.h index 303c804c24..a80d2fbb26 100644 --- a/Userland/Libraries/LibDiff/Format.h +++ b/Userland/Libraries/LibDiff/Format.h @@ -25,4 +25,5 @@ ErrorOr write_unified_header(StringView old_path, StringView new_path, Str ErrorOr write_normal(Hunk const& hunk, Stream& stream, ColorOutput color_output = ColorOutput::No); +ErrorOr write_context(Hunk const& hunk, Stream& stream, ColorOutput color_output = ColorOutput::No); } diff --git a/Userland/Libraries/LibDiff/Hunks.h b/Userland/Libraries/LibDiff/Hunks.h index a67a0e00b6..ca89d35e63 100644 --- a/Userland/Libraries/LibDiff/Hunks.h +++ b/Userland/Libraries/LibDiff/Hunks.h @@ -31,6 +31,8 @@ struct Line { Removal = '-', Context = ' ', + // NOTE: This should only be used when deconstructing a hunk into old and new lines (context format) + Change = '!', }; static constexpr Operation operation_from_symbol(char symbol)