]> granicus.if.org Git - vim/commitdiff
patch 8.1.1321: no docs or tests for listener functions v8.1.1321
authorBram Moolenaar <Bram@vim.org>
Sat, 11 May 2019 19:14:24 +0000 (21:14 +0200)
committerBram Moolenaar <Bram@vim.org>
Sat, 11 May 2019 19:14:24 +0000 (21:14 +0200)
Problem:    No docs or tests for listener functions.
Solution:   Add help and tests for listener_add() and listener_remove().
            Invoke the callbacks before redrawing.

runtime/doc/eval.txt
runtime/doc/usr_41.txt
src/change.c
src/evalfunc.c
src/proto/evalfunc.pro
src/screen.c
src/testdir/Make_all.mak
src/testdir/test_listener.vim [new file with mode: 0644]
src/version.c

index a8109d93a88d3a6359b61ae0f5360b08d96a73dc..0b71c44dabf3721b2e6dae0de661c8e44d066a6f 100644 (file)
@@ -2457,6 +2457,9 @@ line({expr})                      Number  line nr of cursor, last line or mark
 line2byte({lnum})              Number  byte count of line {lnum}
 lispindent({lnum})             Number  Lisp indent for line {lnum}
 list2str({list} [, {utf8}])    String  turn numbers in {list} into a String
+listener_add({callback} [, {buf}])
+                               Number  add a callback to listen to changes
+listener_remove({id})          none    remove a listener callback
 localtime()                    Number  current time
 log({expr})                    Float   natural logarithm (base e) of {expr}
 log10({expr})                  Float   logarithm of Float {expr} to base 10
@@ -6311,6 +6314,53 @@ list2str({list} [, {utf8}])                              *list2str()*
                With utf-8 composing characters work as expected: >
                        list2str([97, 769])     returns "á"
 <
+listener_add({callback} [, {buf}])                     *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
+               values, see |bufname()|.  When {buf} is omitted the current
+               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.  Each list item is a dictionary with these entries:
+                   lnum        the first line number of the change
+                   end         the first line below the change
+                   added       number of lines added; negative if lines were
+                               deleted
+                   col         first column in "lnum" that was affected by
+                               the change; one if unknown or the whole line
+                               was affected; this is a byte index, first
+                               character has a value of one.
+               When lines are inserted the values are:
+                   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 {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
+               of a buffer.
+               The {callback} is also not invoked when the buffer is
+               unloaded, use the |BufUnload| autocmd event for that.
+
+listener_remove({id})                                  *listener_remove()*
+               Remove a listener previously added with listener_add().
+
 localtime()                                            *localtime()*
                Return the current time, measured as seconds since 1st Jan
                1970.  See also |strftime()| and |getftime()|.
index c5317e231e4411094c69338fb4471109e535ec96..866664c4dfd86230b5ed8cca7ece0999f7eb82e7 100644 (file)
@@ -812,6 +812,8 @@ Buffers, windows and the argument list:
        setbufline()            replace a line in the specified buffer
        appendbufline()         append a list of lines in the specified buffer
        deletebufline()         delete lines from a specified buffer
+       listener_add()          add a callback to listen to changes
+       listener_remove()       remove a listener callback
        win_findbuf()           find windows containing a buffer
        win_getid()             get window ID of a window
        win_gotoid()            go to window with ID
index 06d20f4ac26979b9d7b629dabfa795265a023af9..27ea9ac8fba7239b6b3e0feda167fb528bc0cd14 100644 (file)
@@ -184,7 +184,7 @@ may_record_change(
     dict_add_number(dict, "lnum", (varnumber_T)lnum);
     dict_add_number(dict, "end", (varnumber_T)lnume);
     dict_add_number(dict, "added", (varnumber_T)xtra);
-    dict_add_number(dict, "col", (varnumber_T)col);
+    dict_add_number(dict, "col", (varnumber_T)col + 1);
 
     list_append_dict(recorded_changes, dict);
 }
@@ -198,19 +198,27 @@ f_listener_add(typval_T *argvars, typval_T *rettv)
     char_u     *callback;
     partial_T  *partial;
     listener_T *lnr;
+    buf_T      *buf = curbuf;
 
     callback = get_callback(&argvars[0], &partial);
     if (callback == NULL)
        return;
 
+    if (argvars[1].v_type != VAR_UNKNOWN)
+    {
+       buf = get_buf_arg(&argvars[1]);
+       if (buf == NULL)
+           return;
+    }
+
     lnr = (listener_T *)alloc_clear((sizeof(listener_T)));
     if (lnr == NULL)
     {
        free_callback(callback, partial);
        return;
     }
-    lnr->lr_next = curbuf->b_listener;
-    curbuf->b_listener = lnr;
+    lnr->lr_next = buf->b_listener;
+    buf->b_listener = lnr;
 
     if (partial == NULL)
        lnr->lr_callback = vim_strsave(callback);
@@ -232,22 +240,23 @@ f_listener_remove(typval_T *argvars, typval_T *rettv UNUSED)
     listener_T *next;
     listener_T *prev = NULL;
     int                id = tv_get_number(argvars);
-    buf_T      *buf = curbuf;
+    buf_T      *buf;
 
-    for (lnr = buf->b_listener; lnr != NULL; lnr = next)
-    {
-       next = lnr->lr_next;
-       if (lnr->lr_id == id)
+    for (buf = firstbuf; buf != NULL; buf = buf->b_next)
+       for (lnr = buf->b_listener; lnr != NULL; lnr = next)
        {
-           if (prev != NULL)
-               prev->lr_next = lnr->lr_next;
-           else
-               buf->b_listener = lnr->lr_next;
-           free_callback(lnr->lr_callback, lnr->lr_partial);
-           vim_free(lnr);
+           next = lnr->lr_next;
+           if (lnr->lr_id == id)
+           {
+               if (prev != NULL)
+                   prev->lr_next = lnr->lr_next;
+               else
+                   buf->b_listener = lnr->lr_next;
+               free_callback(lnr->lr_callback, lnr->lr_partial);
+               vim_free(lnr);
+           }
+           prev = lnr;
        }
-       prev = lnr;
-    }
 }
 
 /*
index 02ca1ae8289d8f17497ab853a7f2e1b7f8a70a97..b67aeb8d7f22a24b625375809ab02a15c1aa8ddb 100644 (file)
@@ -2009,12 +2009,11 @@ tv_get_buf(typval_T *tv, int curtab_only)
     return buf;
 }
 
-#ifdef FEAT_SIGNS
 /*
  * Get the buffer from "arg" and give an error and return NULL if it is not
  * valid.
  */
