summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2019-05-16 22:11:47 +0200
committerBram Moolenaar <Bram@vim.org>2019-05-16 22:11:47 +0200
commitdda4144d39a9d685b8dda830978e7410bd372c40 (patch)
tree7044aa90183afff7fbf2cb141943c3812d6c2647
parenteda652215abf696f86b872888945a2d2dd8c7192 (diff)
patch 8.1.1335: listener callback is called after inserting textv8.1.1335
Problem: Listener callback is called after inserting text. Solution: Flush the changes before inserting or deleting a line. Store changes per buffer.
-rw-r--r--src/change.c125
-rw-r--r--src/memline.c11
-rw-r--r--src/proto/change.pro1
-rw-r--r--src/structs.h1
-rw-r--r--src/testdir/test_listener.vim32
-rw-r--r--src/version.c2
6 files changed, 126 insertions, 46 deletions
diff --git a/src/change.c b/src/change.c
index 86f1ffcfb4..f1c3cc4d02 100644
--- a/src/change.c
+++ b/src/change.c
@@ -152,32 +152,30 @@ changed_internal(void)
}
#ifdef FEAT_EVAL
-static list_T *recorded_changes = NULL;
static long next_listener_id = 0;
/*
- * Record a change for listeners added with listener_add().
+ * Check if the change at "lnum" / "col" is above or overlaps with an existing
+ * changed. If above then flush changes and invoke listeners.
+ * If "merge" is TRUE do the merge.
+ * Returns TRUE if the change was merged.
*/
- static void
-may_record_change(
- linenr_T lnum,
- colnr_T col,
- linenr_T lnume,
- long xtra)
+ static int
+check_recorded_changes(
+ buf_T *buf,
+ linenr_T lnum,
+ colnr_T col,
+ linenr_T lnume,
+ long xtra,
+ int merge)
{
- dict_T *dict;
-
- if (curbuf->b_listener == NULL)
- return;
-
- // If the new change is going to change the line numbers in already listed
- // changes, then flush.
- if (recorded_changes != NULL && xtra != 0)
+ if (buf->b_recorded_changes != NULL && xtra != 0)
{
listitem_T *li;
linenr_T nr;
- for (li = recorded_changes->lv_first; li != NULL; li = li->li_next)
+ for (li = buf->b_recorded_changes->lv_first; li != NULL;
+ li = li->li_next)
{
nr = (linenr_T)dict_get_number(
li->li_tv.vval.v_dict, (char_u *)"lnum");
@@ -187,35 +185,64 @@ may_record_change(
&& col + 1 == (colnr_T)dict_get_number(
li->li_tv.vval.v_dict, (char_u *)"col"))
{
- dictitem_T *di;
-
- // Same start point and nothing is following, entries can
- // be merged.
- di = dict_find(li->li_tv.vval.v_dict, (char_u *)"end", -1);
- nr = tv_get_number(&di->di_tv);
- if (lnume > nr)
- di->di_tv.vval.v_number = lnume;
- di = dict_find(li->li_tv.vval.v_dict,
+ if (merge)
+ {
+ dictitem_T *di;
+
+ // Same start point and nothing is following, entries
+ // can be merged.
+ di = dict_find(li->li_tv.vval.v_dict,
+ (char_u *)"end", -1);
+ nr = tv_get_number(&di->di_tv);
+ if (lnume > nr)
+ di->di_tv.vval.v_number = lnume;
+ di = dict_find(li->li_tv.vval.v_dict,
(char_u *)"added", -1);
- di->di_tv.vval.v_number += xtra;
- return;
+ di->di_tv.vval.v_number += xtra;
+ return TRUE;
+ }
+ }
+ else
+ {
+ // the current change is going to make the line number in
+ // the older change invalid, flush now
+ invoke_listeners(curbuf);
+ break;
}
-
- // the current change is going to make the line number in the
- // older change invalid, flush now
- invoke_listeners(curbuf);
- break;
}
}
}
+ return FALSE;
+}
- if (recorded_changes == NULL)
+/*
+ * Record a change for listeners added with listener_add().
+ * Always for the current buffer.
+ */
+ static void
+may_record_change(
+ linenr_T lnum,
+ colnr_T col,
+ linenr_T lnume,
+ long xtra)
+{
+ dict_T *dict;
+
+ if (curbuf->b_listener == NULL)
+ return;
+
+ // If the new change is going to change the line numbers in already listed
+ // changes, then flush.
+ if (check_recorded_changes(curbuf, lnum, col, lnume, xtra, TRUE))
+ return;
+
+ if (curbuf->b_recorded_changes == NULL)
{
- recorded_changes = list_alloc();
- if (recorded_changes == NULL) // out of memory
+ curbuf->b_recorded_changes = list_alloc();
+ if (curbuf->b_recorded_changes == NULL) // out of memory
return;
- ++recorded_changes->lv_refcount;
- recorded_changes->lv_lock = VAR_FIXED;
+ ++curbuf->b_recorded_changes->lv_refcount;
+ curbuf->b_recorded_changes->lv_lock = VAR_FIXED;
}
dict = dict_alloc();
@@ -226,7 +253,7 @@ may_record_change(
dict_add_number(dict, "added", (varnumber_T)xtra);
dict_add_number(dict, "col", (varnumber_T)col + 1);
- list_append_dict(recorded_changes, dict);
+ list_append_dict(curbuf->b_recorded_changes, dict);
}
/*
@@ -317,6 +344,16 @@ f_listener_remove(typval_T *argvars, typval_T *rettv UNUSED)
}
/*
+ * Called before inserting a line above "lnum"/"lnum3" or deleting line "lnum"
+ * to "lnume".
+ */
+ void
+may_invoke_listeners(buf_T *buf, linenr_T lnum, linenr_T lnume, int added)
+{
+ check_recorded_changes(buf, lnum, 0, lnume, added, FALSE);
+}
+
+/*
* Called when a sequence of changes is done: invoke listeners added with
* listener_add().
*/
@@ -332,7 +369,7 @@ invoke_listeners(buf_T *buf)
linenr_T end = 0;
linenr_T added = 0;
- if (recorded_changes == NULL // nothing changed
+ if (buf->b_recorded_changes == NULL // nothing changed
|| buf->b_listener == NULL) // no listeners
return;
@@ -340,7 +377,7 @@ invoke_listeners(buf_T *buf)
argv[0].vval.v_number = buf->b_fnum; // a:bufnr
- for (li = recorded_changes->lv_first; li != NULL; li = li->li_next)
+ for (li = buf->b_recorded_changes->lv_first; li != NULL; li = li->li_next)
{
varnumber_T lnum;
@@ -360,7 +397,7 @@ invoke_listeners(buf_T *buf)
argv[3].vval.v_number = added;
argv[4].v_type = VAR_LIST;
- argv[4].vval.v_list = recorded_changes;
+ argv[4].vval.v_list = buf->b_recorded_changes;
++textlock;
for (lnr = buf->b_listener; lnr != NULL; lnr = lnr->lr_next)
@@ -371,8 +408,8 @@ invoke_listeners(buf_T *buf)
}
--textlock;
- list_unref(recorded_changes);
- recorded_changes = NULL;
+ list_unref(buf->b_recorded_changes);
+ buf->b_recorded_changes = NULL;
}
#endif
diff --git a/src/memline.c b/src/memline.c
index 812d10dc1a..006a8b52df 100644
--- a/src/memline.c
+++ b/src/memline.c
@@ -2790,6 +2790,12 @@ ml_append_int(
if (len == 0)
len = (colnr_T)STRLEN(line) + 1; // space needed for the text
+#ifdef FEAT_EVAL
+ // When inserting above recorded changes: flush the changes before changing
+ // the text.
+ may_invoke_listeners(buf, lnum + 1, lnum + 1, 1);
+#endif
+
#ifdef FEAT_TEXT_PROP
if (curbuf->b_has_textprop && lnum > 0)
// Add text properties that continue from the previous line.
@@ -3526,6 +3532,11 @@ ml_delete_int(buf_T *buf, linenr_T lnum, int message)
if (lnum < 1 || lnum > buf->b_ml.ml_line_count)
return FAIL;
+#ifdef FEAT_EVAL
+ // When inserting above recorded changes: flush the changes before changing
+ // the text.
+ may_invoke_listeners(buf, lnum, lnum + 1, -1);
+#endif
if (lowest_marked && lowest_marked > lnum)
lowest_marked--;
diff --git a/src/proto/change.pro b/src/proto/change.pro
index f0f390b05b..79306e3061 100644
--- a/src/proto/change.pro
+++ b/src/proto/change.pro
@@ -5,6 +5,7 @@ void changed_internal(void);
void f_listener_add(typval_T *argvars, typval_T *rettv);
void f_listener_flush(typval_T *argvars, typval_T *rettv);
void f_listener_remove(typval_T *argvars, typval_T *rettv);
+void may_invoke_listeners(buf_T *buf, linenr_T lnum, linenr_T lnume, int added);
void invoke_listeners(buf_T *buf);
void changed_bytes(linenr_T lnum, colnr_T col);
void inserted_bytes(linenr_T lnum, colnr_T col, int added);
diff --git a/src/structs.h b/src/structs.h
index 36bdf9a53e..afd3e02813 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -2439,6 +2439,7 @@ struct file_buffer
dict_T *b_vars; /* internal variables, local to buffer */
listener_T *b_listener;
+ list_T *b_recorded_changes;
#endif
#ifdef FEAT_TEXT_PROP
int b_has_textprop; // TRUE when text props were added
diff --git a/src/testdir/test_listener.vim b/src/testdir/test_listener.vim
index d5d633274e..66c3d51439 100644
--- a/src/testdir/test_listener.vim
+++ b/src/testdir/test_listener.vim
@@ -1,6 +1,8 @@
" tests for listener_add() and listener_remove()
-func s:StoreList(l)
+func s:StoreList(s, l)
+ let s:start = a:s
+ let s:text = getline(a:s)
let s:list = a:l
endfunc
@@ -17,7 +19,7 @@ func Test_listening()
new
call setline(1, ['one', 'two'])
let s:list = []
- let id = listener_add({b, s, e, a, l -> s:StoreList(l)})
+ let id = listener_add({b, s, e, a, l -> s:StoreList(s, l)})
call setline(1, 'one one')
call listener_flush()
call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list)
@@ -66,8 +68,10 @@ func Test_listening()
" an insert just above a previous change that was the last one gets merged
call setline(1, ['one one', 'two'])
call listener_flush()
+ let s:list = []
call setline(2, 'something')
call append(1, 'two two')
+ call assert_equal([], s:list)
call listener_flush()
call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 1}], s:list)
@@ -77,8 +81,32 @@ func Test_listening()
call setline(2, 'something')
call append(0, 'two two')
call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 0}], s:list)
+ call assert_equal('something', s:text)
call listener_flush()
call assert_equal([{'lnum': 1, 'end': 1, 'col': 1, 'added': 1}], s:list)
+ call assert_equal('two two', s:text)
+
+ " a delete at a previous change that was the last one gets merged
+ call setline(1, ['one one', 'two'])
+ call listener_flush()
+ let s:list = []
+ call setline(2, 'something')
+ 2del
+ call assert_equal([], s:list)
+ call listener_flush()
+ call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': -1}], s:list)
+
+ " a delete above a previous change causes a flush
+ call setline(1, ['one one', 'two'])
+ call listener_flush()
+ call setline(2, 'another')
+ 1del
+ call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 0}], s:list)
+ call assert_equal(2, s:start)
+ call assert_equal('another', s:text)
+ call listener_flush()
+ call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': -1}], s:list)
+ call assert_equal('another', s:text)
" the "o" command first adds an empty line and then changes it
%del
diff --git a/src/version.c b/src/version.c
index ad16cd5706..b085052baa 100644
--- a/src/version.c
+++ b/src/version.c
@@ -768,6 +768,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1335,
+/**/
1334,
/**/
1333,