summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes Altmanninger <aclopte@gmail.com>2024-11-28 14:10:22 +0100
committerMaxime Coste <mawww@kakoune.org>2025-06-26 11:42:43 +1000
commitba41928aa7b052b037092c38a7cd6a3c05bb928e (patch)
tree9b3e12ebf3a231e53d9516c65a97886f3a7b16b4
parentcff5f12a852a34a548aabfb6166c86fa35466a83 (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.
-rw-r--r--src/normal.cc26
-rw-r--r--test/normal/pipe-replaces-all-lines/cmd1
-rw-r--r--test/normal/pipe-replaces-all-lines/in2
-rw-r--r--test/normal/pipe-replaces-all-lines/out2
4 files changed, 25 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
diff --git a/test/normal/pipe-replaces-all-lines/cmd b/test/normal/pipe-replaces-all-lines/cmd
new file mode 100644
index 00000000..fb90dc1a
--- /dev/null
+++ b/test/normal/pipe-replaces-all-lines/cmd
@@ -0,0 +1 @@
+%|printf '\n\n'<ret>
diff --git a/test/normal/pipe-replaces-all-lines/in b/test/normal/pipe-replaces-all-lines/in
new file mode 100644
index 00000000..422c2b7a
--- /dev/null
+++ b/test/normal/pipe-replaces-all-lines/in
@@ -0,0 +1,2 @@
+a
+b
diff --git a/test/normal/pipe-replaces-all-lines/out b/test/normal/pipe-replaces-all-lines/out
new file mode 100644
index 00000000..139597f9
--- /dev/null
+++ b/test/normal/pipe-replaces-all-lines/out
@@ -0,0 +1,2 @@
+
+