-    static buf_T *
+    buf_T *
 get_buf_arg(typval_T *arg)
 {
     buf_T *buf;
@@ -2026,7 +2025,6 @@ get_buf_arg(typval_T *arg)
        semsg(_("E158: Invalid buffer name: %s"), tv_get_string(arg));
     return buf;
 }
-#endif
 
 /*
  * "bufname(expr)" function
index c0ada9d9fef8cf09f4dc683874922a8ab7622cde..5a53136cfd08591ef83fb87d7e0fe2dfb51ae093 100644 (file)
@@ -5,6 +5,7 @@ int find_internal_func(char_u *name);
 int call_internal_func(char_u *name, int argcount, typval_T *argvars, typval_T *rettv);
 buf_T *buflist_find_by_name(char_u *name, int curtab_only);
 buf_T *tv_get_buf(typval_T *tv, int curtab_only);
+buf_T *get_buf_arg(typval_T *arg);
 void execute_redir_str(char_u *value, int value_len);
 void mzscheme_call_vim(char_u *name, typval_T *args, typval_T *rettv);
 float_T vim_round(float_T f);
index 3ebc244f588da2caae3131f5b3be5708b37ed871..5cdbd2c87f56b970b3c86b827064bad44c076799 100644 (file)
@@ -564,6 +564,11 @@ update_screen(int type_arg)
        type = 0;
     }
 
+#ifdef FEAT_EVAL
+    // Before updating the screen, notify any listeners of changed text.
+    invoke_listeners();
+#endif
+
     if (must_redraw)
     {
        if (type < must_redraw)     /* use maximal type */
index 1f50bd8cf813c46eb2fb7647cac368fd5da24480..7dc7e368acc168a1e570975cc43174272fe1ca8a 100644 (file)
@@ -168,6 +168,7 @@ NEW_TESTS = \
        test_lispwords \
        test_listchars \
        test_listdict \
+       test_listener \
        test_listlbr \
        test_listlbr_utf8 \
        test_lua \
@@ -359,6 +360,7 @@ NEW_TESTS_RES = \
        test_lineending.res \
        test_listchars.res \
        test_listdict.res \
+       test_listener.res \
        test_listlbr.res \
        test_lua.res \
        test_makeencoding.res \
diff --git a/src/testdir/test_listener.vim b/src/testdir/test_listener.vim
new file mode 100644 (file)
index 0000000..87183e5
--- /dev/null
@@ -0,0 +1,77 @@
+" tests for listener_add() and listener_remove()
+
+func StoreList(l)
+  let g:list = a:l
+endfunc
+
+func AnotherStoreList(l)
+  let g:list2 = a:l
+endfunc
+
+func EvilStoreList(l)
+  let g:list3 = a:l
+  call assert_fails("call add(a:l, 'myitem')", "E742:")
+endfunc
+
+func Test_listening()
+  new
+  call setline(1, ['one', 'two'])
+  let id = listener_add({l -> StoreList(l)})
+  call setline(1, 'one one')
+  redraw
+  call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], g:list)
+
+  " Two listeners, both get called.
+  let id2 = listener_add({l -> AnotherStoreList(l)})
+  let g:list = []
+  let g:list2 = []
+  exe "normal $asome\<Esc>"
+  redraw
+  call assert_equal([{'lnum': 1, 'end': 2, 'col': 8, 'added': 0}], g:list)
+  call assert_equal([{'lnum': 1, 'end': 2, 'col': 8, 'added': 0}], g:list2)
+
+  call listener_remove(id2)
+  let g:list = []
+  let g:list2 = []
+  call setline(3, 'three')
+  redraw
+  call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1}], g:list)
+  call assert_equal([], g:list2)
+
+  " the "o" command first adds an empty line and then changes it
+  let g: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}], g:list)
+
+  let g:list = []
+  call listener_remove(id)
+  call setline(1, 'asdfasdf')
+  redraw
+  call assert_equal([], g:list)
+
+  " Trying to change the list fails
+  let id = listener_add({l -> EvilStoreList(l)})
+  let g:list3 = []
+  call setline(1, 'asdfasdf')
+  redraw
+  call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], g:list3)
+
+  bwipe!
+endfunc
+
+func Test_listening_other_buf()
+  new
+  call setline(1, ['one', 'two'])
+  let bufnr = bufnr('')
+  normal ww
+  let id = listener_add({l -> StoreList(l)}, bufnr)
+  let g:list = []
+  call setbufline(bufnr, 1, 'hello')
+  redraw
+  call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], g:list)
+
+  exe "buf " .. bufnr
+  bwipe!
+endfunc
index 1829fa338ab3bf970f40ac13d0c95c5de623801f..85704a620392686689d31c92c54b9074d8aae1c6 100644 (file)
@@ -767,6 +767,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1321,
 /**/
     1320,
 /**/