Commit: Revert "buffered listener cannot obtain the text of a change"
Christian Brabandt <[email protected]>
| Newsgroups | gmane.editors.vim.devel |
|---|---|
| Message-ID | <[email protected]> |
Revert "buffered listener cannot obtain the text of a change" Commit: https://github.com/vim/vim/commit/3cd5d1d1aa8effbef32f8b6a4d0f4ade9acdcef6 Author: h-east <[email protected]> Date: Mon Aug 17 17:48:28 2026 +0900 Revert "buffered listener cannot obtain the text of a change" This reverts commit 9ce68018125e13cd365c68105245872b79d80c9c. diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 44ebf952f..d88d5deac 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -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()|. diff --git a/src/change.c b/src/change.c index 4ce587c8f..c9e27c194 100644 --- a/src/change.c +++ b/src/change.c @@ -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; } /* diff --git a/src/evalfunc.c b/src/evalfunc.c index 5fbfa4ab9..1b8cce9da 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -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}, diff --git a/src/proto/typval.pro b/src/proto/typval.pro index 1b5eb2905..fcd9cd1c4 100644 --- a/src/proto/typval.pro +++ b/src/proto/typval.pro @@ -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); diff --git a/src/structs.h b/src/structs.h index 6a26648c8..fdba4ee83 100644 --- a/src/structs.h +++ b/src/structs.h @@ -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 diff --git a/src/testdir/test_listener.vim b/src/testdir/test_listener.vim index c8d89802e..6b91e8d04 100644 --- a/src/testdir/test_listener.vim +++ b/src/testdir/test_listener.vim @@ -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")})', diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim index 49ab862d1..350a790fb 100644 --- a/src/testdir/test_vim9_builtin.vim +++ b/src/testdir/test_vim9_builtin.vim @@ -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() diff --git a/src/typval.c b/src/typval.c index 6a4947d8a..f704f039c 100644 --- a/src/typval.c +++ b/src/typval.c @@ -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. */ -- -- 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 visit https://groups.google.com/d/msgid/vim_dev/E1wvtCR-008DLb-Kq%40256bit.org.