Patch 8.1.1332
Problem: Cannot flush change listeners without also redrawing. The line
numbers in the list of changes may become invalid.
Solution: Add listener_flush(). Invoke listeners before adding a change
that makes line numbers invalid.
Files: src/evalfunc.c, src/change.c, src/proto/change.pro,
src/screen.c, runtime/doc/eval.txt, src/testdir/test_listener.vim
*** ../vim-8.1.1331/src/evalfunc.c 2019-05-12 13:07:10.563191431 +0200
--- src/evalfunc.c 2019-05-14 18:48:29.245329088 +0200
***************
*** 768,773 ****
--- 768,774 ----
{"lispindent", 1, 1, f_lispindent},
{"list2str", 1, 2, f_list2str},
{"listener_add", 1, 2, f_listener_add},
+ {"listener_flush", 0, 1, f_listener_flush},
{"listener_remove", 1, 1, f_listener_remove},
{"localtime", 0, 0, f_localtime},
#ifdef FEAT_FLOAT
*** ../vim-8.1.1331/src/change.c 2019-05-11 21:14:02.332269584 +0200
--- src/change.c 2019-05-14 20:20:30.221554995 +0200
***************
*** 169,174 ****
--- 169,214 ----
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)
+ {
+ 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;
+
+ // 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)
{
recorded_changes = list_alloc();
***************
*** 231,236 ****
--- 271,293 ----
}
/*
+ * listener_flush() function
+ */
+ void
+ f_listener_flush(typval_T *argvars, typval_T *rettv UNUSED)
+ {
+ buf_T *buf = curbuf;
+
+ if (argvars[0].v_type != VAR_UNKNOWN)
+ {
+ buf = get_buf_arg(&argvars[0]);
+ if (buf == NULL)
+ return;
+ }
+ invoke_listeners(buf);
+ }
+
+ /*
* listener_remove() function
*/
void
***************
*** 264,288 ****
* listener_add().
*/
void
! invoke_listeners(void)
{
listener_T *lnr;
typval_T rettv;
int dummy;
! typval_T argv[2];
! if (recorded_changes == NULL) // nothing changed
return;
- argv[0].v_type = VAR_LIST;
- argv[0].vval.v_list = recorded_changes;
! for (lnr = curbuf->b_listener; lnr != NULL; lnr = lnr->lr_next)
{
call_func(lnr->lr_callback, -1, &rettv,
! 1, argv, NULL, 0L, 0L, &dummy, TRUE, lnr->lr_partial, NULL);
clear_tv(&rettv);
}
list_unref(recorded_changes);
recorded_changes = NULL;
}
--- 321,376 ----
* listener_add().
*/
void
! invoke_listeners(buf_T *buf)
{
listener_T *lnr;
typval_T rettv;
int dummy;
! typval_T argv[6];
! listitem_T *li;
! linenr_T start = MAXLNUM;
! linenr_T end = 0;
! linenr_T added = 0;
! if (recorded_changes == NULL // nothing changed
! || buf->b_listener == NULL) // no listeners
return;
! argv[0].v_type = VAR_NUMBER;
! argv[0].vval.v_number = buf->b_fnum; // a:bufnr
!
!
! for (li = recorded_changes->lv_first; li != NULL; li = li->li_next)
! {
! varnumber_T lnum;
!
! lnum = dict_get_number(li->li_tv.vval.v_dict, (char_u *)"lnum");
! if (start > lnum)
! start = lnum;
! lnum = dict_get_number(li->li_tv.vval.v_dict, (char_u *)"end");
! if (lnum > end)
! end = lnum;
! added = dict_get_number(li->li_tv.vval.v_dict, (char_u *)"added");
! }
! argv[1].v_type = VAR_NUMBER;
! argv[1].vval.v_number = start;
! argv[2].v_type = VAR_NUMBER;
! argv[2].vval.v_number = end;
! argv[3].v_type = VAR_NUMBER;
! argv[3].vval.v_number = added;
!
! argv[4].v_type = VAR_LIST;
! argv[4].vval.v_list = recorded_changes;
! ++textlock;
!
! for (lnr = buf->b_listener; lnr != NULL; lnr = lnr->lr_next)
{
call_func(lnr->lr_callback, -1, &rettv,
! 5, argv, NULL, 0L, 0L, &dummy, TRUE, lnr->lr_partial, NULL);
clear_tv(&rettv);
}
+ --textlock;
list_unref(recorded_changes);
recorded_changes = NULL;
}
*** ../vim-8.1.1331/src/proto/change.pro 2019-05-11 19:14:11.589313987
+0200
--- src/proto/change.pro 2019-05-14 19:02:12.920839840 +0200
***************
*** 3,10 ****
void changed(void);
void changed_internal(void);
void f_listener_add(typval_T *argvars, typval_T *rettv);
void f_listener_remove(typval_T *argvars, typval_T *rettv);
! void invoke_listeners(void);
void changed_bytes(linenr_T lnum, colnr_T col);
void inserted_bytes(linenr_T lnum, colnr_T col, int added);
void appended_lines(linenr_T lnum, long count);
--- 3,11 ----
void changed(void);
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 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);
void appended_lines(linenr_T lnum, long count);
*** ../vim-8.1.1331/src/screen.c 2019-05-11 21:14:02.336269566 +0200
--- src/screen.c 2019-05-14 18:52:55.103887107 +0200
***************
*** 565,572 ****
}
#ifdef FEAT_EVAL
! // Before updating the screen, notify any listeners of changed text.
! invoke_listeners();
#endif
if (must_redraw)
--- 565,577 ----
}
#ifdef FEAT_EVAL
! {
! buf_T *buf;
!
! // Before updating the screen, notify any listeners of changed text.
! FOR_ALL_BUFFERS(buf)
! invoke_listeners(buf);
! }
#endif
if (must_redraw)
*** ../vim-8.1.1331/runtime/doc/eval.txt 2019-05-12 13:53:46.906851000
+0200
--- runtime/doc/eval.txt 2019-05-14 21:17:56.054323991 +0200
***************
*** 2459,2464 ****
--- 2459,2465 ----
list2str({list} [, {utf8}]) String turn numbers in {list} into a String
listener_add({callback} [, {buf}])
Number add a callback to listen to changes
+ listener_flush([{buf}]) none invoke listener callbacks
listener_remove({id}) none remove a listener callback
localtime() Number current time
log({expr}) Float natural logarithm (base e) of {expr}
***************
*** 6322,6329 ****
buffer is used.
Returns a unique ID that can be passed to |listener_remove()|.
! The {callback} is invoked with a list of items that indicate a
! change. The list cannot be changed. Each list item is a
dictionary with these entries:
lnum the first line number of the change
end the first line below the change
--- 6323,6343 ----
buffer is used.
Returns a unique ID that can be passed to |listener_remove()|.
! The {callback} is invoked with four arguments:
! a:bufnr the buffer that was changed
! a:start first changed line number
! a:end first line number below the change
! a:added total number of lines added, negative if lines
! were deleted
! a:changes a List of items with details about the changes
!
! Example: >
! func Listener(bufnr, start, end, added, changes)
! echo 'lines ' .. a:start .. ' until ' .. a:end .. ' changed'
! endfunc
! call listener_add('Listener', bufnr)
!
! < The List cannot be changed. Each item in a:changes is a
dictionary with these entries:
lnum the first line number of the change
end the first line below the change
***************
*** 6337,6371 ****
lnum line below which the new line is added
end equal to "lnum"
added number of lines inserted
! col one
When lines are deleted the values are:
lnum the first deleted line
end the line below the first deleted line, before
the deletion was done
added negative, number of lines deleted
! col one
When lines are changed:
lnum the first changed line
end the line below the last changed line
! added zero
! col first column with a change or one
! The entries are in the order the changes was made, thus the
! most recent change is at the end. One has to go through the
! list from end to start to compute the line numbers in the
! current state of the text.
!
! When using the same function for multiple buffers, you can
! pass the buffer to that function using a |Partial|.
! Example: >
! func Listener(bufnr, changes)
! " ...
! endfunc
! let bufnr = ...
! call listener_add(function('Listener', [bufnr]), bufnr)
!
! < The {callback} is invoked just before the screen is updated.
! To trigger this in a script use the `:redraw` command.
The {callback} is not invoked when the buffer is first loaded.
Use the |BufReadPost| autocmd event to handle the initial text
--- 6351,6382 ----
lnum line below which the new line is added
end equal to "lnum"
added number of lines inserted
! col 1
When lines are deleted the values are:
lnum the first deleted line
end the line below the first deleted line, before
the deletion was done
added negative, number of lines deleted
! col 1
When lines are changed:
lnum the first changed line
end the line below the last changed line
! added 0
! col first column with a change or 1
! The entries are in the order the changes were made, thus the
! most recent change is at the end. The line numbers are valid
! when the callback is invoked, but later changes may make them
! invalid, thus keeping a copy for later might not work.
!
! The {callback} is invoked just before the screen is updated,
! when |listener_flush()| is called or when a change is being
! made that changes the line count in a way it causes a line
! number in the list of changes to become invalid.
!
! The {callback} is invoked with the text locked, see
! |textlock|. If you do need to make changes to the buffer, use
! a timer to do this later |timer_start()|.
The {callback} is not invoked when the buffer is first loaded.
Use the |BufReadPost| autocmd event to handle the initial text
***************
*** 6373,6378 ****
--- 6384,6397 ----
The {callback} is also not invoked when the buffer is
unloaded, use the |BufUnload| autocmd event for that.
+ listener_flush([{buf}])
*listener_flush()*
+ Invoke listener callbacks for buffer {buf}. If there are no
+ pending changes then no callbacks are invoked.
+
+ {buf} refers to a buffer name or number. For the accepted
+ values, see |bufname()|. When {buf} is omitted the current
+ buffer is used.
+
listener_remove({id}) *listener_remove()*
Remove a listener previously added with listener_add().
*** ../vim-8.1.1331/src/testdir/test_listener.vim 2019-05-12
14:36:22.938437845 +0200
--- src/testdir/test_listener.vim 2019-05-14 20:56:05.034204412 +0200
***************
*** 16,24 ****
func Test_listening()
new
call setline(1, ['one', 'two'])
! let id = listener_add({l -> s:StoreList(l)})
call setline(1, 'one one')
! redraw
call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list)
" Undo is also a change
--- 16,25 ----
func Test_listening()
new
call setline(1, ['one', 'two'])
! let s:list = []
! let id = listener_add({b, s, e, a, l -> s:StoreList(l)})
call setline(1, 'one one')
! call listener_flush()
call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list)
" Undo is also a change
***************
*** 26,37 ****
call append(2, 'two two')
undo
redraw
! call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1},
! \ {'lnum': 3, 'end': 4, 'col': 1, 'added': -1}, ], s:list)
1
! " Two listeners, both get called.
! let id2 = listener_add({l -> s:AnotherStoreList(l)})
let s:list = []
let s:list2 = []
exe "normal $asome\<Esc>"
--- 27,40 ----
call append(2, 'two two')
undo
redraw
! " the two changes get merged
! call assert_equal([{'lnum': 3, 'end': 4, 'col': 1, 'added': 0}], s:list)
1
! " Two listeners, both get called. Also check column.
! call setline(1, ['one one', 'two'])
! call listener_flush()
! let id2 = listener_add({b, s, e, a, l -> s:AnotherStoreList(l)})
let s:list = []
let s:list2 = []
exe "normal $asome\<Esc>"
***************
*** 39,45 ****
--- 42,51 ----
call assert_equal([{'lnum': 1, 'end': 2, 'col': 8, 'added': 0}], s:list)
call assert_equal([{'lnum': 1, 'end': 2, 'col': 8, 'added': 0}], s:list2)
+ " removing listener works
call listener_remove(id2)
+ call setline(1, ['one one', 'two'])
+ call listener_flush()
let s:list = []
let s:list2 = []
call setline(3, 'three')
***************
*** 47,58 ****
call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1}], s:list)
call assert_equal([], s:list2)
" the "o" command first adds an empty line and then changes it
let s:list = []
exe "normal Gofour\<Esc>"
redraw
! call assert_equal([{'lnum': 4, 'end': 4, 'col': 1, 'added': 1},
! \ {'lnum': 4, 'end': 5, 'col': 1, 'added': 0}], s:list)
" Remove last listener
let s:list = []
--- 53,94 ----
call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1}], s:list)
call assert_equal([], s:list2)
+ " a change above a previous change without a line number change is reported
+ " together
+ call setline(1, ['one one', 'two'])
+ call listener_flush()
+ call append(2, 'two two')
+ call setline(1, 'something')
+ call listener_flush()
+ call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1},
+ \ {'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list)
+
+ " an insert just above a previous change that was the last one gets merged
+ call setline(1, ['one one', 'two'])
+ call listener_flush()
+ call setline(2, 'something')
+ call append(1, 'two two')
+ call listener_flush()
+ call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 1}], s:list)
+
+ " an insert above a previous change causes a flush
+ call setline(1, ['one one', 'two'])
+ call listener_flush()
+ call setline(2, 'something')
+ call append(0, 'two two')
+ call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 0}], s:list)
+ call listener_flush()
+ call assert_equal([{'lnum': 1, 'end': 1, 'col': 1, 'added': 1}], s:list)
+
" the "o" command first adds an empty line and then changes it
+ %del
+ call setline(1, ['one one', 'two'])
+ call listener_flush()
let s:list = []
exe "normal Gofour\<Esc>"
redraw
! call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1},
! \ {'lnum': 3, 'end': 4, 'col': 1, 'added': 0}], s:list)
" Remove last listener
let s:list = []
***************
*** 62,68 ****
call assert_equal([], s:list)
" Trying to change the list fails
! let id = listener_add({l -> s:EvilStoreList(l)})
let s:list3 = []
call setline(1, 'asdfasdf')
redraw
--- 98,104 ----
call assert_equal([], s:list)
" Trying to change the list fails
! let id = listener_add({b, s, e, a, l -> s:EvilStoreList(l)})
let s:list3 = []
call setline(1, 'asdfasdf')
redraw
***************
*** 72,80 ****
bwipe!
endfunc
! func s:StoreBufList(buf, l)
let s:bufnr = a:buf
! let s:list = a:l
endfunc
func Test_listening_other_buf()
--- 108,171 ----
bwipe!
endfunc
! func s:StoreListArgs(buf, start, end, added, list)
! let s:buf = a:buf
! let s:start = a:start
! let s:end = a:end
! let s:added = a:added
! let s:list = a:list
! endfunc
!
! func Test_listener_args()
! new
! call setline(1, ['one', 'two'])
! let s:list = []
! let id = listener_add('s:StoreListArgs')
!
! " just one change
! call setline(1, 'one one')
! call listener_flush()
! call assert_equal(bufnr(''), s:buf)
! call assert_equal(1, s:start)
! call assert_equal(2, s:end)
! call assert_equal(0, s:added)
! call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list)
!
! " two disconnected changes
! call setline(1, ['one', 'two', 'three', 'four'])
! call listener_flush()
! call setline(1, 'one one')
! call setline(3, 'three three')
! call listener_flush()
! call assert_equal(bufnr(''), s:buf)
! call assert_equal(1, s:start)
! call assert_equal(4, s:end)
! call assert_equal(0, s:added)
! call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0},
! \ {'lnum': 3, 'end': 4, 'col': 1, 'added': 0}], s:list)
!
! " add and remove lines
! call setline(1, ['one', 'two', 'three', 'four', 'five', 'six'])
! call listener_flush()
! call append(2, 'two two')
! 4del
! call append(5, 'five five')
! call listener_flush()
! call assert_equal(bufnr(''), s:buf)
! call assert_equal(3, s:start)
! call assert_equal(6, s:end)
! call assert_equal(1, s:added)
! call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1},
! \ {'lnum': 4, 'end': 5, 'col': 1, 'added': -1},
! \ {'lnum': 6, 'end': 6, 'col': 1, 'added': 1}], s:list)
!
! call listener_remove(id)
! bwipe!
! endfunc
!
! func s:StoreBufList(buf, start, end, added, list)
let s:bufnr = a:buf
! let s:list = a:list
endfunc
func Test_listening_other_buf()
***************
*** 82,88 ****
call setline(1, ['one', 'two'])
let bufnr = bufnr('')
normal ww
! let id = listener_add(function('s:StoreBufList', [bufnr]), bufnr)
let s:list = []
call setbufline(bufnr, 1, 'hello')
redraw
--- 173,179 ----
call setline(1, ['one', 'two'])
let bufnr = bufnr('')
normal ww
! let id = listener_add(function('s:StoreBufList'), bufnr)
let s:list = []
call setbufline(bufnr, 1, 'hello')
redraw
*** ../vim-8.1.1331/src/version.c 2019-05-14 17:57:14.861402461 +0200
--- src/version.c 2019-05-14 21:18:19.926188697 +0200
***************
*** 769,770 ****
--- 769,772 ----
{ /* Add new patch number below this line */
+ /**/
+ 1332,
/**/
--
Hanson's Treatment of Time:
There are never enough hours in a day, but always too
many days before Saturday.
/// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net \\\
/// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
\\\ an exciting new programming language -- http://www.Zimbu.org ///
\\\ help me help AIDS victims -- http://ICCF-Holland.org ///
--
--
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php
---
You received this message because you are subscribed to the Google Groups
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/vim_dev/201905141920.x4EJKq9q023882%40masaka.moolenaar.net.
For more options, visit https://groups.google.com/d/optout.