mirror of
https://github.com/vim/vim.git
synced 2025-10-18 07:54:29 -04:00
patch 9.1.1782: buffer-listener callbacks may not match buffer content
Problem: buffer-listener callbacks may not match buffer content, since they are buffered until the screen is updated. Solution: Allow to handle buffer-callbacks un-buffered, meaning to handle those changes as soon as they happen (Paul Ollis). fixes: #18183 closes: #18295 Signed-off-by: Paul Ollis <paul@cleversheep.org> Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
committed by
Christian Brabandt
parent
3a6cf6d53b
commit
e87d17ecfb
@@ -1,4 +1,4 @@
|
|||||||
*builtin.txt* For Vim version 9.1. Last change: 2025 Sep 18
|
*builtin.txt* For Vim version 9.1. Last change: 2025 Sep 21
|
||||||
|
|
||||||
|
|
||||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||||
@@ -369,7 +369,7 @@ lispindent({lnum}) Number Lisp indent for line {lnum}
|
|||||||
list2blob({list}) Blob turn {list} of numbers into a Blob
|
list2blob({list}) Blob turn {list} of numbers into a Blob
|
||||||
list2str({list} [, {utf8}]) String turn {list} of numbers into a String
|
list2str({list} [, {utf8}]) String turn {list} of numbers into a String
|
||||||
list2tuple({list}) Tuple turn {list} of items into a tuple
|
list2tuple({list}) Tuple turn {list} of items into a tuple
|
||||||
listener_add({callback} [, {buf}])
|
listener_add({callback} [, {buf} [, {unbuffered}]])
|
||||||
Number add a callback to listen to changes
|
Number add a callback to listen to changes
|
||||||
listener_flush([{buf}]) none invoke listener callbacks
|
listener_flush([{buf}]) none invoke listener callbacks
|
||||||
listener_remove({id}) none remove a listener callback
|
listener_remove({id}) none remove a listener callback
|
||||||
@@ -6715,7 +6715,7 @@ list2tuple({list}) *list2tuple()*
|
|||||||
Return type: tuple<{type}> (depending on the given |List|)
|
Return type: tuple<{type}> (depending on the given |List|)
|
||||||
|
|
||||||
|
|
||||||
listener_add({callback} [, {buf}]) *listener_add()*
|
listener_add({callback} [, {buf} [, {unbuffered}]]) *listener_add()*
|
||||||
Add a callback function that will be invoked when changes have
|
Add a callback function that will be invoked when changes have
|
||||||
been made to buffer {buf}.
|
been made to buffer {buf}.
|
||||||
{buf} refers to a buffer name or number. For the accepted
|
{buf} refers to a buffer name or number. For the accepted
|
||||||
@@ -6723,6 +6723,11 @@ listener_add({callback} [, {buf}]) *listener_add()*
|
|||||||
buffer is used.
|
buffer is used.
|
||||||
Returns a unique ID that can be passed to |listener_remove()|.
|
Returns a unique ID that can be passed to |listener_remove()|.
|
||||||
|
|
||||||
|
If the {buf} already has registered callbacks then the
|
||||||
|
equivalent of >
|
||||||
|
listener_flush({buf})
|
||||||
|
< is performed before the new callback is added.
|
||||||
|
|
||||||
The {callback} is invoked with five arguments:
|
The {callback} is invoked with five arguments:
|
||||||
bufnr the buffer that was changed
|
bufnr the buffer that was changed
|
||||||
start first changed line number
|
start first changed line number
|
||||||
@@ -6765,20 +6770,37 @@ listener_add({callback} [, {buf}]) *listener_add()*
|
|||||||
added 0
|
added 0
|
||||||
col first column with a change or 1
|
col first column with a change or 1
|
||||||
|
|
||||||
The entries are in the order the changes were made, thus the
|
When {unbuffered} is |FALSE| or not provided the {callback} is
|
||||||
most recent change is at the end. The line numbers are valid
|
invoked:
|
||||||
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,
|
1. Just before the screen is updated.
|
||||||
when |listener_flush()| is called or when a change is being
|
2. When |listener_flush()| is called.
|
||||||
made that changes the line count in a way it causes a line
|
3. When a change is being made that changes the line count in
|
||||||
number in the list of changes to become invalid.
|
a way that causes a line number in the list of changes to
|
||||||
|
become invalid.
|
||||||
|
|
||||||
|
The entries are in the order the changes were made, thus the
|
||||||
|
most recent change is at the end.
|
||||||
|
|
||||||
|
Because of the third trigger reason for triggering a callback
|
||||||
|
listed above, the line numbers passed to the callback are not
|
||||||
|
guaranteed to be valid. If this is a problem then make
|
||||||
|
{unbuffered} |TRUE|.
|
||||||
|
|
||||||
|
When {unbuffered} is |TRUE| the {callback} is invoked for every
|
||||||
|
single change. The changes list only holds a single dictionary
|
||||||
|
and the "start", "end" and "added" values in the dictionary are
|
||||||
|
the same as the corresponding callback arguments. 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 with the text locked, see
|
The {callback} is invoked with the text locked, see
|
||||||
|textlock|. If you do need to make changes to the buffer, use
|
|textlock|. If you do need to make changes to the buffer, use
|
||||||
a timer to do this later |timer_start()|.
|
a timer to do this later |timer_start()|.
|
||||||
|
|
||||||
|
You may not call listener_add() during the {callback}. *E1569*
|
||||||
|
|
||||||
The {callback} is not invoked when the buffer is first loaded.
|
The {callback} is not invoked when the buffer is first loaded.
|
||||||
Use the |BufReadPost| autocmd event to handle the initial text
|
Use the |BufReadPost| autocmd event to handle the initial text
|
||||||
of a buffer.
|
of a buffer.
|
||||||
|
@@ -4741,6 +4741,7 @@ E1565 remote.txt /*E1565*
|
|||||||
E1566 remote.txt /*E1566*
|
E1566 remote.txt /*E1566*
|
||||||
E1567 remote.txt /*E1567*
|
E1567 remote.txt /*E1567*
|
||||||
E1568 options.txt /*E1568*
|
E1568 options.txt /*E1568*
|
||||||
|
E1569 builtin.txt /*E1569*
|
||||||
E157 sign.txt /*E157*
|
E157 sign.txt /*E157*
|
||||||
E158 sign.txt /*E158*
|
E158 sign.txt /*E158*
|
||||||
E159 sign.txt /*E159*
|
E159 sign.txt /*E159*
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
*version9.txt* For Vim version 9.1. Last change: 2025 Sep 18
|
*version9.txt* For Vim version 9.1. Last change: 2025 Sep 21
|
||||||
|
|
||||||
|
|
||||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||||
@@ -41736,6 +41736,8 @@ Functions: ~
|
|||||||
- |matchfuzzy()| and |matchfuzzypos()| use an improved fuzzy matching
|
- |matchfuzzy()| and |matchfuzzypos()| use an improved fuzzy matching
|
||||||
algorithm (same as fzy).
|
algorithm (same as fzy).
|
||||||
- |sha256()| also accepts a |Blob| as argument.
|
- |sha256()| also accepts a |Blob| as argument.
|
||||||
|
- |listener_add()| allows to register un-buffered listeners, so that chagnes
|
||||||
|
are handled as soon as they happen.
|
||||||
|
|
||||||
Others: ~
|
Others: ~
|
||||||
- the regex engines match correctly case-insensitive multi-byte characters
|
- the regex engines match correctly case-insensitive multi-byte characters
|
||||||
|
334
src/change.c
334
src/change.c
@@ -149,8 +149,89 @@ changed_internal(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef FEAT_EVAL
|
#ifdef FEAT_EVAL
|
||||||
|
// Set when listener callbacks are being invoked.
|
||||||
|
static int recursive = FALSE;
|
||||||
|
|
||||||
static long next_listener_id = 0;
|
static long next_listener_id = 0;
|
||||||
|
|
||||||
|
// A flag that is set when any buffer listener housekeeping is required.
|
||||||
|
// Currently the only condition is when a listener is marked for removal.
|
||||||
|
static bool houskeeping_required;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove a given listener_T entry from its containing list.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
remove_listener_from_list(
|
||||||
|
listener_T **list,
|
||||||
|
listener_T *lnr,
|
||||||
|
listener_T *prev)
|
||||||
|
{
|
||||||
|
if (prev != NULL)
|
||||||
|
prev->lr_next = lnr->lr_next;
|
||||||
|
else
|
||||||
|
*list = lnr->lr_next;
|
||||||
|
free_callback(&lnr->lr_callback);
|
||||||
|
vim_free(lnr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Clean up a buffer change listener list.
|
||||||
|
*
|
||||||
|
* If "all" is TRUE then all entries are removed. Otherwise only those with an ID
|
||||||
|
* of zero are removed. If "buf" is non-NULL then the buffer's recorded changes
|
||||||
|
* will be discarded in the event that all listeners were removed.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
clean_listener_list(buf_T *buf, listener_T **list, bool all)
|
||||||
|
{
|
||||||
|
listener_T *prev;
|
||||||
|
listener_T *lnr;
|
||||||
|
listener_T *next;
|
||||||
|
|
||||||
|
prev = NULL;
|
||||||
|
for (lnr = *list; lnr != NULL; lnr = next)
|
||||||
|
{
|
||||||
|
next = lnr->lr_next;
|
||||||
|
if (all || lnr->lr_id == 0)
|
||||||
|
remove_listener_from_list(list, lnr, prev);
|
||||||
|
else
|
||||||
|
prev = lnr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop any recorded changes for a buffer with no listeners.
|
||||||
|
if (buf != NULL)
|
||||||
|
{
|
||||||
|
if (*list == NULL && buf->b_recorded_changes != NULL)
|
||||||
|
{
|
||||||
|
list_unref(buf->b_recorded_changes);
|
||||||
|
buf->b_recorded_changes = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Perform houskeeping tasks for buffer change listeners.
|
||||||
|
*
|
||||||
|
* This does nothing unless the "houskeeping_required" flag has been set.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
perform_listener_housekeeping(void)
|
||||||
|
{
|
||||||
|
buf_T *buf;
|
||||||
|
|
||||||
|
if (houskeeping_required)
|
||||||
|
{
|
||||||
|
FOR_ALL_BUFFERS(buf)
|
||||||
|
{
|
||||||
|
clean_listener_list(buf, &buf->b_listener, FALSE);
|
||||||
|
clean_listener_list(NULL, &buf->b_sync_listener, FALSE);
|
||||||
|
}
|
||||||
|
houskeeping_required = FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check if the change at "lnum" is above or overlaps with an existing
|
* Check if the change at "lnum" is above or overlaps with an existing
|
||||||
* change. If above then flush changes and invoke listeners.
|
* change. If above then flush changes and invoke listeners.
|
||||||
@@ -162,6 +243,7 @@ check_recorded_changes(
|
|||||||
linenr_T lnume,
|
linenr_T lnume,
|
||||||
long xtra)
|
long xtra)
|
||||||
{
|
{
|
||||||
|
perform_listener_housekeeping();
|
||||||
if (buf->b_recorded_changes == NULL || xtra == 0)
|
if (buf->b_recorded_changes == NULL || xtra == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -188,6 +270,9 @@ check_recorded_changes(
|
|||||||
/*
|
/*
|
||||||
* Record a change for listeners added with listener_add().
|
* Record a change for listeners added with listener_add().
|
||||||
* Always for the current buffer.
|
* Always for the current buffer.
|
||||||
|
*
|
||||||
|
* This only deals with listeners that are prepared to accept multiple buffered
|
||||||
|
* changes.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
may_record_change(
|
may_record_change(
|
||||||
@@ -198,6 +283,7 @@ may_record_change(
|
|||||||
{
|
{
|
||||||
dict_T *dict;
|
dict_T *dict;
|
||||||
|
|
||||||
|
perform_listener_housekeeping();
|
||||||
if (curbuf->b_listener == NULL)
|
if (curbuf->b_listener == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -234,8 +320,17 @@ f_listener_add(typval_T *argvars, typval_T *rettv)
|
|||||||
callback_T callback;
|
callback_T callback;
|
||||||
listener_T *lnr;
|
listener_T *lnr;
|
||||||
buf_T *buf = curbuf;
|
buf_T *buf = curbuf;
|
||||||
|
int unbuffered = 0;
|
||||||
|
|
||||||
if (in_vim9script() && check_for_opt_buffer_arg(argvars, 1) == FAIL)
|
if (recursive)
|
||||||
|
{
|
||||||
|
emsg(_(e_cannot_add_listener_in_listener_callback));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_vim9script() && (
|
||||||
|
check_for_opt_buffer_arg(argvars, 1) == FAIL
|
||||||
|
|| check_for_opt_bool_arg(argvars, 2) == FAIL))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
callback = get_callback(&argvars[0]);
|
callback = get_callback(&argvars[0]);
|
||||||
@@ -250,6 +345,8 @@ f_listener_add(typval_T *argvars, typval_T *rettv)
|
|||||||
free_callback(&callback);
|
free_callback(&callback);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (argvars[2].v_type != VAR_UNKNOWN)
|
||||||
|
unbuffered = (int)tv_get_bool(&argvars[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
lnr = ALLOC_CLEAR_ONE(listener_T);
|
lnr = ALLOC_CLEAR_ONE(listener_T);
|
||||||
@@ -258,8 +355,23 @@ f_listener_add(typval_T *argvars, typval_T *rettv)
|
|||||||
free_callback(&callback);
|
free_callback(&callback);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Perform any pending housekeeping and then make sure any buffered change
|
||||||
|
// reports are flushed so that the new listener does not see out of date
|
||||||
|
// changes.
|
||||||
|
perform_listener_housekeeping();
|
||||||
|
invoke_listeners(buf);
|
||||||
|
|
||||||
|
if (unbuffered)
|
||||||
|
{
|
||||||
|
lnr->lr_next = buf->b_sync_listener;
|
||||||
|
buf->b_sync_listener = lnr;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
lnr->lr_next = buf->b_listener;
|
lnr->lr_next = buf->b_listener;
|
||||||
buf->b_listener = lnr;
|
buf->b_listener = lnr;
|
||||||
|
}
|
||||||
|
|
||||||
set_callback(&lnr->lr_callback, &callback);
|
set_callback(&lnr->lr_callback, &callback);
|
||||||
if (callback.cb_free_name)
|
if (callback.cb_free_name)
|
||||||
@@ -277,6 +389,9 @@ f_listener_flush(typval_T *argvars, typval_T *rettv UNUSED)
|
|||||||
{
|
{
|
||||||
buf_T *buf = curbuf;
|
buf_T *buf = curbuf;
|
||||||
|
|
||||||
|
if (recursive)
|
||||||
|
return;
|
||||||
|
|
||||||
if (in_vim9script() && check_for_opt_buffer_arg(argvars, 0) == FAIL)
|
if (in_vim9script() && check_for_opt_buffer_arg(argvars, 0) == FAIL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -286,29 +401,43 @@ f_listener_flush(typval_T *argvars, typval_T *rettv UNUSED)
|
|||||||
if (buf == NULL)
|
if (buf == NULL)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
perform_listener_housekeeping();
|
||||||
invoke_listeners(buf);
|
invoke_listeners(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
static void
|
* Find the buffer change listener entry for a given unique ID.
|
||||||
remove_listener(buf_T *buf, listener_T *lnr, listener_T *prev)
|
*/
|
||||||
|
static listener_T *
|
||||||
|
find_listener(
|
||||||
|
int id,
|
||||||
|
listener_T *list_start,
|
||||||
|
listener_T **prev)
|
||||||
{
|
{
|
||||||
if (prev != NULL)
|
listener_T *next;
|
||||||
prev->lr_next = lnr->lr_next;
|
listener_T *lnr;
|
||||||
else
|
|
||||||
buf->b_listener = lnr->lr_next;
|
*prev = NULL;
|
||||||
free_callback(&lnr->lr_callback);
|
for (lnr = list_start; lnr != NULL; lnr = next)
|
||||||
vim_free(lnr);
|
{
|
||||||
|
next = lnr->lr_next;
|
||||||
|
if (lnr->lr_id == id)
|
||||||
|
return lnr;
|
||||||
|
*prev = lnr;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* listener_remove() function
|
* listener_remove() function
|
||||||
|
*
|
||||||
|
* This simply marks the listener_T entry as unused, by setting its ID to zero.
|
||||||
|
* The listener_T entry gets removed later by housekeeping.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
f_listener_remove(typval_T *argvars, typval_T *rettv)
|
f_listener_remove(typval_T *argvars, typval_T *rettv)
|
||||||
{
|
{
|
||||||
listener_T *lnr;
|
listener_T *lnr;
|
||||||
listener_T *next;
|
|
||||||
listener_T *prev;
|
listener_T *prev;
|
||||||
int id;
|
int id;
|
||||||
buf_T *buf;
|
buf_T *buf;
|
||||||
@@ -319,29 +448,23 @@ f_listener_remove(typval_T *argvars, typval_T *rettv)
|
|||||||
id = tv_get_number(argvars);
|
id = tv_get_number(argvars);
|
||||||
FOR_ALL_BUFFERS(buf)
|
FOR_ALL_BUFFERS(buf)
|
||||||
{
|
{
|
||||||
prev = NULL;
|
lnr = find_listener(id, buf->b_listener, &prev);
|
||||||
for (lnr = buf->b_listener; lnr != NULL; lnr = next)
|
if (lnr == NULL)
|
||||||
|
lnr = find_listener(id, buf->b_sync_listener, &prev);
|
||||||
|
if (lnr != NULL)
|
||||||
{
|
{
|
||||||
next = lnr->lr_next;
|
// Clear the ID to indicate that the listener is unused flag
|
||||||
if (lnr->lr_id == id)
|
// houskeeping.
|
||||||
{
|
|
||||||
if (textlock > 0)
|
|
||||||
{
|
|
||||||
// in invoke_listeners(), clear ID and delete later
|
|
||||||
lnr->lr_id = 0;
|
lnr->lr_id = 0;
|
||||||
return;
|
houskeeping_required = TRUE;
|
||||||
}
|
|
||||||
remove_listener(buf, lnr, prev);
|
|
||||||
rettv->vval.v_number = 1;
|
rettv->vval.v_number = 1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
prev = lnr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called before inserting a line above "lnum"/"lnum3" or deleting line "lnum"
|
* Called before inserting a line above "lnum"/"lnume" or deleting line "lnum"
|
||||||
* to "lnume".
|
* to "lnume".
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
@@ -350,6 +473,98 @@ may_invoke_listeners(buf_T *buf, linenr_T lnum, linenr_T lnume, int added)
|
|||||||
check_recorded_changes(buf, lnum, lnume, added);
|
check_recorded_changes(buf, lnum, lnume, added);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Common processing for invoke_listeners and invoke_sync_listeners.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
invoke_listener_set(
|
||||||
|
buf_T *buf,
|
||||||
|
linenr_T start,
|
||||||
|
linenr_T end,
|
||||||
|
long added,
|
||||||
|
list_T *recorded_changes,
|
||||||
|
listener_T *listeners)
|
||||||
|
{
|
||||||
|
int save_updating_screen = updating_screen;
|
||||||
|
listener_T *lnr;
|
||||||
|
typval_T rettv;
|
||||||
|
typval_T argv[6];
|
||||||
|
|
||||||
|
argv[0].v_type = VAR_NUMBER;
|
||||||
|
argv[0].vval.v_number = buf->b_fnum; // a:bufnr
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Protect against recursive callbacks, lock the buffer against changes and
|
||||||
|
// set the updating_screen flag to prevent channel input processing, which
|
||||||
|
// might also try to update the buffer.
|
||||||
|
recursive = TRUE;
|
||||||
|
++textlock;
|
||||||
|
updating_screen = TRUE;
|
||||||
|
|
||||||
|
for (lnr = listeners; lnr != NULL; lnr = lnr->lr_next)
|
||||||
|
{
|
||||||
|
call_callback(&lnr->lr_callback, -1, &rettv, 5, argv);
|
||||||
|
clear_tv(&rettv);
|
||||||
|
}
|
||||||
|
|
||||||
|
--textlock;
|
||||||
|
if (save_updating_screen)
|
||||||
|
updating_screen = TRUE;
|
||||||
|
else
|
||||||
|
after_updating_screen(TRUE);
|
||||||
|
recursive = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called when any change occurs: invoke listeners added with the "unbuffered"
|
||||||
|
* parameter set.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
invoke_sync_listeners(
|
||||||
|
buf_T *buf,
|
||||||
|
linenr_T start,
|
||||||
|
colnr_T col,
|
||||||
|
linenr_T end,
|
||||||
|
long added)
|
||||||
|
{
|
||||||
|
list_T *recorded_changes;
|
||||||
|
dict_T *dict;
|
||||||
|
|
||||||
|
if (recursive || curbuf->b_sync_listener == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Create a single entry list to store the details of the change (including
|
||||||
|
// the column).
|
||||||
|
recorded_changes = list_alloc();
|
||||||
|
if (recorded_changes == NULL) // out of memory
|
||||||
|
return;
|
||||||
|
|
||||||
|
++recorded_changes->lv_refcount;
|
||||||
|
recorded_changes->lv_lock = VAR_FIXED;
|
||||||
|
|
||||||
|
dict = dict_alloc();
|
||||||
|
if (dict == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
dict_add_number(dict, "lnum", (varnumber_T)start);
|
||||||
|
dict_add_number(dict, "end", (varnumber_T)end);
|
||||||
|
dict_add_number(dict, "added", (varnumber_T)added);
|
||||||
|
dict_add_number(dict, "col", (varnumber_T)col + 1);
|
||||||
|
list_append_dict(recorded_changes, dict);
|
||||||
|
|
||||||
|
invoke_listener_set(
|
||||||
|
buf, start, end, added, recorded_changes, buf->b_sync_listener);
|
||||||
|
|
||||||
|
list_unref(recorded_changes);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called when a sequence of changes is done: invoke listeners added with
|
* Called when a sequence of changes is done: invoke listeners added with
|
||||||
* listener_add().
|
* listener_add().
|
||||||
@@ -357,30 +572,15 @@ may_invoke_listeners(buf_T *buf, linenr_T lnum, linenr_T lnume, int added)
|
|||||||
void
|
void
|
||||||
invoke_listeners(buf_T *buf)
|
invoke_listeners(buf_T *buf)
|
||||||
{
|
{
|
||||||
listener_T *lnr;
|
|
||||||
typval_T rettv;
|
|
||||||
typval_T argv[6];
|
|
||||||
listitem_T *li;
|
listitem_T *li;
|
||||||
linenr_T start = MAXLNUM;
|
linenr_T start = MAXLNUM;
|
||||||
linenr_T end = 0;
|
linenr_T end = 0;
|
||||||
linenr_T added = 0;
|
linenr_T added = 0;
|
||||||
int save_updating_screen = updating_screen;
|
|
||||||
static int recursive = FALSE;
|
|
||||||
listener_T *next;
|
|
||||||
listener_T *prev;
|
|
||||||
|
|
||||||
if (buf->b_recorded_changes == NULL // nothing changed
|
if (buf->b_recorded_changes == NULL // nothing changed
|
||||||
|| buf->b_listener == NULL // no listeners
|
|| buf->b_listener == NULL // no listeners
|
||||||
|| recursive) // already busy
|
|| recursive) // already busy
|
||||||
return;
|
return;
|
||||||
recursive = TRUE;
|
|
||||||
|
|
||||||
// Block messages on channels from being handled, so that they don't make
|
|
||||||
// text changes here.
|
|
||||||
++updating_screen;
|
|
||||||
|
|
||||||
argv[0].v_type = VAR_NUMBER;
|
|
||||||
argv[0].vval.v_number = buf->b_fnum; // a:bufnr
|
|
||||||
|
|
||||||
FOR_ALL_LIST_ITEMS(buf->b_recorded_changes, li)
|
FOR_ALL_LIST_ITEMS(buf->b_recorded_changes, li)
|
||||||
{
|
{
|
||||||
@@ -394,43 +594,12 @@ invoke_listeners(buf_T *buf)
|
|||||||
end = lnum;
|
end = lnum;
|
||||||
added += dict_get_number(li->li_tv.vval.v_dict, "added");
|
added += dict_get_number(li->li_tv.vval.v_dict, "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;
|
invoke_listener_set(
|
||||||
argv[4].vval.v_list = buf->b_recorded_changes;
|
buf, start, end, added, buf->b_recorded_changes, buf->b_listener);
|
||||||
++textlock;
|
|
||||||
|
|
||||||
for (lnr = buf->b_listener; lnr != NULL; lnr = lnr->lr_next)
|
|
||||||
{
|
|
||||||
call_callback(&lnr->lr_callback, -1, &rettv, 5, argv);
|
|
||||||
clear_tv(&rettv);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If f_listener_remove() was called may have to remove a listener now.
|
|
||||||
prev = NULL;
|
|
||||||
for (lnr = buf->b_listener; lnr != NULL; lnr = next)
|
|
||||||
{
|
|
||||||
next = lnr->lr_next;
|
|
||||||
if (lnr->lr_id == 0)
|
|
||||||
remove_listener(buf, lnr, prev);
|
|
||||||
else
|
|
||||||
prev = lnr;
|
|
||||||
}
|
|
||||||
|
|
||||||
--textlock;
|
|
||||||
list_unref(buf->b_recorded_changes);
|
list_unref(buf->b_recorded_changes);
|
||||||
buf->b_recorded_changes = NULL;
|
buf->b_recorded_changes = NULL;
|
||||||
|
|
||||||
if (save_updating_screen)
|
|
||||||
updating_screen = TRUE;
|
|
||||||
else
|
|
||||||
after_updating_screen(TRUE);
|
|
||||||
recursive = FALSE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -439,17 +608,10 @@ invoke_listeners(buf_T *buf)
|
|||||||
void
|
void
|
||||||
remove_listeners(buf_T *buf)
|
remove_listeners(buf_T *buf)
|
||||||
{
|
{
|
||||||
listener_T *lnr;
|
clean_listener_list(buf, &buf->b_listener, TRUE);
|
||||||
listener_T *next;
|
clean_listener_list(NULL, &buf->b_sync_listener, TRUE);
|
||||||
|
|
||||||
for (lnr = buf->b_listener; lnr != NULL; lnr = next)
|
|
||||||
{
|
|
||||||
next = lnr->lr_next;
|
|
||||||
free_callback(&lnr->lr_callback);
|
|
||||||
vim_free(lnr);
|
|
||||||
}
|
|
||||||
buf->b_listener = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -475,6 +637,12 @@ changed_common(
|
|||||||
changed();
|
changed();
|
||||||
|
|
||||||
#ifdef FEAT_EVAL
|
#ifdef FEAT_EVAL
|
||||||
|
// Immediately send this change to any listeners that require changes no to
|
||||||
|
// be buffered.
|
||||||
|
invoke_sync_listeners(curbuf, lnum, col, lnume, xtra);
|
||||||
|
|
||||||
|
// If there are any listeners accepting buffered changes then add changes
|
||||||
|
// to the current buffer's list, flushing previous changes first if necessary.
|
||||||
may_record_change(lnum, col, lnume, xtra);
|
may_record_change(lnum, col, lnume, xtra);
|
||||||
#endif
|
#endif
|
||||||
#ifdef FEAT_DIFF
|
#ifdef FEAT_DIFF
|
||||||
|
@@ -3795,3 +3795,7 @@ EXTERN char e_socket_server_unavailable[]
|
|||||||
#endif
|
#endif
|
||||||
EXTERN char e_osc_response_timed_out[]
|
EXTERN char e_osc_response_timed_out[]
|
||||||
INIT(= N_("E1568: OSC command response timed out: %.*s"));
|
INIT(= N_("E1568: OSC command response timed out: %.*s"));
|
||||||
|
#ifdef FEAT_EVAL
|
||||||
|
EXTERN char e_cannot_add_listener_in_listener_callback[]
|
||||||
|
INIT(= N_("E1569: Cannot use listener_add in a listener callback"));
|
||||||
|
#endif
|
||||||
|
@@ -1258,7 +1258,6 @@ static argcheck_T arg1_string_or_list_any[] = {arg_string_or_list_any};
|
|||||||
static argcheck_T arg1_string_or_list_string[] = {arg_string_or_list_string};
|
static argcheck_T arg1_string_or_list_string[] = {arg_string_or_list_string};
|
||||||
static argcheck_T arg1_string_or_nr[] = {arg_string_or_nr};
|
static argcheck_T arg1_string_or_nr[] = {arg_string_or_nr};
|
||||||
static argcheck_T arg1_string_or_blob[] = {arg_string_or_blob};
|
static argcheck_T arg1_string_or_blob[] = {arg_string_or_blob};
|
||||||
static argcheck_T arg2_any_buffer[] = {arg_any, arg_buffer};
|
|
||||||
static argcheck_T arg2_buffer_any[] = {arg_buffer, arg_any};
|
static argcheck_T arg2_buffer_any[] = {arg_buffer, arg_any};
|
||||||
static argcheck_T arg2_buffer_bool[] = {arg_buffer, arg_bool};
|
static argcheck_T arg2_buffer_bool[] = {arg_buffer, arg_bool};
|
||||||
static argcheck_T arg2_buffer_list_any[] = {arg_buffer, arg_list_any};
|
static argcheck_T arg2_buffer_list_any[] = {arg_buffer, arg_list_any};
|
||||||
@@ -1301,6 +1300,7 @@ static argcheck_T arg2_string_or_list_number[] = {arg_string_or_list_any, arg_nu
|
|||||||
static argcheck_T arg2_string_string_or_number[] = {arg_string, arg_string_or_nr};
|
static argcheck_T arg2_string_string_or_number[] = {arg_string, arg_string_or_nr};
|
||||||
static argcheck_T arg2_blob_dict[] = {arg_blob, arg_dict_any};
|
static argcheck_T arg2_blob_dict[] = {arg_blob, arg_dict_any};
|
||||||
static argcheck_T arg2_list_or_tuple_string[] = {arg_list_or_tuple, arg_string};
|
static argcheck_T arg2_list_or_tuple_string[] = {arg_list_or_tuple, arg_string};
|
||||||
|
static argcheck_T arg3_any_buffer_bool[] = {arg_any, arg_buffer, arg_bool};
|
||||||
static argcheck_T arg3_any_list_dict[] = {arg_any, arg_list_any, arg_dict_any};
|
static argcheck_T arg3_any_list_dict[] = {arg_any, arg_list_any, arg_dict_any};
|
||||||
static argcheck_T arg3_buffer_lnum_lnum[] = {arg_buffer, arg_lnum, arg_lnum};
|
static argcheck_T arg3_buffer_lnum_lnum[] = {arg_buffer, arg_lnum, arg_lnum};
|
||||||
static argcheck_T arg3_buffer_number_number[] = {arg_buffer, arg_number, arg_number};
|
static argcheck_T arg3_buffer_number_number[] = {arg_buffer, arg_number, arg_number};
|
||||||
@@ -2507,7 +2507,7 @@ static const funcentry_T global_functions[] =
|
|||||||
ret_string, f_list2str},
|
ret_string, f_list2str},
|
||||||
{"list2tuple", 1, 1, FEARG_1, arg1_list_any,
|
{"list2tuple", 1, 1, FEARG_1, arg1_list_any,
|
||||||
ret_tuple_any, f_list2tuple},
|
ret_tuple_any, f_list2tuple},
|
||||||
{"listener_add", 1, 2, FEARG_2, arg2_any_buffer,
|
{"listener_add", 1, 3, FEARG_2, arg3_any_buffer_bool,
|
||||||
ret_number, f_listener_add},
|
ret_number, f_listener_add},
|
||||||
{"listener_flush", 0, 1, FEARG_1, arg1_buffer,
|
{"listener_flush", 0, 1, FEARG_1, arg1_buffer,
|
||||||
ret_void, f_listener_flush},
|
ret_void, f_listener_flush},
|
||||||
|
5
src/po/vim.pot
generated
5
src/po/vim.pot
generated
@@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Vim\n"
|
"Project-Id-Version: Vim\n"
|
||||||
"Report-Msgid-Bugs-To: vim-dev@vim.org\n"
|
"Report-Msgid-Bugs-To: vim-dev@vim.org\n"
|
||||||
"POT-Creation-Date: 2025-08-27 19:10+0200\n"
|
"POT-Creation-Date: 2025-09-21 18:48+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@@ -8836,6 +8836,9 @@ msgstr ""
|
|||||||
msgid "E1568: OSC command response timed out: %.*s"
|
msgid "E1568: OSC command response timed out: %.*s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "E1569: Cannot use listener_add in a listener callback"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. type of cmdline window or 0
|
#. type of cmdline window or 0
|
||||||
#. result of cmdline window or 0
|
#. result of cmdline window or 0
|
||||||
#. buffer of cmdline window or NULL
|
#. buffer of cmdline window or NULL
|
||||||
|
@@ -3497,7 +3497,8 @@ struct file_buffer
|
|||||||
dictitem_T b_bufvar; // variable for "b:" Dictionary
|
dictitem_T b_bufvar; // variable for "b:" Dictionary
|
||||||
dict_T *b_vars; // internal variables, local to buffer
|
dict_T *b_vars; // internal variables, local to buffer
|
||||||
|
|
||||||
listener_T *b_listener;
|
listener_T *b_listener; // Listeners accepting buffered reports.
|
||||||
|
listener_T *b_sync_listener; // Listeners requiring unbuffered reports.
|
||||||
list_T *b_recorded_changes;
|
list_T *b_recorded_changes;
|
||||||
#endif
|
#endif
|
||||||
#ifdef FEAT_PROP_POPUP
|
#ifdef FEAT_PROP_POPUP
|
||||||
|
@@ -8,6 +8,14 @@ func s:StoreList(s, e, a, l)
|
|||||||
let s:list = a:l
|
let s:list = a:l
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
|
func s:StoreListUnbuffered(s, e, a, l)
|
||||||
|
let s:start = a:s
|
||||||
|
let s:end = a:e
|
||||||
|
let s:added = a:a
|
||||||
|
let s:text = getline(a:s)
|
||||||
|
let s:list2 = a:l
|
||||||
|
endfunc
|
||||||
|
|
||||||
func s:AnotherStoreList(l)
|
func s:AnotherStoreList(l)
|
||||||
let s:list2 = a:l
|
let s:list2 = a:l
|
||||||
endfunc
|
endfunc
|
||||||
@@ -131,9 +139,15 @@ func Test_listening()
|
|||||||
call setline(1, 'asdfasdf')
|
call setline(1, 'asdfasdf')
|
||||||
redraw
|
redraw
|
||||||
call assert_equal([], s:list)
|
call assert_equal([], s:list)
|
||||||
|
bwipe!
|
||||||
|
endfunc
|
||||||
|
|
||||||
" Trying to change the list fails
|
func Test_change_list_is_locked()
|
||||||
|
" Trying to change the list passed to the callback fails
|
||||||
|
new
|
||||||
|
call setline(1, ['one', 'two'])
|
||||||
let id = listener_add({b, s, e, a, l -> s:EvilStoreList(l)})
|
let id = listener_add({b, s, e, a, l -> s:EvilStoreList(l)})
|
||||||
|
|
||||||
let s:list3 = []
|
let s:list3 = []
|
||||||
call setline(1, 'asdfasdf')
|
call setline(1, 'asdfasdf')
|
||||||
redraw
|
redraw
|
||||||
@@ -143,6 +157,166 @@ func Test_listening()
|
|||||||
bwipe!
|
bwipe!
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
|
func Test_change_list_is_locked_unbuffered()
|
||||||
|
" Trying to change the list passed to the callback fails (unbuffered mode).
|
||||||
|
new
|
||||||
|
call setline(1, ['one', 'two'])
|
||||||
|
let id = listener_add({b, s, e, a, l -> s:EvilStoreList(l)}, bufnr(), v:true)
|
||||||
|
|
||||||
|
let s:list3 = []
|
||||||
|
call setline(1, 'asdfasdf')
|
||||||
|
redraw
|
||||||
|
call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list3)
|
||||||
|
|
||||||
|
eval id->listener_remove()
|
||||||
|
bwipe!
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
func Test_new_listener_does_not_receive_ood_changes()
|
||||||
|
new
|
||||||
|
call setline(1, ['one', 'two', 'three'])
|
||||||
|
let s:list = []
|
||||||
|
let s:list2 = []
|
||||||
|
|
||||||
|
" Add a listener and make a change.
|
||||||
|
let id = listener_add({b, s, e, a, l -> s:StoreList(s, e, a, l)})
|
||||||
|
call setline(1, 'one one')
|
||||||
|
|
||||||
|
" Add a second listener, it should not see the above change to the buffer,
|
||||||
|
" only the change after it was added.
|
||||||
|
let id = listener_add({b, s, e, a, l -> s:AnotherStoreList(l)})
|
||||||
|
call setline(2, 'two two')
|
||||||
|
|
||||||
|
redraw
|
||||||
|
call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 0}], s:list)
|
||||||
|
|
||||||
|
call listener_remove(id)
|
||||||
|
bwipe!
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
func Test_callbacks_do_not_recurse()
|
||||||
|
func DodgyExtendList(b, s, e, a, l)
|
||||||
|
call extend(s:list, a:l)
|
||||||
|
if len(s:list) < 3 " Limit recursion in the face of test failure.
|
||||||
|
call listener_flush()
|
||||||
|
redraw
|
||||||
|
endif
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
new
|
||||||
|
call setline(1, ['one', 'two', 'three'])
|
||||||
|
let s:list = []
|
||||||
|
|
||||||
|
" Add a listener and make a change.
|
||||||
|
let id = listener_add("DodgyExtendList")
|
||||||
|
call setline(1, 'one one')
|
||||||
|
|
||||||
|
" The callback should only be invoked once, i.e. recursion is blocked.
|
||||||
|
redraw
|
||||||
|
call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list)
|
||||||
|
|
||||||
|
call listener_remove(id)
|
||||||
|
bwipe!
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
func Test_clean_up_after_last_listener_removed()
|
||||||
|
new
|
||||||
|
call setline(1, ['one', 'two', 'three'])
|
||||||
|
let s:list = []
|
||||||
|
|
||||||
|
" Add a listener, make a change, but then remove the listener before the
|
||||||
|
" listener gets invoked.
|
||||||
|
let id = listener_add({b, s, e, a, l -> s:StoreList(s, e, a, l)})
|
||||||
|
call setline(3, 'three three')
|
||||||
|
let ok = listener_remove(id)
|
||||||
|
call assert_equal(1, ok)
|
||||||
|
|
||||||
|
" Further buffer changes should (obviously) have no effect.
|
||||||
|
let s:list = []
|
||||||
|
call setline(2, 'two two')
|
||||||
|
redraw
|
||||||
|
call assert_equal([], s:list)
|
||||||
|
|
||||||
|
" Add a new listener, it should not see the above change to line 3 of the
|
||||||
|
" buffer.
|
||||||
|
let id = listener_add({b, s, e, a, l -> s:StoreList(s, e, a, l)})
|
||||||
|
redraw
|
||||||
|
call assert_equal([], s:list)
|
||||||
|
|
||||||
|
call listener_remove(id)
|
||||||
|
bwipe!
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
func Test_a_callback_may_not_add_a_listener()
|
||||||
|
func ListenerWotAdds_listener(bufnr, start, end, added, changes)
|
||||||
|
call s:StoreList(a:start, a:end, a:added, a:changes)
|
||||||
|
call assert_fails(
|
||||||
|
\ "call listener_add({b, s, e, a, l -> s:AnotherStoreList(l)})", "E1569:")
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
new
|
||||||
|
call setline(1, ['one', 'two', 'three'])
|
||||||
|
let s:list = []
|
||||||
|
|
||||||
|
" Add a listener, make a change, but then remove the listener before the
|
||||||
|
" listener gets invoked.
|
||||||
|
let id = listener_add("ListenerWotAdds_listener")
|
||||||
|
call setline(3, 'three three')
|
||||||
|
redraw
|
||||||
|
call assert_equal([{'lnum': 3, 'end': 4, 'col': 1, 'added': 0}], s:list)
|
||||||
|
|
||||||
|
let s:list2 = []
|
||||||
|
call setline(2, 'two two')
|
||||||
|
redraw
|
||||||
|
call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 0}], s:list)
|
||||||
|
call assert_equal([], s:list2)
|
||||||
|
|
||||||
|
call listener_remove(id)
|
||||||
|
bwipe!
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
func Test_changes_can_be_unbuffered()
|
||||||
|
new
|
||||||
|
call setline(1, ['one', 'two', 'three'])
|
||||||
|
let s:list = []
|
||||||
|
let s:list2 = []
|
||||||
|
|
||||||
|
" Add both a buffered and an unbuffered listener.
|
||||||
|
let id_a = listener_add({b, s, e, a, l -> s:StoreList(s, e, a, l)})
|
||||||
|
let id_b = listener_add(
|
||||||
|
\ {b, s, e, a, l -> s:StoreListUnbuffered(s, e, a, l)},
|
||||||
|
\ bufnr(), v:true)
|
||||||
|
|
||||||
|
" Make a change, which only the second listener should see immediately.
|
||||||
|
call setline(2, 'two two')
|
||||||
|
call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 0}], s:list2)
|
||||||
|
call assert_equal(2, s:start)
|
||||||
|
call assert_equal(3, s:end)
|
||||||
|
call assert_equal(0, s:added)
|
||||||
|
call assert_equal([], s:list)
|
||||||
|
|
||||||
|
" Make another change, which only the second listener should see immediately.
|
||||||
|
call setline(3, 'three three')
|
||||||
|
call assert_equal([{'lnum': 3, 'end': 4, 'col': 1, 'added': 0}], s:list2)
|
||||||
|
call assert_equal(3, s:start)
|
||||||
|
call assert_equal(4, s:end)
|
||||||
|
call assert_equal(0, s:added)
|
||||||
|
call assert_equal([], s:list)
|
||||||
|
|
||||||
|
" Force changes to be flushed. Only the first listener should be invoked,
|
||||||
|
" with both the above changes.
|
||||||
|
let s:list2 = []
|
||||||
|
redraw
|
||||||
|
call assert_equal([
|
||||||
|
\ {'lnum': 2, 'end': 3, 'col': 1, 'added': 0},
|
||||||
|
\ {'lnum': 3, 'end': 4, 'col': 1, 'added': 0}], s:list)
|
||||||
|
call assert_equal([], s:list2)
|
||||||
|
|
||||||
|
call listener_remove(id_a)
|
||||||
|
call listener_remove(id_b)
|
||||||
|
bwipe!
|
||||||
|
endfunc
|
||||||
|
|
||||||
func s:StoreListArgs(buf, start, end, added, list)
|
func s:StoreListArgs(buf, start, end, added, list)
|
||||||
let s:buf = a:buf
|
let s:buf = a:buf
|
||||||
let s:start = a:start
|
let s:start = a:start
|
||||||
|
@@ -724,6 +724,8 @@ static char *(features[]) =
|
|||||||
|
|
||||||
static int included_patches[] =
|
static int included_patches[] =
|
||||||
{ /* Add new patch number below this line */
|
{ /* Add new patch number below this line */
|
||||||
|
/**/
|
||||||
|
1782,
|
||||||
/**/
|
/**/
|
||||||
1781,
|
1781,
|
||||||
/**/
|
/**/
|
||||||
|
Reference in New Issue
Block a user