# HG changeset patch # User Bram Moolenaar # Date 1558037706 -7200 # Node ID 978bcd70883d33b5d31561ce512770bf32e015f5 # Parent 64f31042d128e26fe6db81478fd5603fc77b52e7 patch 8.1.1335: listener callback is called after inserting text commit https://github.com/vim/vim/commit/dda4144d39a9d685b8dda830978e7410bd372c40 Author: Bram Moolenaar Date: Thu May 16 22:11:47 2019 +0200 patch 8.1.1335: listener callback is called after inserting text Problem: Listener callback is called after inserting text. Solution: Flush the changes before inserting or deleting a line. Store changes per buffer. diff --git a/src/change.c b/src/change.c --- a/src/change.c +++ b/src/change.c @@ -152,11 +152,72 @@ changed_internal(void) } #ifdef FEAT_EVAL -static list_T *recorded_changes = NULL; static long next_listener_id = 0; /* + * 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 int +check_recorded_changes( + buf_T *buf, + linenr_T lnum, + colnr_T col, + linenr_T lnume, + long xtra, + int merge) +{ + if (buf->b_recorded_changes != NULL && xtra != 0) + { + listitem_T *li; + linenr_T nr; + + 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"); + if (nr >= lnum || nr > lnume) + { + if (li->li_next == NULL && lnum == nr + && col + 1 == (colnr_T)dict_get_number( + li->li_tv.vval.v_dict, (char_u *)"col")) + { + 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 TRUE; + } + } + else + { + // the current change is going to make the line number in + // the older change invalid, flush now + invoke_listeners(curbuf); + break; + } + } + } + } + return FALSE; +} + +/* * Record a change for listeners added with listener_add(). + * Always for the current buffer. */ static void may_record_change( @@ -172,50 +233,16 @@ may_record_change( // If the new change is going to change the line numbers in already listed // changes, then flush. - if (recorded_changes != NULL && xtra != 0) - { - listitem_T *li; - linenr_T nr; - - for (li = 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"); - if (nr >= lnum || nr > lnume) - { - if (li->li_next == NULL && lnum == nr - && col + 1 == (colnr_T)dict_get_number( - li->li_tv.vval.v_dict, (char_u *)"col")) - { - dictitem_T *di; + if (check_recorded_changes(curbuf, lnum, col, lnume, xtra, TRUE)) + return; - // 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; - } - - // the current change is going to make the line number in the - // older change invalid, flush now - invoke_listeners(curbuf); - break; - } - } - } - - if (recorded_changes == NULL) + 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, typ } /* + * 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 --- 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, 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 --- 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 --- 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 --- 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 --- 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,