Revert "buffered listener cannot obtain the text of a change"
This reverts commit 9ce6801812.
This commit is contained in:
1 parent
9ce6801812
commit
3cd5d1d1aa
8 files changed
+12
-298
No files matched your search
+6
-41
@@ -390,7 +390,7 @@ lispindent({lnum}) Number Lisp indent for line {lnum}
|
||||
list2blob({list}) Blob turn {list} of numbers into a Blob
|
||||
list2str({list} [, {utf8}]) String turn {list} of numbers into a String
|
||||
list2tuple({list}) Tuple turn {list} of items into a tuple
|
||||
listener_add({callback} [, {buf} [, {options}]])
|
||||
listener_add({callback} [, {buf} [, {unbuffered}]])
|
||||
Number add a callback to listen to changes
|
||||
listener_flush([{buf}]) none invoke listener callbacks
|
||||
listener_remove({id}) Number remove a listener callback
|
||||
@@ -6811,7 +6811,7 @@ list2tuple({list}) *list2tuple()*
|
||||
Return type: tuple<{type}> (depending on the given |List|)
|
||||
|
||||
|
||||
listener_add({callback} [, {buf} [, {options}]]) *listener_add()*
|
||||
listener_add({callback} [, {buf} [, {unbuffered}]]) *listener_add()*
|
||||
Add a callback function that will be invoked when changes have
|
||||
been made to buffer {buf}.
|
||||
{buf} refers to a buffer name or number. For the accepted
|
||||
@@ -6848,8 +6848,6 @@ listener_add({callback} [, {buf} [, {options}]]) *listener_add()*
|
||||
the change; one if unknown or the whole line
|
||||
was affected; this is a byte index, first
|
||||
character has a value of one.
|
||||
text only present when the "text" option is set,
|
||||
see below
|
||||
When lines are inserted (not when a line is split, e.g. by
|
||||
typing CR in Insert mode) the values are:
|
||||
lnum line above which the new line is added
|
||||
@@ -6868,15 +6866,7 @@ listener_add({callback} [, {buf} [, {options}]]) *listener_add()*
|
||||
added 0
|
||||
col first column with a change or 1
|
||||
|
||||
{options} is a Dictionary with these optional entries:
|
||||
unbuffered invoke the {callback} for every single change
|
||||
instead of for a batch of changes
|
||||
text include the resulting text in each item of
|
||||
"changes"
|
||||
For backwards compatibility {options} may also be a |Boolean|,
|
||||
which is then used for the "unbuffered" entry.
|
||||
|
||||
When "unbuffered" is |FALSE| or not provided the {callback} is
|
||||
When {unbuffered} is |FALSE| or not provided the {callback} is
|
||||
invoked:
|
||||
|
||||
1. Just before the screen is updated.
|
||||
@@ -6884,7 +6874,6 @@ listener_add({callback} [, {buf} [, {options}]]) *listener_add()*
|
||||
3. When a change is being made that changes the line count in
|
||||
a way that causes a line number in the list of changes to
|
||||
become invalid.
|
||||
4. When "text" is set and a lot of text has been recorded.
|
||||
|
||||
The entries are in the order the changes were made, thus the
|
||||
most recent change is at the end.
|
||||
@@ -6892,10 +6881,10 @@ listener_add({callback} [, {buf} [, {options}]]) *listener_add()*
|
||||
Because of the third reason for triggering a callback listed
|
||||
above, the line numbers passed to the callback are not
|
||||
guaranteed to be valid. In particular, the end value can be
|
||||
greater than line('$') + 1. If this is a problem then set
|
||||
"unbuffered".
|
||||
greater than line('$') + 1. If this is a problem then make
|
||||
{unbuffered} |TRUE|.
|
||||
|
||||
When "unbuffered" is |TRUE| the {callback} is invoked for every
|
||||
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
|
||||
@@ -6903,30 +6892,6 @@ listener_add({callback} [, {buf} [, {options}]]) *listener_add()*
|
||||
invoked, but later changes may make them invalid, thus keeping
|
||||
a copy for later might not work.
|
||||
|
||||
*listener-text*
|
||||
When "text" is set each item in "changes" gets a "text" entry,
|
||||
a List with the lines that occupy the changed region right
|
||||
after that change was made: the lines "lnum" up to but not
|
||||
including "end" plus "added". This List is empty when the
|
||||
change only deleted lines. Together with "lnum", "end" and
|
||||
"added" the item then fully describes the change: replace the
|
||||
lines "lnum" up to but not including "end" with "text".
|
||||
|
||||
The text is copied at the moment the change is recorded, so it
|
||||
is not affected by later changes. Without it a buffered
|
||||
{callback} cannot reliably obtain the text of a change: two
|
||||
changes to the same line do not change the line count and are
|
||||
reported together, and by then |getbufline()| can only return
|
||||
the result of the second one. Setting "unbuffered" avoids
|
||||
that as well, but then the {callback} is invoked for every
|
||||
single change, which is expensive for a command that changes
|
||||
many lines.
|
||||
|
||||
Setting "text" costs a copy of the changed lines for every
|
||||
change. To keep the memory used by a long sequence of changes
|
||||
bounded, the {callback} is also invoked when a lot of text has
|
||||
been recorded.
|
||||
|
||||
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()|.
|
||||
|
||||
+4
-77
@@ -207,7 +207,6 @@ clean_listener_list(buf_T *buf, listener_T **list, bool all)
|
||||
{
|
||||
list_unref(buf->b_recorded_changes);
|
||||
buf->b_recorded_changes = NULL;
|
||||
buf->b_recorded_text_size = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,60 +267,6 @@ check_recorded_changes(
|
||||
}
|
||||
}
|
||||
|
||||
// Amount of recorded text after which the listeners are invoked, to bound the
|
||||
// memory used by a long sequence of changes.
|
||||
#define LISTENER_TEXT_MAX (4 * 1024 * 1024)
|
||||
|
||||
/*
|
||||
* Return true when any listener in "list" asked for the resulting text.
|
||||
*/
|
||||
static bool
|
||||
listeners_want_text(listener_T *list)
|
||||
{
|
||||
listener_T *lnr;
|
||||
|
||||
for (lnr = list; lnr != NULL; lnr = lnr->lr_next)
|
||||
if (lnr->lr_text)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Store in "dict" the text that occupies the changed region right after the
|
||||
* change: the lines from "lnum" up to but not including "lnume" + "xtra".
|
||||
* Returns the number of bytes stored.
|
||||
*/
|
||||
static size_t
|
||||
add_change_text(
|
||||
buf_T *buf,
|
||||
dict_T *dict,
|
||||
linenr_T lnum,
|
||||
linenr_T lnume,
|
||||
long xtra)
|
||||
{
|
||||
list_T *l = list_alloc();
|
||||
linenr_T below = lnume + xtra; // line below the changed region
|
||||
size_t size = 0;
|
||||
|
||||
if (l == NULL)
|
||||
return 0;
|
||||
if (dict_add_list(dict, "text", l) == FAIL)
|
||||
{
|
||||
list_free(l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (below > buf->b_ml.ml_line_count + 1)
|
||||
below = buf->b_ml.ml_line_count + 1;
|
||||
for (linenr_T lp = lnum < 1 ? 1 : lnum; lp < below; ++lp)
|
||||
{
|
||||
if (list_append_string(l, ml_get_buf(buf, lp, FALSE), -1) == FAIL)
|
||||
break;
|
||||
size += (size_t)ml_get_buf_len(buf, lp) + 1;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/*
|
||||
* Record a change for listeners added with listener_add().
|
||||
* Always for the current buffer.
|
||||
@@ -361,14 +306,8 @@ may_record_change(
|
||||
dict_add_number(dict, "end", (varnumber_T)lnume);
|
||||
dict_add_number(dict, "added", (varnumber_T)xtra);
|
||||
dict_add_number(dict, "col", (varnumber_T)col + 1);
|
||||
if (listeners_want_text(curbuf->b_listener))
|
||||
curbuf->b_recorded_text_size +=
|
||||
add_change_text(curbuf, dict, lnum, lnume, xtra);
|
||||
|
||||
list_append_dict(curbuf->b_recorded_changes, dict);
|
||||
|
||||
if (curbuf->b_recorded_text_size > LISTENER_TEXT_MAX)
|
||||
invoke_listeners(curbuf);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -380,8 +319,7 @@ f_listener_add(typval_T *argvars, typval_T *rettv)
|
||||
callback_T callback;
|
||||
listener_T *lnr;
|
||||
buf_T *buf = curbuf;
|
||||
bool unbuffered = false;
|
||||
bool want_text = false;
|
||||
int unbuffered = 0;
|
||||
|
||||
if (check_secure())
|
||||
return;
|
||||
@@ -394,7 +332,7 @@ f_listener_add(typval_T *argvars, typval_T *rettv)
|
||||
|
||||
if (in_vim9script() && (
|
||||
check_for_opt_buffer_arg(argvars, 1) == FAIL
|
||||
|| check_for_opt_bool_or_dict_arg(argvars, 2) == FAIL))
|
||||
|| check_for_opt_bool_arg(argvars, 2) == FAIL))
|
||||
return;
|
||||
|
||||
callback = get_callback(&argvars[0]);
|
||||
@@ -409,15 +347,8 @@ f_listener_add(typval_T *argvars, typval_T *rettv)
|
||||
free_callback(&callback);
|
||||
return;
|
||||
}
|
||||
if (argvars[2].v_type == VAR_DICT)
|
||||
{
|
||||
dict_T *d = argvars[2].vval.v_dict;
|
||||
|
||||
unbuffered = dict_get_bool(d, "unbuffered", false);
|
||||
want_text = dict_get_bool(d, "text", false);
|
||||
}
|
||||
else if (argvars[2].v_type != VAR_UNKNOWN)
|
||||
unbuffered = tv_get_bool(&argvars[2]);
|
||||
if (argvars[2].v_type != VAR_UNKNOWN)
|
||||
unbuffered = (int)tv_get_bool(&argvars[2]);
|
||||
}
|
||||
|
||||
lnr = ALLOC_CLEAR_ONE(listener_T);
|
||||
@@ -446,7 +377,6 @@ f_listener_add(typval_T *argvars, typval_T *rettv)
|
||||
|
||||
set_callback(&lnr->lr_callback, &callback);
|
||||
|
||||
lnr->lr_text = want_text;
|
||||
lnr->lr_id = ++next_listener_id;
|
||||
rettv->vval.v_number = lnr->lr_id;
|
||||
}
|
||||
@@ -643,8 +573,6 @@ invoke_sync_listeners(
|
||||
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);
|
||||
if (listeners_want_text(buf->b_sync_listener))
|
||||
(void)add_change_text(buf, dict, start, end, added);
|
||||
list_append_dict(recorded_changes, dict);
|
||||
|
||||
invoke_listener_set(
|
||||
@@ -688,7 +616,6 @@ invoke_listeners(buf_T *buf)
|
||||
|
||||
list_unref(buf->b_recorded_changes);
|
||||
buf->b_recorded_changes = NULL;
|
||||
buf->b_recorded_text_size = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+2
-17
@@ -412,20 +412,6 @@ arg_bool_or_nr(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check "type" is a bool or a dict of 'any'.
|
||||
*/
|
||||
static int
|
||||
arg_bool_or_dict_any(
|
||||
type_T *type,
|
||||
type_T *decl_type UNUSED,
|
||||
argcontext_T *context)
|
||||
{
|
||||
if (type->tt_type == VAR_DICT || type_any_or_unknown(type))
|
||||
return OK;
|
||||
return check_arg_type(&t_bool, type, context);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check "type" is a list of 'any' or a blob.
|
||||
*/
|
||||
@@ -1315,8 +1301,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_blob_dict[] = {arg_blob, arg_dict_any};
|
||||
static argcheck_T arg2_list_or_tuple_string[] = {arg_list_or_tuple, arg_string};
|
||||
static argcheck_T arg3_any_buffer_bool_or_dict[] = {
|
||||
arg_any, arg_buffer, arg_bool_or_dict_any};
|
||||
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_buffer_lnum_lnum[] = {arg_buffer, arg_lnum, arg_lnum};
|
||||
static argcheck_T arg3_buffer_number_number[] = {arg_buffer, arg_number, arg_number};
|
||||
@@ -2530,7 +2515,7 @@ static const funcentry_T global_functions[] =
|
||||
ret_string, f_list2str},
|
||||
{"list2tuple", 1, 1, FEARG_1, arg1_list_any,
|
||||
ret_tuple_any, f_list2tuple},
|
||||
{"listener_add", 1, 3, FEARG_2, arg3_any_buffer_bool_or_dict,
|
||||
{"listener_add", 1, 3, FEARG_2, arg3_any_buffer_bool,
|
||||
ret_number, f_listener_add},
|
||||
{"listener_flush", 0, 1, FEARG_1, arg1_buffer,
|
||||
ret_void, f_listener_flush},
|
||||
|
||||
@@ -20,7 +20,6 @@ int check_for_float_or_nr_arg(typval_T *args, int idx);
|
||||
int check_for_bool_arg(typval_T *args, int idx);
|
||||
int check_for_opt_bool_arg(typval_T *args, int idx);
|
||||
int check_for_opt_bool_or_number_arg(typval_T *args, int idx);
|
||||
int check_for_opt_bool_or_dict_arg(typval_T *args, int idx);
|
||||
int check_for_blob_arg(typval_T *args, int idx);
|
||||
int check_for_list_arg(typval_T *args, int idx);
|
||||
int check_for_nonnull_list_arg(typval_T *args, int idx);
|
||||
|
||||
@@ -2973,7 +2973,6 @@ struct listener_S
|
||||
{
|
||||
listener_T *lr_next;
|
||||
int lr_id;
|
||||
bool lr_text; // include the resulting text in each change
|
||||
callback_T lr_callback;
|
||||
};
|
||||
|
||||
@@ -3634,7 +3633,6 @@ struct file_buffer
|
||||
listener_T *b_listener; // Listeners accepting buffered reports.
|
||||
listener_T *b_sync_listener; // Listeners requiring unbuffered reports.
|
||||
list_T *b_recorded_changes;
|
||||
size_t b_recorded_text_size; // bytes of text held by the above
|
||||
#endif
|
||||
#ifdef FEAT_PROP_POPUP
|
||||
bool b_has_textprop; // true when text props were added
|
||||
|
||||
@@ -801,153 +801,6 @@ func Test_listener_blockwise_paste()
|
||||
bwipe!
|
||||
endfunc
|
||||
|
||||
func s:StoreChanges(l)
|
||||
call add(s:changes, deepcopy(a:l))
|
||||
endfunc
|
||||
|
||||
" The "text" option makes each change self-contained.
|
||||
func Test_listener_text()
|
||||
new
|
||||
call setline(1, ['one', 'two', 'three'])
|
||||
let s:changes = []
|
||||
let id = listener_add({b, s, e, a, l -> s:StoreChanges(l)},
|
||||
\ bufnr(), #{text: v:true})
|
||||
|
||||
" Two changes to the same line do not change the line count, so they are
|
||||
" reported together. Each one still carries the text it resulted in.
|
||||
call setline(1, 'first')
|
||||
call setline(1, 'second')
|
||||
call listener_flush()
|
||||
call assert_equal([[
|
||||
\ {'lnum': 1, 'end': 2, 'col': 1, 'added': 0, 'text': ['first']},
|
||||
\ {'lnum': 1, 'end': 2, 'col': 1, 'added': 0, 'text': ['second']}]],
|
||||
\ s:changes)
|
||||
|
||||
" Inserted lines are reported as the text that was inserted.
|
||||
let s:changes = []
|
||||
call append(1, ['a', 'b'])
|
||||
call listener_flush()
|
||||
call assert_equal([[
|
||||
\ {'lnum': 2, 'end': 2, 'col': 1, 'added': 2, 'text': ['a', 'b']}]],
|
||||
\ s:changes)
|
||||
|
||||
" Deleting lines leaves an empty text.
|
||||
let s:changes = []
|
||||
2,3del
|
||||
call listener_flush()
|
||||
call assert_equal([[
|
||||
\ {'lnum': 2, 'end': 4, 'col': 1, 'added': -2, 'text': []}]],
|
||||
\ s:changes)
|
||||
|
||||
" Deleting and inserting at the same spot keeps both entries apart.
|
||||
let s:changes = []
|
||||
call setline(1, ['one', 'two', 'three'])
|
||||
call listener_flush()
|
||||
let s:changes = []
|
||||
1del
|
||||
call append(0, 'zero')
|
||||
call listener_flush()
|
||||
call assert_equal([
|
||||
\ [{'lnum': 1, 'end': 2, 'col': 1, 'added': -1, 'text': []}],
|
||||
\ [{'lnum': 1, 'end': 1, 'col': 1, 'added': 1, 'text': ['zero']}]],
|
||||
\ s:changes)
|
||||
|
||||
call listener_remove(id)
|
||||
bwipe!
|
||||
endfunc
|
||||
|
||||
" Deleting every line and undoing it are each reported as one change.
|
||||
func Test_listener_text_whole_buffer()
|
||||
new
|
||||
call setline(1, range(1, 3000)->map({_, v -> 'line ' .. v}))
|
||||
let s:changes = []
|
||||
let id = listener_add({b, s, e, a, l -> s:StoreChanges(l)},
|
||||
\ bufnr(), #{text: v:true})
|
||||
let &undolevels = &undolevels
|
||||
|
||||
" Nothing is left, so no text is copied.
|
||||
%delete _
|
||||
call listener_flush()
|
||||
call assert_equal([[
|
||||
\ {'lnum': 1, 'end': 3001, 'col': 1, 'added': -3000, 'text': []}]],
|
||||
\ s:changes)
|
||||
|
||||
" The undo restores all the lines, so they are all copied.
|
||||
let s:changes = []
|
||||
undo
|
||||
call listener_flush()
|
||||
call assert_equal(1, len(s:changes))
|
||||
call assert_equal(1, len(s:changes[0]))
|
||||
let change = s:changes[0][0]
|
||||
call assert_equal(1, change.lnum)
|
||||
call assert_equal(2, change.end)
|
||||
call assert_equal(2999, change.added)
|
||||
call assert_equal(3000, len(change.text))
|
||||
call assert_equal('line 1', change.text[0])
|
||||
call assert_equal('line 3000', change.text[-1])
|
||||
|
||||
call listener_remove(id)
|
||||
bwipe!
|
||||
endfunc
|
||||
|
||||
" The "text" option also applies to an unbuffered listener.
|
||||
func Test_listener_text_unbuffered()
|
||||
new
|
||||
call setline(1, ['one', 'two', 'three'])
|
||||
let s:changes = []
|
||||
let id = listener_add({b, s, e, a, l -> s:StoreChanges(l)},
|
||||
\ bufnr(), #{unbuffered: v:true, text: v:true})
|
||||
|
||||
call setline(2, 'two two')
|
||||
call assert_equal([[
|
||||
\ {'lnum': 2, 'end': 3, 'col': 1, 'added': 0, 'text': ['two two']}]],
|
||||
\ s:changes)
|
||||
|
||||
call listener_remove(id)
|
||||
bwipe!
|
||||
endfunc
|
||||
|
||||
" Without the "text" option there is no "text" entry.
|
||||
func Test_listener_no_text_by_default()
|
||||
new
|
||||
call setline(1, ['one', 'two'])
|
||||
let s:changes = []
|
||||
let id = listener_add({b, s, e, a, l -> s:StoreChanges(l)},
|
||||
\ bufnr(), #{unbuffered: v:true})
|
||||
|
||||
call setline(1, 'one one')
|
||||
call assert_equal([[
|
||||
\ {'lnum': 1, 'end': 2, 'col': 1, 'added': 0}]], s:changes)
|
||||
|
||||
call listener_remove(id)
|
||||
bwipe!
|
||||
endfunc
|
||||
|
||||
" The third argument keeps accepting a Boolean.
|
||||
func Test_listener_options_argument()
|
||||
new
|
||||
call setline(1, ['one', 'two'])
|
||||
let s:changes = []
|
||||
let id = listener_add({b, s, e, a, l -> s:StoreChanges(l)}, bufnr(), v:true)
|
||||
call setline(1, 'one one')
|
||||
call assert_equal([[
|
||||
\ {'lnum': 1, 'end': 2, 'col': 1, 'added': 0}]], s:changes)
|
||||
call listener_remove(id)
|
||||
|
||||
" An empty Dictionary is the same as not passing the argument.
|
||||
let s:changes = []
|
||||
let id = listener_add({b, s, e, a, l -> s:StoreChanges(l)}, bufnr(), {})
|
||||
call setline(1, 'one two')
|
||||
call assert_equal([], s:changes)
|
||||
call listener_flush()
|
||||
call assert_equal([[
|
||||
\ {'lnum': 1, 'end': 2, 'col': 1, 'added': 0}]], s:changes)
|
||||
call listener_remove(id)
|
||||
|
||||
call assert_fails('call listener_add("Foo", bufnr(), [])', 'E745:')
|
||||
bwipe!
|
||||
endfunc
|
||||
|
||||
func Test_listener_add_in_sandbox()
|
||||
call assert_fails(
|
||||
\ 'sandbox call redraw_listener_add({"on_start": function("tr")})',
|
||||
|
||||
@@ -2657,7 +2657,6 @@ enddef
|
||||
|
||||
def Test_listener_add()
|
||||
v9.CheckSourceDefAndScriptFailure(['listener_add("1", true)'], ['E1013: Argument 2: type mismatch, expected string but got bool', 'E1220: String or Number required for argument 2'])
|
||||
v9.CheckSourceDefAndScriptFailure(['listener_add("1", 1, [1])'], ['E1013: Argument 3: type mismatch, expected bool but got list<number>', 'E1212: Bool required for argument 3'])
|
||||
enddef
|
||||
|
||||
def Test_listener_flush()
|
||||
|
||||
@@ -577,18 +577,6 @@ check_for_opt_bool_or_number_arg(typval_T *args, int idx)
|
||||
return check_for_bool_or_number_arg(args, idx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for an optional bool or dict argument at 'idx'.
|
||||
* Return FAIL if the type is wrong.
|
||||
*/
|
||||
int
|
||||
check_for_opt_bool_or_dict_arg(typval_T *args, int idx)
|
||||
{
|
||||
if (args[idx].v_type == VAR_UNKNOWN || args[idx].v_type == VAR_DICT)
|
||||
return OK;
|
||||
return check_for_bool_arg(args, idx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Give an error and return FAIL unless "args[idx]" is a blob.
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user