Skip to content

Commit fe1ade0

Browse files
committed
patch 8.1.1332: cannot flush listeners without redrawing, mix of changes
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.
1 parent fb222df commit fe1ade0

File tree

7 files changed

+250
-43
lines changed

7 files changed

+250
-43
lines changed

runtime/doc/eval.txt

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2459,6 +2459,7 @@ lispindent({lnum}) Number Lisp indent for line {lnum}
24592459
list2str({list} [, {utf8}]) String turn numbers in {list} into a String
24602460
listener_add({callback} [, {buf}])
24612461
Number add a callback to listen to changes
2462+
listener_flush([{buf}]) none invoke listener callbacks
24622463
listener_remove({id}) none remove a listener callback
24632464
localtime() Number current time
24642465
log({expr}) Float natural logarithm (base e) of {expr}
@@ -6322,8 +6323,21 @@ listener_add({callback} [, {buf}]) *listener_add()*
63226323
buffer is used.
63236324
Returns a unique ID that can be passed to |listener_remove()|.
63246325

6325-
The {callback} is invoked with a list of items that indicate a
6326-
change. The list cannot be changed. Each list item is a
6326+
The {callback} is invoked with four arguments:
6327+
a:bufnr the buffer that was changed
6328+
a:start first changed line number
6329+
a:end first line number below the change
6330+
a:added total number of lines added, negative if lines
6331+
were deleted
6332+
a:changes a List of items with details about the changes
6333+
6334+
Example: >
6335+
func Listener(bufnr, start, end, added, changes)
6336+
echo 'lines ' .. a:start .. ' until ' .. a:end .. ' changed'
6337+
endfunc
6338+
call listener_add('Listener', bufnr)
6339+
6340+
< The List cannot be changed. Each item in a:changes is a
63276341
dictionary with these entries:
63286342
lnum the first line number of the change
63296343
end the first line below the change
@@ -6337,42 +6351,47 @@ listener_add({callback} [, {buf}]) *listener_add()*
63376351
lnum line below which the new line is added
63386352
end equal to "lnum"
63396353
added number of lines inserted
6340-
col one
6354+
col 1
63416355
When lines are deleted the values are:
63426356
lnum the first deleted line
63436357
end the line below the first deleted line, before
63446358
the deletion was done
63456359
added negative, number of lines deleted
6346-
col one
6360+
col 1
63476361
When lines are changed:
63486362
lnum the first changed line
63496363
end the line below the last changed line
6350-
added zero
6351-
col first column with a change or one
6364+
added 0
6365+
col first column with a change or 1
63526366

6353-
The entries are in the order the changes was made, thus the
6354-
most recent change is at the end. One has to go through the
6355-
list from end to start to compute the line numbers in the
6356-
current state of the text.
6367+
The entries are in the order the changes were made, thus the
6368+
most recent change is at the end. The line numbers are valid
6369+
when the callback is invoked, but later changes may make them
6370+
invalid, thus keeping a copy for later might not work.
63576371

6358-
When using the same function for multiple buffers, you can
6359-
pass the buffer to that function using a |Partial|.
6360-
Example: >
6361-
func Listener(bufnr, changes)
6362-
" ...
6363-
endfunc
6364-
let bufnr = ...
6365-
call listener_add(function('Listener', [bufnr]), bufnr)
6372+
The {callback} is invoked just before the screen is updated,
6373+
when |listener_flush()| is called or when a change is being
6374+
made that changes the line count in a way it causes a line
6375+
number in the list of changes to become invalid.
63666376

6367-
< The {callback} is invoked just before the screen is updated.
6368-
To trigger this in a script use the `:redraw` command.
6377+
The {callback} is invoked with the text locked, see
6378+
|textlock|. If you do need to make changes to the buffer, use
6379+
a timer to do this later |timer_start()|.
63696380

63706381
The {callback} is not invoked when the buffer is first loaded.
63716382
Use the |BufReadPost| autocmd event to handle the initial text
63726383
of a buffer.
63736384
The {callback} is also not invoked when the buffer is
63746385
unloaded, use the |BufUnload| autocmd event for that.
63756386

6387+
listener_flush([{buf}]) *listener_flush()*
6388+
Invoke listener callbacks for buffer {buf}. If there are no
6389+
pending changes then no callbacks are invoked.
6390+
6391+
{buf} refers to a buffer name or number. For the accepted
6392+
values, see |bufname()|. When {buf} is omitted the current
6393+
buffer is used.
6394+
63766395
listener_remove({id}) *listener_remove()*
63776396
Remove a listener previously added with listener_add().
63786397

src/change.c

