patch 8.1.1321: no docs or tests for listener functions

Problem:    No docs or tests for listener functions.
Solution:   Add help and tests for listener_add() and listener_remove().
            Invoke the callbacks before redrawing.
diff --git a/src/change.c b/src/change.c
index 06d20f4..27ea9ac 100644
--- a/src/change.c
+++ b/src/change.c
@@ -184,7 +184,7 @@
     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 @@
     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 @@
     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;
-    }
 }
 
 /*
diff --git a/src/evalfunc.c b/src/evalfunc.c
index 02ca1ae..b67aeb8 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -2009,12 +2009,11 @@
     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 @@
 	semsg(_("E158: Invalid buffer name: %s"), tv_get_string(arg));
     return buf;
 }
-#endif
 
 /*
  * "bufname(expr)" function
diff --git a/src/proto/evalfunc.pro b/src/proto/evalfunc.pro
index c0ada9d..5a53136 100644
--- a/src/proto/evalfunc.pro
+++ b/src/proto/evalfunc.pro
@@ -5,6 +5,7 @@
 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);
diff --git a/src/screen.c b/src/screen.c
index 3ebc244..5cdbd2c 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -564,6 +564,11 @@
 	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 */
diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak
index 1f50bd8..7dc7e36 100644
--- a/src/testdir/Make_all.mak
+++ b/src/testdir/Make_all.mak
@@ -168,6 +168,7 @@
 	test_lispwords \
 	test_listchars \
 	test_listdict \
+	test_listener \
 	test_listlbr \
 	test_listlbr_utf8 \
 	test_lua \
@@ -359,6 +360,7 @@
 	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
index 0000000..87183e5
--- /dev/null
+++ b/src/testdir/test_listener.vim
@@ -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
diff --git a/src/version.c b/src/version.c
index 1829fa3..85704a6 100644
--- a/src/version.c
+++ b/src/version.c
@@ -768,6 +768,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1321,
+/**/
     1320,
 /**/
     1319,