aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBjörn Linse <bjorn.linse@gmail.com>2020-02-29 15:27:17 +0100
committerBjörn Linse <bjorn.linse@gmail.com>2020-09-09 21:22:21 +0200
commitbc86f76c0a1d3234b749a105c9aae65f84c51320 (patch)
treec8930079e178c2a3233ef43602f7136d3f6981fb
parent81fa107f595ee0392b5f004b86e4e8d41e49cc9e (diff)
downloadrneovim-bc86f76c0a1d3234b749a105c9aae65f84c51320.tar.gz
rneovim-bc86f76c0a1d3234b749a105c9aae65f84c51320.tar.bz2
rneovim-bc86f76c0a1d3234b749a105c9aae65f84c51320.zip
api/buffer: add "on_bytes" callback to nvim_buf_attach
This implements byte-resolution updates of buffer changes. Note: there is no promise that the buffer state is valid inside the callback!
-rw-r--r--src/nvim/api/buffer.c4
-rw-r--r--src/nvim/buffer_defs.h1
-rw-r--r--src/nvim/buffer_updates.c22
-rw-r--r--src/nvim/buffer_updates.h1
-rw-r--r--src/nvim/change.c14
-rw-r--r--src/nvim/ex_cmds.c41
-rw-r--r--src/nvim/extmark.c135
-rw-r--r--src/nvim/extmark.h12
-rw-r--r--src/nvim/fileio.c1
-rw-r--r--src/nvim/indent.c5
-rw-r--r--src/nvim/memline.c13
-rw-r--r--src/nvim/ops.c34
-rw-r--r--test/functional/lua/buffer_updates_spec.lua118
13 files changed, 278 insertions, 123 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 5290011325..ab768a1aef 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -175,8 +175,7 @@ Boolean nvim_buf_attach(uint64_t channel_id,
}
cb.on_lines = v->data.luaref;
v->data.luaref = LUA_NOREF;
- } else if (is_lua && strequal("_on_bytes", k.data)) {
- // NB: undocumented, untested and incomplete interface!
+ } else if (is_lua && strequal("on_bytes", k.data)) {
if (v->type != kObjectTypeLuaRef) {
api_set_error(err, kErrorTypeValidation, "callback is not a function");
goto error;
@@ -1796,6 +1795,7 @@ Dictionary nvim__buf_stats(Buffer buffer, Error *err)
// NB: this should be zero at any time API functions are called,
// this exists to debug issues
PUT(rv, "dirty_bytes", INTEGER_OBJ((Integer)buf->deleted_bytes));
+ PUT(rv, "dirty_bytes2", INTEGER_OBJ((Integer)buf->deleted_bytes2));
u_header_T *uhp = NULL;
if (buf->b_u_curhead != NULL) {
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 550f8a5e40..b3c95f9362 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -835,6 +835,7 @@ struct file_buffer {
// tree-sitter) or the corresponding UTF-32/UTF-16 size (like LSP) of the
// deleted text.
size_t deleted_bytes;
+ size_t deleted_bytes2;
size_t deleted_codepoints;
size_t deleted_codeunits;
diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c
index e6393bf02c..1e7cc8520f 100644
--- a/src/nvim/buffer_updates.c
+++ b/src/nvim/buffer_updates.c
@@ -2,6 +2,7 @@
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include "nvim/buffer_updates.h"
+#include "nvim/extmark.h"
#include "nvim/memline.h"
#include "nvim/api/private/helpers.h"
#include "nvim/msgpack_rpc/channel.h"
@@ -282,9 +283,9 @@ void buf_updates_send_changes(buf_T *buf,
}
void buf_updates_send_splice(buf_T *buf,
- linenr_T start_line, colnr_T start_col,
- linenr_T oldextent_line, colnr_T oldextent_col,
- linenr_T newextent_line, colnr_T newextent_col)
+ int start_row, colnr_T start_col, bcount_t start_byte,
+ int old_row, colnr_T old_col, bcount_t old_byte,
+ int new_row, colnr_T new_col, bcount_t new_byte)
{
if (!buf_updates_active(buf)) {
return;
@@ -296,7 +297,7 @@ void buf_updates_send_splice(buf_T *buf,
BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
bool keep = true;
if (cb.on_bytes != LUA_NOREF) {
- FIXED_TEMP_ARRAY(args, 8);
+ FIXED_TEMP_ARRAY(args, 11);
// the first argument is always the buffer handle
args.items[0] = BUFFER_OBJ(buf->handle);
@@ -304,12 +305,15 @@ void buf_updates_send_splice(buf_T *buf,
// next argument is b:changedtick
args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf));
- args.items[2] = INTEGER_OBJ(start_line);
+ args.items[2] = INTEGER_OBJ(start_row);
args.items[3] = INTEGER_OBJ(start_col);
- args.items[4] = INTEGER_OBJ(oldextent_line);
- args.items[5] = INTEGER_OBJ(oldextent_col);
- args.items[6] = INTEGER_OBJ(newextent_line);
- args.items[7] = INTEGER_OBJ(newextent_col);
+ args.items[4] = INTEGER_OBJ(start_byte);
+ args.items[5] = INTEGER_OBJ(old_row);
+ args.items[6] = INTEGER_OBJ(old_col);
+ args.items[7] = INTEGER_OBJ(old_byte);
+ args.items[8] = INTEGER_OBJ(new_row);
+ args.items[9] = INTEGER_OBJ(new_col);
+ args.items[10] = INTEGER_OBJ(new_byte);
textlock++;
Object res = executor_exec_lua_cb(cb.on_bytes, "bytes", args, true, NULL);
diff --git a/src/nvim/buffer_updates.h b/src/nvim/buffer_updates.h
index b2d0a62270..961fec879b 100644
--- a/src/nvim/buffer_updates.h
+++ b/src/nvim/buffer_updates.h
@@ -2,6 +2,7 @@
#define NVIM_BUFFER_UPDATES_H
#include "nvim/buffer_defs.h"
+#include "nvim/extmark.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "buffer_updates.h.generated.h"
diff --git a/src/nvim/change.c b/src/nvim/change.c
index df95b583cd..6ffc26332a 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -1597,7 +1597,7 @@ int open_line(
if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count
|| curwin->w_p_diff) {
mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L,
- kExtmarkUndo);
+ kExtmarkNOOP);
}
did_append = true;
} else {
@@ -1611,6 +1611,7 @@ int open_line(
}
ml_replace(curwin->w_cursor.lnum, p_extra, true);
changed_bytes(curwin->w_cursor.lnum, 0);
+ // TODO: extmark_splice_cols here??
curwin->w_cursor.lnum--;
did_append = false;
}
@@ -1691,8 +1692,9 @@ int open_line(
// Always move extmarks - Here we move only the line where the
// cursor is, the previous mark_adjust takes care of the lines after
int cols_added = mincol-1+less_cols_off-less_cols;
- extmark_splice(curbuf, (int)lnum-1, mincol-1, 0, less_cols_off,
- 1, cols_added, kExtmarkUndo);
+ extmark_splice(curbuf, (int)lnum-1, mincol-1,
+ 0, less_cols_off, less_cols_off,
+ 1, cols_added, 1 + cols_added, kExtmarkUndo);
} else {
changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col);
}
@@ -1704,8 +1706,10 @@ int open_line(
}
if (did_append) {
changed_lines(curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1L, true);
- extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1,
- 0, 0, 0, 1, 0, kExtmarkUndo);
+ // bail out and just get the final lenght of the line we just manipulated
+ bcount_t extra = (bcount_t)STRLEN(ml_get(curwin->w_cursor.lnum));
+ extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, 0,
+ 0, 0, 0, 1, 0, 1+extra, kExtmarkUndo);
}
curbuf_splice_pending--;
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 2bac6cba58..9be6adcd61 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -851,6 +851,11 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
return OK;
}
+ bcount_t start_byte = ml_find_line_or_offset(curbuf, line1, NULL, true);
+ bcount_t end_byte = ml_find_line_or_offset(curbuf, line2+1, NULL, true);
+ bcount_t extent_byte = end_byte-start_byte;
+ bcount_t dest_byte = ml_find_line_or_offset(curbuf, dest+1, NULL, true);
+
num_lines = line2 - line1 + 1;
/*
@@ -885,6 +890,8 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
last_line = curbuf->b_ml.ml_line_count;
mark_adjust_nofold(line1, line2, last_line - line2, 0L, kExtmarkNOOP);
changed_lines(last_line - num_lines + 1, 0, last_line + 1, num_lines, false);
+ int line_off = 0;
+ bcount_t byte_off = 0;
if (dest >= line2) {
mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, kExtmarkNOOP);
FOR_ALL_TAB_WINDOWS(tab, win) {
@@ -894,6 +901,8 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
}
curbuf->b_op_start.lnum = dest - num_lines + 1;
curbuf->b_op_end.lnum = dest;
+ line_off = -num_lines;
+ byte_off = -extent_byte;
} else {
mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, kExtmarkNOOP);
FOR_ALL_TAB_WINDOWS(tab, win) {
@@ -909,11 +918,10 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
-(last_line - dest - extra), 0L, kExtmarkNOOP);
// extmarks are handled separately
- int size = line2-line1+1;
- int off = dest >= line2 ? -size : 0;
- extmark_move_region(curbuf, line1-1, 0,
- line2-line1+1, 0,
- dest+off, 0, kExtmarkUndo);
+ extmark_move_region(curbuf, line1-1, 0, start_byte,
+ line2-line1+1, 0, extent_byte,
+ dest+line_off, 0, dest_byte+byte_off,
+ kExtmarkUndo);
changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false);
@@ -3913,6 +3921,18 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
ADJUST_SUB_FIRSTLNUM();
+ // TODO(bfredl): adjust also in preview, because decorations?
+ // this has some robustness issues, will look into later.
+ bool do_splice = !preview;
+ bcount_t replaced_bytes = 0;
+ lpos_T start = regmatch.startpos[0], end = regmatch.endpos[0];
+ if (do_splice) {
+ for (i = 0; i < nmatch-1; i++) {
+ replaced_bytes += STRLEN(ml_get(lnum_start+i)) + 1;
+ }
+ replaced_bytes += end.col - start.col;
+ }
+
// Now the trick is to replace CTRL-M chars with a real line
// break. This would make it impossible to insert a CTRL-M in
@@ -3956,17 +3976,14 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
current_match.end.col = new_endcol;
current_match.end.lnum = lnum;
- // TODO(bfredl): adjust in preview, because decorations?
- // this has some robustness issues, will look into later.
- if (!preview) {
- lpos_T start = regmatch.startpos[0], end = regmatch.endpos[0];
+ if (do_splice) {
int matchcols = end.col - ((end.lnum == start.lnum)
? start.col : 0);
int subcols = new_endcol - ((lnum == lnum_start) ? start_col : 0);
extmark_splice(curbuf, lnum_start-1, start_col,
- end.lnum-start.lnum, matchcols,
- lnum-lnum_start, subcols, kExtmarkUndo);
- }
+ end.lnum-start.lnum, matchcols, replaced_bytes,
+ lnum-lnum_start, subcols, sublen-1, kExtmarkUndo);
+ }
}
diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c
index bda7829507..bb981cb2f6 100644
--- a/src/nvim/extmark.c
+++ b/src/nvim/extmark.c
@@ -479,18 +479,18 @@ void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo)
// Undo
ExtmarkSplice splice = undo_info.data.splice;
if (undo) {
- extmark_splice(curbuf,
- splice.start_row, splice.start_col,
- splice.new_row, splice.new_col,
- splice.old_row, splice.old_col,
- kExtmarkNoUndo);
+ extmark_splice_impl(curbuf,
+ splice.start_row, splice.start_col, splice.start_byte,
+ splice.new_row, splice.new_col, splice.new_byte,
+ splice.old_row, splice.old_col, splice.old_byte,
+ kExtmarkNoUndo);
} else {
- extmark_splice(curbuf,
- splice.start_row, splice.start_col,
- splice.old_row, splice.old_col,
- splice.new_row, splice.new_col,
- kExtmarkNoUndo);
+ extmark_splice_impl(curbuf,
+ splice.start_row, splice.start_col, splice.start_byte,
+ splice.old_row, splice.old_col, splice.old_byte,
+ splice.new_row, splice.new_col, splice.new_byte,
+ kExtmarkNoUndo);
}
// kExtmarkSavePos
} else if (undo_info.type == kExtmarkSavePos) {
@@ -509,15 +509,15 @@ void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo)
ExtmarkMove move = undo_info.data.move;
if (undo) {
extmark_move_region(curbuf,
- move.new_row, move.new_col,
- move.extent_row, move.extent_col,
- move.start_row, move.start_col,
+ move.new_row, move.new_col, move.new_byte,
+ move.extent_row, move.extent_col, move.extent_byte,
+ move.start_row, move.start_col, move.start_byte,
kExtmarkNoUndo);
} else {
extmark_move_region(curbuf,
- move.start_row, move.start_col,
- move.extent_row, move.extent_col,
- move.new_row, move.new_col,
+ move.start_row, move.start_col, move.start_byte,
+ move.extent_row, move.extent_col, move.extent_byte,
+ move.new_row, move.new_col, move.new_byte,
kExtmarkNoUndo);
}
}
@@ -532,36 +532,57 @@ void extmark_adjust(buf_T *buf,
long amount_after,
ExtmarkOp undo)
{
- if (!curbuf_splice_pending) {
- int old_extent, new_extent;
- if (amount == MAXLNUM) {
- old_extent = (int)(line2 - line1+1);
- new_extent = (int)(amount_after + old_extent);
- } else {
- // A region is either deleted (amount == MAXLNUM) or
- // added (line2 == MAXLNUM). The only other case is :move
- // which is handled by a separate entry point extmark_move_region.
- assert(line2 == MAXLNUM);
- old_extent = 0;
- new_extent = (int)amount;
- }
- extmark_splice(buf,
- (int)line1-1, 0,
- old_extent, 0,
- new_extent, 0, undo);
+ if (curbuf_splice_pending) {
+ return;
}
-}
+ bcount_t start_byte = ml_find_line_or_offset(buf, line1, NULL, true);
+ bcount_t old_byte = 0, new_byte = 0;
+ int old_row, new_row;
+ if (amount == MAXLNUM) {
+ old_row = (int)(line2 - line1+1);
+ // TODO: ej kasta?
+ old_byte = (bcount_t)buf->deleted_bytes2;
+ new_row = (int)(amount_after + old_row);
+ } else {
+ // A region is either deleted (amount == MAXLNUM) or
+ // added (line2 == MAXLNUM). The only other case is :move
+ // which is handled by a separate entry point extmark_move_region.
+ assert(line2 == MAXLNUM);
+ old_row = 0;
+ new_row = (int)amount;
+ }
+ if (new_row > 0) {
+ new_byte = ml_find_line_or_offset(buf, line1+new_row, NULL, true)-start_byte;
+ }
+ extmark_splice_impl(buf,
+ (int)line1-1, 0, start_byte,
+ old_row, 0, old_byte,
+ new_row, 0, new_byte, undo);
+}
void extmark_splice(buf_T *buf,
- int start_row, colnr_T start_col,
- int old_row, colnr_T old_col,
- int new_row, colnr_T new_col,
- ExtmarkOp undo)
+ int start_row, colnr_T start_col,
+ int old_row, colnr_T old_col, bcount_t old_byte,
+ int new_row, colnr_T new_col, bcount_t new_byte,
+ ExtmarkOp undo)
{
- buf_updates_send_splice(buf, start_row, start_col,
- old_row, old_col,
- new_row, new_col);
+ long offset = ml_find_line_or_offset(buf, start_row+1, NULL, true);
+ extmark_splice_impl(buf, start_row, start_col, offset+start_col,
+ old_row, old_col, old_byte, new_row, new_col, new_byte,
+ undo);
+}
+
+void extmark_splice_impl(buf_T *buf,
+ int start_row, colnr_T start_col, bcount_t start_byte,
+ int old_row, colnr_T old_col, bcount_t old_byte,
+ int new_row, colnr_T new_col, bcount_t new_byte,
+ ExtmarkOp undo)
+{
+ curbuf->deleted_bytes2 = 0;
+ buf_updates_send_splice(buf, start_row, start_col, start_byte,
+ old_row, old_col, old_byte,
+ new_row, new_col, new_byte);
if (undo == kExtmarkUndo && (old_row > 0 || old_col > 0)) {
// Copy marks that would be effected by delete
@@ -599,15 +620,19 @@ void extmark_splice(buf_T *buf,
if (old_col == 0 && start_col >= splice->start_col
&& start_col <= splice->start_col+splice->new_col) {
splice->new_col += new_col;
+ splice->new_byte += new_byte;
merged = true;
} else if (new_col == 0
&& start_col == splice->start_col+splice->new_col) {
splice->old_col += old_col;
+ splice->old_byte += old_byte;
merged = true;
} else if (new_col == 0
&& start_col + old_col == splice->start_col) {
splice->start_col = start_col;
+ splice->start_byte = start_byte;
splice->old_col += old_col;
+ splice->old_byte += old_byte;
merged = true;
}
}
@@ -618,10 +643,13 @@ void extmark_splice(buf_T *buf,
ExtmarkSplice splice;
splice.start_row = start_row;
splice.start_col = start_col;
+ splice.start_byte = start_byte;
splice.old_row = old_row;
splice.old_col = old_col;
+ splice.old_byte = old_byte;
splice.new_row = new_row;
splice.new_col = new_col;
+ splice.new_byte = new_byte;
kv_push(uhp->uh_extmark,
((ExtmarkUndoObject){ .type = kExtmarkSplice,
@@ -635,29 +663,31 @@ void extmark_splice_cols(buf_T *buf,
colnr_T old_col, colnr_T new_col,
ExtmarkOp undo)
{
- extmark_splice(buf, start_row, start_col, 0, old_col, 0, new_col, undo);
+ extmark_splice(buf, start_row, start_col,
+ 0, old_col, old_col,
+ 0, new_col, new_col, undo);
}
void extmark_move_region(buf_T *buf,
- int start_row, colnr_T start_col,
- int extent_row, colnr_T extent_col,
- int new_row, colnr_T new_col,
+ int start_row, colnr_T start_col, bcount_t start_byte,
+ int extent_row, colnr_T extent_col, bcount_t extent_byte,
+ int new_row, colnr_T new_col, bcount_t new_byte,
ExtmarkOp undo)
{
// TODO(bfredl): this is not synced to the buffer state inside the callback.
// But unless we make the undo implementation smarter, this is not ensured
// anyway.
- buf_updates_send_splice(buf, start_row, start_col,
- extent_row, extent_col,
- 0, 0);
+ buf_updates_send_splice(buf, start_row, start_col, start_byte,
+ extent_row, extent_col, extent_byte,
+ 0, 0, 0);
marktree_move_region(buf->b_marktree, start_row, start_col,
extent_row, extent_col,
new_row, new_col);
- buf_updates_send_splice(buf, new_row, new_col,
- 0, 0,
- extent_row, extent_col);
+ buf_updates_send_splice(buf, new_row, new_col, new_byte,
+ 0, 0, 0,
+ extent_row, extent_col, extent_byte);
if (undo == kExtmarkUndo) {
@@ -669,10 +699,13 @@ void extmark_move_region(buf_T *buf,
ExtmarkMove move;
move.start_row = start_row;
move.start_col = start_col;
+ move.start_byte = start_byte;
move.extent_row = extent_row;
move.extent_col = extent_col;
+ move.extent_byte = extent_byte;
move.new_row = new_row;
move.new_col = new_col;
+ move.new_byte = new_byte;
kv_push(uhp->uh_extmark,
((ExtmarkUndoObject){ .type = kExtmarkMove,
diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h
index e1197834bd..534e97a7f4 100644
--- a/src/nvim/extmark.h
+++ b/src/nvim/extmark.h
@@ -20,6 +20,9 @@ typedef struct
typedef kvec_t(ExtmarkInfo) ExtmarkInfoArray;
+// TODO(bfredl): good enough name for now.
+typedef ptrdiff_t bcount_t;
+
// delete the columns between mincol and endcol
typedef struct {
@@ -29,9 +32,9 @@ typedef struct {
colnr_T old_col;
int new_row;
colnr_T new_col;
- size_t start_byte;
- size_t old_byte;
- size_t new_byte;
+ bcount_t start_byte;
+ bcount_t old_byte;
+ bcount_t new_byte;
} ExtmarkSplice;
// adjust marks after :move operation
@@ -42,6 +45,9 @@ typedef struct {
int extent_col;
int new_row;
int new_col;
+ bcount_t start_byte;
+ bcount_t extent_byte;
+ bcount_t new_byte;
} ExtmarkMove;
// extmark was updated
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 7740673bbe..286f2b4fca 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -1797,6 +1797,7 @@ failed:
linecnt--;
}
curbuf->deleted_bytes = 0;
+ curbuf->deleted_bytes2 = 0;
curbuf->deleted_codepoints = 0;
curbuf->deleted_codeunits = 0;
linecnt = curbuf->b_ml.ml_line_count - linecnt;
diff --git a/src/nvim/indent.c b/src/nvim/indent.c
index fb277b25fd..25a9b38b5d 100644
--- a/src/nvim/indent.c
+++ b/src/nvim/indent.c
@@ -297,10 +297,9 @@ int set_indent(int size, int flags)
if (!(flags & SIN_UNDO) || (u_savesub(curwin->w_cursor.lnum) == OK)) {
ml_replace(curwin->w_cursor.lnum, newline, false);
if (!(flags & SIN_NOMARK)) {
- extmark_splice(curbuf,
+ extmark_splice_cols(curbuf,
(int)curwin->w_cursor.lnum-1, skipcols,
- 0, (int)(p-oldline) - skipcols,
- 0, (int)(s-newline) - skipcols,
+ (int)(p-oldline) - skipcols, (int)(s-newline) - skipcols,
kExtmarkUndo);
}
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 7d84c1cca3..9dde4a401b 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -2405,12 +2405,13 @@ void ml_add_deleted_len_buf(buf_T *buf, char_u *ptr, ssize_t len)
if (len == -1) {
len = STRLEN(ptr);
}
- buf->deleted_bytes += len+1;
- if (buf->update_need_codepoints) {
- mb_utflen(ptr, len, &buf->deleted_codepoints,
- &buf->deleted_codeunits);
- buf->deleted_codepoints++; // NL char
- buf->deleted_codeunits++;
+ curbuf->deleted_bytes += len+1;
+ curbuf->deleted_bytes2 += len+1;
+ if (curbuf->update_need_codepoints) {
+ mb_utflen(ptr, len, &curbuf->deleted_codepoints,
+ &curbuf->deleted_codeunits);
+ curbuf->deleted_codepoints++; // NL char
+ curbuf->deleted_codeunits++;
}
}
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 14cd189bc3..1f55d2c315 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -1659,17 +1659,20 @@ int op_delete(oparg_T *oap)
curpos = curwin->w_cursor; // remember curwin->w_cursor
curwin->w_cursor.lnum++;
del_lines(oap->line_count - 2, false);
+ bcount_t deleted_bytes = (bcount_t)curbuf->deleted_bytes2 - startpos.col;
// delete from start of line until op_end
n = (oap->end.col + 1 - !oap->inclusive);
curwin->w_cursor.col = 0;
(void)del_bytes((colnr_T)n, !virtual_op,
oap->op_type == OP_DELETE && !oap->is_VIsual);
+ deleted_bytes += n;
curwin->w_cursor = curpos; // restore curwin->w_cursor
(void)do_join(2, false, false, false, false);
curbuf_splice_pending--;
extmark_splice(curbuf, (int)startpos.lnum-1, startpos.col,
- (int)oap->line_count-1, n, 0, 0, kExtmarkUndo);
+ (int)oap->line_count-1, n, deleted_bytes,
+ 0, 0, 0, kExtmarkUndo);
}
}
@@ -1854,6 +1857,7 @@ int op_replace(oparg_T *oap, int c)
}
// replace the line
ml_replace(curwin->w_cursor.lnum, newp, false);
+ curbuf_splice_pending++;
linenr_T baselnum = curwin->w_cursor.lnum;
if (after_p != NULL) {
ml_append(curwin->w_cursor.lnum++, after_p, (int)after_p_len, false);
@@ -1861,9 +1865,10 @@ int op_replace(oparg_T *oap, int c)
oap->end.lnum++;
xfree(after_p);
}
+ curbuf_splice_pending--;
extmark_splice(curbuf, (int)baselnum-1, bd.textcol,
- 0, bd.textlen,
- newrows, newcols, kExtmarkUndo);
+ 0, bd.textlen, bd.textlen,
+ newrows, newcols, newrows+newcols, kExtmarkUndo);
}
} else {
// Characterwise or linewise motion replace.
@@ -3369,13 +3374,23 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
}
+ bcount_t totsize = 0;
+ int lastsize = 0;
+ if (y_type == kMTCharWise
+ || (y_type == kMTLineWise && flags & PUT_LINE_SPLIT)) {
+ for (i = 0; i < y_size-1; i++) {
+ totsize += (bcount_t)STRLEN(y_array[i]) + 1;
+ }
+ lastsize = (int)STRLEN(y_array[y_size-1]);
+ totsize += lastsize;
+ }
if (y_type == kMTCharWise) {
- extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0,
- (int)y_size-1, (int)STRLEN(y_array[y_size-1]),
+ extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0, 0,
+ (int)y_size-1, lastsize, totsize,
kExtmarkUndo);
} else if (y_type == kMTLineWise && flags & PUT_LINE_SPLIT) {
- extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0,
- (int)y_size+1, 0, kExtmarkUndo);
+ extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0, 0,
+ (int)y_size+1, 0, totsize+1, kExtmarkUndo);
}
}
@@ -3819,9 +3834,10 @@ int do_join(size_t count,
}
if (t > 0 && curbuf_splice_pending == 0) {
+ colnr_T removed = (int)(curr- curr_start);
extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, sumsize,
- 1, (int)(curr- curr_start),
- 0, spaces[t],
+ 1, removed, removed + 1,
+ 0, spaces[t], spaces[t],
kExtmarkUndo);
}
currsize = (int)STRLEN(curr);
diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua
index 364d4524ad..a798d4fcbb 100644
--- a/test/functional/lua/buffer_updates_spec.lua
+++ b/test/functional/lua/buffer_updates_spec.lua
@@ -7,6 +7,7 @@ local clear = helpers.clear
local eq = helpers.eq
local exec_lua = helpers.exec_lua
local feed = helpers.feed
+local deepcopy = helpers.deepcopy
local origlines = {"original line 1",
"original line 2",
@@ -16,32 +17,37 @@ local origlines = {"original line 1",
"original line 6",
" indented line"}
-describe('lua: buffer event callbacks', function()
- before_each(function()
- clear()
- exec_lua([[
- local events = {}
+local function attach_buffer(evname)
+ exec_lua([[
+ local evname = ...
+ local events = {}
- function test_register(bufnr, id, changedtick, utf_sizes)
- local function callback(...)
- table.insert(events, {id, ...})
- if test_unreg == id then
- return true
- end
+ function test_register(bufnr, id, changedtick, utf_sizes)
+ local function callback(...)
+ table.insert(events, {id, ...})
+ if test_unreg == id then
+ return true
end
- local opts = {on_lines=callback, on_detach=callback, utf_sizes=utf_sizes}
- if changedtick then
- opts.on_changedtick = callback
- end
- vim.api.nvim_buf_attach(bufnr, false, opts)
end
-
- function get_events()
- local ret_events = events
- events = {}
- return ret_events
+ local opts = {[evname]=callback, on_detach=callback, utf_sizes=utf_sizes}
+ if changedtick then
+ opts.on_changedtick = callback
end
- ]])
+ vim.api.nvim_buf_attach(bufnr, false, opts)
+ end
+
+ function get_events()
+ local ret_events = events
+ events = {}
+ return ret_events
+ end
+ ]], evname)
+end
+
+describe('lua buffer event callbacks: on_lines', function()
+ before_each(function()
+ clear()
+ attach_buffer('on_lines')
end)
@@ -62,7 +68,7 @@ describe('lua: buffer event callbacks', function()
local function check_events(expected)
local events = exec_lua("return get_events(...)" )
if utf_sizes then
- -- this test case uses ASCII only, so sizes sshould be the same.
+ -- this test case uses ASCII only, so sizes should be the same.
-- Unicode is tested below.
for _, event in ipairs(expected) do
event[9] = event[8]
@@ -236,3 +242,69 @@ describe('lua: buffer event callbacks', function()
end)
end)
+
+describe('lua buffer event callbacks: on_bytes', function()
+ before_each(function()
+ clear()
+ attach_buffer('on_bytes')
+ end)
+
+
+ -- verifying the sizes with nvim_buf_get_offset is nice (checks we cannot
+ -- assert the wrong thing), but masks errors with unflushed lines (as
+ -- nvim_buf_get_offset forces a flush of the memline). To be safe run the
+ -- test both ways.
+ local function check(verify)
+ local lastsize
+ meths.buf_set_lines(0, 0, -1, true, origlines)
+ local shadow = deepcopy(origlines)
+ local shadowbytes = table.concat(shadow, '\n') .. '\n'
+ if verify then
+ lastsize = meths.buf_get_offset(0, meths.buf_line_count(0))
+ end
+ exec_lua("return test_register(...)", 0, "test1",false,utf_sizes)
+ local tick = meths.buf_get_changedtick(0)
+
+ local verify_name = "test1"
+ local function check_events(expected)
+ local events = exec_lua("return get_events(...)" )
+ eq(expected, events)
+ if verify then
+ for _, event in ipairs(events) do
+ if event[1] == verify_name and event[2] == "bytes" then
+ local _, _, buf, tick, start_row, start_col, start_byte, old_row, old_col, old_byte, new_row, new_col, new_byte = unpack(event)
+ local before = string.sub(shadowbytes, 1, start_byte)
+ -- no text in the tests will contain 0xff bytes (invalid UTF-8)
+ -- so we can use it as marker for unknown bytes
+ local unknown = string.rep('\255', new_byte)
+ local after = string.sub(shadowbytes, start_byte + old_byte + 1)
+ shadowbytes = before .. unknown .. after
+ end
+ end
+ local text = meths.buf_get_lines(0, 0, -1, true)
+ local bytes = table.concat(text, '\n') .. '\n'
+ eq(string.len(bytes), string.len(shadowbytes), shadowbytes)
+ for i = 1, string.len(shadowbytes) do
+ local shadowbyte = string.sub(shadowbytes, i, i)
+ if shadowbyte ~= '\255' then
+ eq(string.sub(bytes, i, i), shadowbyte, i)
+ end
+ end
+ end
+ end
+
+ feed('ggJ')
+ check_events({{'test1', 'bytes', 1, 3, 0, 15, 15, 1, 0, 1, 0, 1, 1}})
+
+ feed('3J')
+ check_events({
+ {'test1', 'bytes', 1, 5, 0, 31, 31, 1, 0, 1, 0, 1, 1},
+ {'test1', 'bytes', 1, 5, 0, 47, 47, 1, 0, 1, 0, 1, 1},
+ })
+ end
+
+ it('works with verify', function()
+ check(true)
+ end)
+end)
+