diff options
| author | Johannes Altmanninger <aclopte@gmail.com> | 2024-11-28 14:10:22 +0100 |
|---|---|---|
| committer | Maxime Coste <mawww@kakoune.org> | 2025-06-26 11:42:43 +1000 |
| commit | ba41928aa7b052b037092c38a7cd6a3c05bb928e (patch) | |
| tree | 9b3e12ebf3a231e53d9516c65a97886f3a7b16b4 /src | |
| parent | cff5f12a852a34a548aabfb6166c86fa35466a83 (diff) | |
Remove spurious newline when | replaces at buffer end
My
kak -e "exec %{%ca<ret>b<esc>%|printf '\n\n'<ret>}"
adds a spurious third line.
When we replace up to the end everything, we keep around a single
newline to uphold the buffer invariant, but that newline
Commit de1433d30 (Avoid the spurious newline insertion when replacing
at end of buffer, 2016-03-16) fixed an issue for most commands that
replace until the buffer end.
A similar issue exists for the "|" command. It triggers in fewer
cases because the replacement is implemented by applying edits
computed by the diff algorithm. It does trigger when "|" replaces
the entire buffer.
Fix that by erasing the spurious newline.
Alternatively, we could allow the diff steps of kind "remove" to
delete the entire buffer, and only restore the invariant after the
whole diff is applied. This should work because the one-past-end
position is valid for Buffer::insert() even if the buffer is empty. It
is not valid for Buffer::erase() or Buffer::advance() where count>0
but if we do that when we're already at the buffer end, that is
probably a bug in the diff. I tried this but ran into a assertion
in ForwardChangesTracker (kak_assert(change.begin >= cur_pos)).
Alternatively, we could change the diff algorithm to always insert
before deleting.
I encountered this issue when using ansi-enable on a fifo buffer.
Specifically, the first BufReadFifo hook would replace the entire
inserted text but leave around a spurious newline.
Diffstat (limited to 'src')
| -rw-r--r-- | src/normal.cc | 26 |
1 files changed, 20 insertions, 6 deletions
diff --git a/src/normal.cc b/src/normal.cc index 0d216436..cdc618a0 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -6,6 +6,7 @@ #include "changes.hh" #include "command_manager.hh" #include "context.hh" +#include "coord.hh" #include "diff.hh" #include "enum.hh" #include "face_registry.hh" @@ -537,8 +538,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,6 +548,7 @@ 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 { @@ -557,17 +560,28 @@ BufferCoord apply_diff(Buffer& buffer, BufferCoord pos, ArrayView<StringView> li posB += len; break; case DiffOp::Add: + if (buffer.is_end(pos) and tried_to_erase_final_newline) + pos = buffer.prev(pos); 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))); + { + 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}; } template<bool replace> @@ -628,12 +642,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 |