Lines changed: 96 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,46 @@ may_record_change(
169169

170170
if (curbuf->b_listener == NULL)
171171
return;
172+
173+
// If the new change is going to change the line numbers in already listed
174+
// changes, then flush.
175+
if (recorded_changes != NULL && xtra != 0)
176+
{
177+
listitem_T *li;
178+
linenr_T nr;
179+
180+
for (li = recorded_changes->lv_first; li != NULL; li = li->li_next)
181+
{
182+
nr = (linenr_T)dict_get_number(
183+
li->li_tv.vval.v_dict, (char_u *)"lnum");
184+
if (nr >= lnum || nr > lnume)
185+
{
186+
if (li->li_next == NULL && lnum == nr
187+
&& col + 1 == (colnr_T)dict_get_number(
188+
li->li_tv.vval.v_dict, (char_u *)"col"))
189+
{
190+
dictitem_T *di;
191+
192+
// Same start point and nothing is following, entries can
193+
// be merged.
194+
di = dict_find(li->li_tv.vval.v_dict, (char_u *)"end", -1);
195+
nr = tv_get_number(&di->di_tv);
196+
if (lnume > nr)
197+
di->di_tv.vval.v_number = lnume;
198+
di = dict_find(li->li_tv.vval.v_dict,
199+
(char_u *)"added", -1);
200+
di->di_tv.vval.v_number += xtra;
201+
return;
202+
}
203+
204+
// the current change is going to make the line number in the
205+
// older change invalid, flush now
206+
invoke_listeners(curbuf);
207+
break;
208+
}
209+
}
210+
}
211+
172212
if (recorded_changes == NULL)
173213
{
174214
recorded_changes = list_alloc();
@@ -230,6 +270,23 @@ f_listener_add(typval_T *argvars, typval_T *rettv)
230270
rettv->vval.v_number = lnr->lr_id;
231271
}
232272

273+
/*
274+
* listener_flush() function
275+
*/
276+
void
277+
f_listener_flush(typval_T *argvars, typval_T *rettv UNUSED)
278+
{
279+
buf_T *buf = curbuf;
280+
281+
if (argvars[0].v_type != VAR_UNKNOWN)
282+
{
283+
buf = get_buf_arg(&argvars[0]);
284+
if (buf == NULL)
285+
return;
286+
}
287+
invoke_listeners(buf);
288+
}
289+
233290
/*
234291
* listener_remove() function
235292
*/
@@ -264,25 +321,56 @@ f_listener_remove(typval_T *argvars, typval_T *rettv UNUSED)
264321
* listener_add().
265322
*/
266323
void
267-
invoke_listeners(void)
324+
invoke_listeners(buf_T *buf)
268325
{
269326
listener_T *lnr;
270327
typval_T rettv;
271328
int dummy;
272-
typval_T argv[2];
273-
274-
if (recorded_changes == NULL) // nothing changed
329+
typval_T argv[6];
330+
listitem_T *li;
331+
linenr_T start = MAXLNUM;
332+
linenr_T end = 0;
333+
linenr_T added = 0;
334+
335+
if (recorded_changes == NULL // nothing changed
336+
|| buf->b_listener == NULL) // no listeners
275337
return;
276-
argv[0].v_type = VAR_LIST;
277-
argv[0].vval.v_list = recorded_changes;
278338

279-
for (lnr = curbuf->b_listener; lnr != NULL; lnr = lnr->lr_next)
339+
argv[0].v_type = VAR_NUMBER;
340+
argv[0].vval.v_number = buf->b_fnum; // a:bufnr
341+
342+
343+
for (li = recorded_changes->lv_first; li != NULL; li = li->li_next)
344+
{
345+
varnumber_T lnum;
346+
347+
lnum = dict_get_number(li->li_tv.vval.v_dict, (char_u *)"lnum");
348+
if (start > lnum)
349+
start = lnum;
350+
lnum = dict_get_number(li->li_tv.vval.v_dict, (char_u *)"end");
351+
if (lnum > end)
352+
end = lnum;
353+
added = dict_get_number(li->li_tv.vval.v_dict, (char_u *)"added");
354+
}
355+
argv[1].v_type = VAR_NUMBER;
356+
argv[1].vval.v_number = start;
357+
argv[2].v_type = VAR_NUMBER;
358+
argv[2].vval.v_number = end;
359+
argv[3].v_type = VAR_NUMBER;
360+
argv[3].vval.v_number = added;
361+
362+
argv[4].v_type = VAR_LIST;
363+
argv[4].vval.v_list = recorded_changes;
364+
++textlock;
365+
366+
for (lnr = buf->b_listener; lnr != NULL; lnr = lnr->lr_next)
280367
{
281368
call_func(lnr->lr_callback, -1, &rettv,
282-
1, argv, NULL, 0L, 0L, &dummy, TRUE, lnr->lr_partial, NULL);
369+
5, argv, NULL, 0L, 0L, &dummy, TRUE, lnr->lr_partial, NULL);
283370
clear_tv(&rettv);
284371
}
285372

373+
--textlock;
286374
list_unref(recorded_changes);
287375
recorded_changes = NULL;
288376
}

src/evalfunc.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,7 @@ static struct fst
768768
{"lispindent", 1, 1, f_lispindent},
769769
{"list2str", 1, 2, f_list2str},
770770
{"listener_add", 1, 2, f_listener_add},
771+
{"listener_flush", 0, 1, f_listener_flush},
771772
{"listener_remove", 1, 1, f_listener_remove},
772773
{"localtime", 0, 0, f_localtime},
773774
#ifdef FEAT_FLOAT

src/proto/change.pro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ void change_warning(int col);
33
void changed(void);
44
void changed_internal(void);
55
void f_listener_add(typval_T *argvars, typval_T *rettv);
6+
void f_listener_flush(typval_T *argvars, typval_T *rettv);
67
void f_listener_remove(typval_T *argvars, typval_T *rettv);
7-
void invoke_listeners(void);
8+
void invoke_listeners(buf_T *buf);
89
void changed_bytes(linenr_T lnum, colnr_T col);
910
void inserted_bytes(linenr_T lnum, colnr_T col, int added);
1011
void appended_lines(linenr_T lnum, long count);

src/screen.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -565,8 +565,13 @@ update_screen(int type_arg)
565565
}
566566

567567
#ifdef FEAT_EVAL
568-
// Before updating the screen, notify any listeners of changed text.
569-
invoke_listeners();
568+
{
569+
buf_T *buf;
570+
571+
// Before updating the screen, notify any listeners of changed text.
572+
FOR_ALL_BUFFERS(buf)
573+
invoke_listeners(buf);
574+
}
570575
#endif
571576

572577
if (must_redraw)

0 commit comments

Comments
 (0)