From f02cf2704c39b1f6285a8e35a202d0237fd8acf2 Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Mon, 3 Jul 2023 17:16:16 +1200 Subject: [PATCH] LibDiff: Add support for writing formatted context hunks There is a little bit more complexity involved here than the other formats. In particular, this is due to the need to determine whether an addition line or removal line is just that, or a 'change'. --- Userland/Libraries/LibDiff/Format.cpp | 116 ++++++++++++++++++++++++++ Userland/Libraries/LibDiff/Format.h | 1 + Userland/Libraries/LibDiff/Hunks.h | 2 + 3 files changed, 119 insertions(+) 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)