diff options
Diffstat (limited to 'src/normal.cc')
| -rw-r--r-- | src/normal.cc | 96 |
1 files changed, 90 insertions, 6 deletions
diff --git a/src/normal.cc b/src/normal.cc index a2a2504b..37bbbb2a 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -1,11 +1,14 @@ #include "normal.hh" +#include "array_view.hh" +#include "assert.hh" #include "buffer.hh" #include "buffer_manager.hh" #include "buffer_utils.hh" #include "changes.hh" #include "command_manager.hh" #include "context.hh" +#include "coord.hh" #include "diff.hh" #include "enum.hh" #include "face_registry.hh" @@ -19,6 +22,7 @@ #include "selectors.hh" #include "shell_manager.hh" #include "string.hh" +#include "units.hh" #include "user_interface.hh" #include "unit_tests.hh" #include "window.hh" @@ -537,8 +541,9 @@ void command(Context& context, NormalParams params) command(context, std::move(env_vars), params.reg); } -BufferCoord apply_diff(Buffer& buffer, BufferCoord pos, ArrayView<StringView> lines_before, StringView after) +BufferRange apply_diff(Buffer& buffer, BufferCoord pos, ArrayView<StringView> lines_before, StringView after) { + BufferCoord first = pos; const auto lines_after = after | split_after<StringView>('\n') | gather<Vector<StringView>>(); auto byte_count = [](auto&& lines, int first, int count) { @@ -546,30 +551,108 @@ BufferCoord apply_diff(Buffer& buffer, BufferCoord pos, ArrayView<StringView> li [](ByteCount l, StringView s) { return l + s.length(); }); }; + bool tried_to_erase_final_newline = false; for_each_diff(lines_before.begin(), (int)lines_before.size(), lines_after.begin(), (int)lines_after.size(), [&, posA = 0, posB = 0](DiffOp op, int len) mutable { switch (op) { case DiffOp::Keep: + kak_assert(not tried_to_erase_final_newline); pos = buffer.advance(pos, byte_count(lines_before, posA, len)); posA += len; posB += len; break; case DiffOp::Add: + if (buffer.is_end(pos)) + tried_to_erase_final_newline = false; pos = buffer.insert(pos, {lines_after[posB].begin(), lines_after[posB + len - 1].end()}).end; posB += len; break; case DiffOp::Remove: - pos = buffer.erase(pos, buffer.advance(pos, byte_count(lines_before, posA, len))); + { + kak_assert(not tried_to_erase_final_newline); + BufferCoord end = buffer.advance(pos, byte_count(lines_before, posA, len)); + tried_to_erase_final_newline |= buffer.is_end(end); + pos = buffer.erase(pos, end); posA += len; break; } + } }); - return pos; + if (tried_to_erase_final_newline) + { + first = std::min(first, buffer.back_coord()); + pos = buffer.erase(buffer.back_coord(), buffer.end_coord()); + } + return {first, pos}; } +UnitTest test_apply_diff{[] { + using Change = Buffer::Change; + auto validate = [&](LineCount line, + StringView new_text, + BufferRange expected_new_range, + ConstArrayView<Change> expected_buffer_changes) + { + Buffer buffer{"", Buffer::Flags::None, { + StringData::create("line1\n"), + StringData::create("line2\n"), + StringData::create("line3\n"), + }}; + const size_t timestamp = buffer.timestamp(); + Vector<StringView> old_text; + for (auto i = line; i < buffer.line_count(); i++) + old_text.push_back(buffer[i]); + BufferRange new_text_range = apply_diff(buffer, BufferCoord{line, 0}, old_text, new_text); + kak_assert(new_text_range == expected_new_range); + kak_assert(buffer.changes_since(timestamp) == expected_buffer_changes); + }; + // When appending at end, we add any missing newline + validate( + /*line=*/3, + "added-line3-missing-eol", + BufferRange{{3, 0}, {4, 0}}, + { + Change{Change::Insert, {3, 0}, {4, 0}}, + } + ); + // Special case: erasing until buffer end also erases the final newline. + validate( + /*line=*/2, + "", + BufferRange{{1, 5}, {1, 5}}, + { + Change{Change::Erase, {2, 0}, {3, 0}}, + } + ); + // Erasing and appending at end still produces forward-only changes. + validate( + /*line=*/2, + "changed-line3\n" + "added-line4\n" + "added-line5\n", + BufferRange{{2, 0}, {5, 0}}, + { + Change{Change::Erase, {2, 0}, {3, 0}}, + Change{Change::Insert, {2, 0}, {5, 0}}, + } + ); + // Same result when append is missing newline. + validate( + /*line=*/2, + "changed-line3\n" + "added-line4\n" + "added-line5-missing-eol", + BufferRange{{2, 0}, {5, 0}}, + { + Change{Change::Erase, {2, 0}, {3, 0}}, + Change{Change::Insert, {2, 0}, {5, 0}}, + } + ); +}}; + template<bool replace> void pipe(Context& context, NormalParams params) { @@ -628,12 +711,12 @@ void pipe(Context& context, NormalParams params) if (in_lines.back().back() != '\n' and not out.empty() and out.back() == '\n') out.resize(out.length()-1, 0); - auto new_end = apply_diff(buffer, first, in_lines, out); - if (new_end != first) + auto [new_first, new_end] = apply_diff(buffer, first, in_lines, out); + if (new_first != new_end) { auto& min = sel.min(); auto& max = sel.max(); - min = first; + min = new_first; max = buffer.char_prev(new_end); } else @@ -1612,6 +1695,7 @@ void replay_macro(Context& context, NormalParams params) auto keys = parse_keys(reg_val[0]); ScopedEdition edition(context); ScopedSelectionEdition selection_edition{context}; + ScopedSetBool disable_keymaps(context.keymaps_disabled()); do { for (auto& key : keys) |
