changeset 17366:9843fbfa0ee5 v8.1.1682

patch 8.1.1682: placing a larger number of signs is slow commit https://github.com/vim/vim/commit/809ce4d317fe12db0b2c17f16b4f77200fb060c4 Author: Bram Moolenaar <Bram@vim.org> Date: Sat Jul 13 21:21:40 2019 +0200 patch 8.1.1682: placing a larger number of signs is slow Problem: Placing a larger number of signs is slow. Solution: Add functions for dealing with a list of signs. (Yegappan Lakshmanan, closes #4636)
author Bram Moolenaar <Bram@vim.org>
date Sat, 13 Jul 2019 21:30:04 +0200
parents c6491636f12e
children d2959e9ddc7c
files runtime/doc/eval.txt runtime/doc/usr_41.txt src/evalfunc.c src/proto/sign.pro src/sign.c src/testdir/test_signs.vim src/version.c
diffstat 7 files changed, 620 insertions(+), 143 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1,4 +1,4 @@
-*eval.txt*	For Vim version 8.1.  Last change: 2019 Jul 04
+*eval.txt*	For Vim version 8.1.  Last change: 2019 Jul 13
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -2660,6 +2660,7 @@ shellescape({string} [, {special}])
 					command argument
 shiftwidth([{col}])		Number	effective value of 'shiftwidth'
 sign_define({name} [, {dict}])	Number	define or update a sign
+sign_define({list})		List	define or update a list of signs
 sign_getdefined([{name}])	List	get a list of defined signs
 sign_getplaced([{expr} [, {dict}]])
 				List	get a list of placed signs
@@ -2667,9 +2668,12 @@ sign_jump({id}, {group}, {expr})
 				Number	jump to a sign
 sign_place({id}, {group}, {name}, {expr} [, {dict}])
 				Number	place a sign
+sign_placelist({list})		List	place a list of signs
 sign_undefine([{name}])		Number	undefine a sign
+sign_undefine({list})		List	undefine a list of signs
 sign_unplace({group} [, {dict}])
 				Number	unplace a sign
+sign_unplacelist({list})	List	unplace a list of signs
 simplify({filename})		String	simplify filename as much as possible
 sin({expr})			Float	sine of {expr}
 sinh({expr})			Float	hyperbolic sine of {expr}
@@ -6494,6 +6498,8 @@ listener_flush([{buf}])					*listener_fl
 
 listener_remove({id})					*listener_remove()*
 		Remove a listener previously added with listener_add().
+		Returns zero when {id} could not be found, one when {id} was
+		removed.
 
 localtime()						*localtime()*
 		Return the current time, measured as seconds since 1st Jan
@@ -8624,6 +8630,7 @@ shiftwidth([{col}])						*shiftwidth()*
 		no {col} argument is given, column 1 will be assumed.
 
 sign_define({name} [, {dict}])				*sign_define()*
+sign_define({list})
 		Define a new sign named {name} or modify the attributes of an
 		existing sign.  This is similar to the |:sign-define| command.
 
@@ -8643,11 +8650,25 @@ sign_define({name} [, {dict}])				*sign_
 		If the sign named {name} already exists, then the attributes
 		of the sign are updated.
 
-		Returns 0 on success and -1 on failure.
+		The one argument {list} can be used to define a list of signs.
+		Each list item is a dictionary with the above items in {dict}
+		and a 'name' item for the sign name.
+
+		Returns 0 on success and -1 on failure.  When the one argument
+		{list} is used, then returns a List of values one for each
+		defined sign.
 
 		Examples: >
-			call sign_define("mySign", {"text" : "=>", "texthl" :
-					\ "Error", "linehl" : "Search"})
+			call sign_define("mySign", {
+				\ "text" : "=>",
+				\ "texthl" : "Error",
+				\ "linehl" : "Search"})
+			call sign_define([
+				\ {'name' : 'sign1',
+				\  'text' : '=>'},
+				\ {'name' : 'sign2',
+				\  'text' : '!!'}
+				\ ])
 <
 sign_getdefined([{name}])				*sign_getdefined()*
 		Get a list of defined signs and their attributes.
@@ -8801,17 +8822,85 @@ sign_place({id}, {group}, {name}, {expr}
 			call sign_place(10, 'g3', 'sign4', 'json.c',
 					\ {'lnum' : 40, 'priority' : 90})
 <
+							*sign_placelist()*
+sign_placelist({list})
+		Place one or more signs.  This is similar to the
+		|sign_place()| function.  The {list} argument specifies the
+		List of signs to place. Each list item is a dict with the
+		following sign attributes:
+		    buffer	buffer name or number. For the accepted
+				values, see |bufname()|.
+		    group	sign group. {group} functions as a namespace
+				for {id}, thus two groups can use the same
+				IDs. If not specified or set to an empty
+				string, then the global group is used.   See
+				|sign-group| for more information.
+		    id		sign identifier. If not specified or zero,
+				then a new unique identifier is allocated.
+				Otherwise the specified number is used. See
+				|sign-identifier| for more information.
+		    lnum	line number in the buffer {expr} where the
+				sign is to be placed. For the accepted values,
+				see |line()|.
+		    name	name of the sign to place. See |sign_define()|
+		    		for more information.
+		    priority	priority of the sign. When multiple signs are
+				placed on a line, the sign with the highest
+				priority is used. If not specified, the
+				default value of 10 is used. See
+				|sign-priority| for more information.
+
+		If {id} refers to an existing sign, then the existing sign is
+		modified to use the specified {name} and/or {priority}.
+
+		Returns a List of sign identifiers. If failed to place a
+		sign, the corresponding list item is set to -1.
+
+		Examples: >
+			" Place sign s1 with id 5 at line 20 and id 10 at line
+			" 30 in buffer a.c
+			let [n1, n2] = sign_place([
+				\ {'id' : 5,
+				\  'name' : 's1',
+				\  'buffer' : 'a.c',
+				\  'lnum' : 20},
+				\ {'id' : 10,
+				\  'name' : 's1',
+				\  'buffer' : 'a.c',
+				\  'lnum' : 30}
+				\ ])
+
+			" Place sign s1 in buffer a.c at line 40 and 50
+			" with auto-generated identifiers
+			let [n1, n2] = sign_place([
+				\ {'name' : 's1',
+				\  'buffer' : 'a.c',
+				\  'lnum' : 40},
+				\ {'name' : 's1',
+				\  'buffer' : 'a.c',
+				\  'lnum' : 50}
+				\ ])
+<
 sign_undefine([{name}])					*sign_undefine()*
+sign_undefine({list})
 		Deletes a previously defined sign {name}. This is similar to
 		the |:sign-undefine| command. If {name} is not supplied, then
 		deletes all the defined signs.
 
-		Returns 0 on success and -1 on failure.
+		The one argument {list} can be used to undefine a list of
+		signs. Each list item is the name of a sign.
+
+		Returns 0 on success and -1 on failure.  For the one argument
+		{list} call, returns a list of values one for each undefined
+		sign.
 
 		Examples: >
 			" Delete a sign named mySign
 			call sign_undefine("mySign")
 
+			" Delete signs 'sign1' and 'sign2'
+			call sign_undefine(["sign1", "sign2"])
+
 			" Delete all the signs
 			call sign_undefine()
 <
@@ -8857,6 +8946,32 @@ sign_unplace({group} [, {dict}])			*sign
 			" Remove all the placed signs from all the buffers
 			call sign_unplace('*')
 <
+sign_unplacelist({list})				*sign_unplacelist()*
+		Remove previously placed signs from one or more buffers.  This
+		is similar to the |sign_unplace()| function.
+
+		The {list} argument specifies the List of signs to remove.
+		Each list item is a dict with the following sign attributes:
+		    buffer	buffer name or number. For the accepted
+				values, see |bufname()|. If not specified,
+				then the specified sign is removed from all
+				the buffers.
+		    group	sign group name. If not specified or set to an
+				empty string, then the global sign group is
+				used. If set to '*', then all the groups
+				including the global group are used.
+		    id		sign identifier. If not specified, then all
+				the signs in the specified group are removed.
+
+		Returns a List where an entry is set to 0 if the corresponding
+		sign was successfully removed or -1 on failure.
+
+		Example: >
+			" Remove sign with id 10 from buffer a.vim and sign
+			" with id 20 from buffer b.vim
+			call sign_unplace([{'id' : 10, 'buffer' : "a.vim"},
+					\ {'id' : 20, 'buffer' : 'b.vim'}])
+<
 simplify({filename})					*simplify()*
 		Simplify the file name as much as possible without changing
 		the meaning.  Shortcuts (on MS-Windows) or symbolic links (on
@@ -9917,8 +10032,8 @@ timer_stop({timer})					*timer_stop()*
 
 timer_stopall()						*timer_stopall()*
 		Stop all timers.  The timer callbacks will no longer be
-		invoked.  Useful if some timers is misbehaving.  If there are
-		no timers there is no error.
+		invoked.  Useful if a timer is misbehaving.  If there are no
+		timers there is no error.
 
 		{only available when compiled with the |+timers| feature}
 
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -1015,8 +1015,10 @@ Signs:						*sign-functions*
 	sign_getplaced()	get a list of placed signs
 	sign_jump()		jump to a sign
 	sign_place()		place a sign
+	sign_placelist()	place a list of signs
 	sign_undefine()		undefine a sign
 	sign_unplace()		unplace a sign
+	sign_unplacelist()	unplace a list of signs
 
 Terminal window:				*terminal-functions*
 	term_start()		open a terminal window and run a job
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -888,8 +888,10 @@ static struct fst
     {"sign_getplaced",	0, 2, f_sign_getplaced},
     {"sign_jump",	3, 3, f_sign_jump},
     {"sign_place",	4, 5, f_sign_place},
+    {"sign_placelist",	1, 1, f_sign_placelist},
     {"sign_undefine",	0, 1, f_sign_undefine},
     {"sign_unplace",	1, 2, f_sign_unplace},
+    {"sign_unplacelist",	1, 2, f_sign_unplacelist},
 #endif
     {"simplify",	1, 1, f_simplify},
 #ifdef FEAT_FLOAT
--- a/src/proto/sign.pro
+++ b/src/proto/sign.pro
@@ -22,6 +22,8 @@ void f_sign_getdefined(typval_T *argvars
 void f_sign_getplaced(typval_T *argvars, typval_T *rettv);
 void f_sign_jump(typval_T *argvars, typval_T *rettv);
 void f_sign_place(typval_T *argvars, typval_T *rettv);
+void f_sign_placelist(typval_T *argvars, typval_T *rettv);
 void f_sign_undefine(typval_T *argvars, typval_T *rettv);
 void f_sign_unplace(typval_T *argvars, typval_T *rettv);
+void f_sign_unplacelist(typval_T *argvars, typval_T *rettv);
 /* vim: set ft=c : */
--- a/src/sign.c
+++ b/src/sign.c
@@ -442,7 +442,8 @@ buf_change_sign_type(
     buf_T	*buf,		// buffer to store sign in
     int		markId,		// sign ID
     char_u	*group,		// sign group
-    int		typenr)		// typenr of sign we are adding
+    int		typenr,		// typenr of sign we are adding
+    int		prio)		// sign priority
 {
     signlist_T	*sign;		// a sign in the signlist
 
@@ -451,6 +452,8 @@ buf_change_sign_type(
 	if (sign->id == markId && sign_in_group(sign, group))
 	{
 	    sign->typenr = typenr;
+	    sign->priority = prio;
+	    sign_sort_by_prio_on_line(buf, sign);
 	    return sign->lnum;
 	}
     }
@@ -1104,8 +1107,9 @@ sign_place(
 	// place a sign
 	buf_addsign(buf, *sign_id, sign_group, prio, lnum, sp->sn_typenr);
     else
-	// ":sign place {id} file={fname}": change sign type
-	lnum = buf_change_sign_type(buf, *sign_id, sign_group, sp->sn_typenr);
+	// ":sign place {id} file={fname}": change sign type and/or priority
+	lnum = buf_change_sign_type(buf, *sign_id, sign_group, sp->sn_typenr,
+								prio);
     if (lnum > 0)
     {
 	redraw_buf_line_later(buf, lnum);
@@ -2096,51 +2100,104 @@ set_context_in_sign_cmd(expand_T *xp, ch
 # endif
 
 /*
+ * Define a sign using the attributes in 'dict'. Returns 0 on success and -1 on
+ * failure.
+ */
+    static int
+sign_define_from_dict(char_u *name_arg, dict_T *dict)
+{
+    char_u	*name = NULL;
+    char_u	*icon = NULL;
+    char_u	*linehl = NULL;
+    char_u	*text = NULL;
+    char_u	*texthl = NULL;
+    int		retval = -1;
+
+    if (name_arg == NULL)
+    {
+	if (dict == NULL)
+	    return -1;
+	name = dict_get_string(dict, (char_u *)"name", TRUE);
+    }
+    else
+	name = vim_strsave(name_arg);
+    if (name == NULL || name[0] == NUL)
+	goto cleanup;
+    if (dict != NULL)
+    {
+	icon = dict_get_string(dict, (char_u *)"icon", TRUE);
+	linehl = dict_get_string(dict, (char_u *)"linehl", TRUE);
+	text = dict_get_string(dict, (char_u *)"text", TRUE);
+	texthl = dict_get_string(dict, (char_u *)"texthl", TRUE);
+    }
+
+    if (sign_define_by_name(name, icon, linehl, text, texthl) == OK)
+	retval = 0;
+
+cleanup:
+    vim_free(name);
+    vim_free(icon);
+    vim_free(linehl);
+    vim_free(text);
+    vim_free(texthl);
+
+    return retval;
+}
+
+/*
+ * Define multiple signs using attributes from list 'l' and store the return
+ * values in 'retlist'.
+ */
+    static void
+sign_define_multiple(list_T *l, list_T *retlist)
+{
+    listitem_T	*li;
+    int		retval;
+
+    for (li = l->lv_first; li != NULL; li = li->li_next)
+    {
+	retval = -1;
+	if (li->li_tv.v_type == VAR_DICT)
+	    retval = sign_define_from_dict(NULL, li->li_tv.vval.v_dict);
+	else
+	    emsg(_(e_dictreq));
+	list_append_number(retlist, retval);
+    }
+}
+
+/*
  * "sign_define()" function
  */
     void
 f_sign_define(typval_T *argvars, typval_T *rettv)
 {
     char_u	*name;
-    dict_T	*dict;
-    char_u	*icon = NULL;
-    char_u	*linehl = NULL;
-    char_u	*text = NULL;
-    char_u	*texthl = NULL;
 
+    if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN)
+    {
+	// Define multiple signs
+	if (rettv_list_alloc(rettv) != OK)
+	    return;
+
+	sign_define_multiple(argvars[0].vval.v_list, rettv->vval.v_list);
+	return;
+    }
+
+    // Define a single sign
     rettv->vval.v_number = -1;
 
     name = tv_get_string_chk(&argvars[0]);
     if (name == NULL)
 	return;
 
-    if (argvars[1].v_type != VAR_UNKNOWN)
+    if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_DICT)
     {
-	if (argvars[1].v_type != VAR_DICT)
-	{
-	    emsg(_(e_dictreq));
-	    return;
-	}
-
-	// sign attributes
-	dict = argvars[1].vval.v_dict;
-	if (dict_find(dict, (char_u *)"icon", -1) != NULL)
-	    icon = dict_get_string(dict, (char_u *)"icon", TRUE);
-	if (dict_find(dict, (char_u *)"linehl", -1) != NULL)
-	    linehl = dict_get_string(dict, (char_u *)"linehl", TRUE);
-	if (dict_find(dict, (char_u *)"text", -1) != NULL)
-	    text = dict_get_string(dict, (char_u *)"text", TRUE);
-	if (dict_find(dict, (char_u *)"texthl", -1) != NULL)
-	    texthl = dict_get_string(dict, (char_u *)"texthl", TRUE);
+	emsg(_(e_dictreq));
+	return;
     }
 
-    if (sign_define_by_name(name, icon, linehl, text, texthl) == OK)
-	rettv->vval.v_number = 0;
-
-    vim_free(icon);
-    vim_free(linehl);
-    vim_free(text);
-    vim_free(texthl);
+    rettv->vval.v_number = sign_define_from_dict(name,
+	    argvars[1].v_type == VAR_DICT ? argvars[1].vval.v_dict : NULL);
 }
 
 /*
@@ -2269,87 +2326,196 @@ cleanup:
 }
 
 /*
+ * Place a new sign using the values specified in dict 'dict'. Returns the sign
+ * identifier if successfully placed, otherwise returns 0.
+ */
+    static int
+sign_place_from_dict(
+	typval_T	*id_tv,
+	typval_T	*group_tv,
+	typval_T	*name_tv,
+	typval_T	*buf_tv,
+	dict_T		*dict)
+{
+    int		sign_id = 0;
+    char_u	*group = NULL;
+    char_u	*sign_name = NULL;
+    buf_T	*buf = NULL;
+    dictitem_T	*di;
+    linenr_T	lnum = 0;
+    int		prio = SIGN_DEF_PRIO;
+    int		notanum = FALSE;
+    int		ret_sign_id = -1;
+
+    // sign identifier
+    if (id_tv == NULL)
+    {
+	di = dict_find(dict, (char_u *)"id", -1);
+	if (di != NULL)
+	    id_tv = &di->di_tv;
+    }
+    if (id_tv == NULL)
+	sign_id = 0;
+    else
+    {
+	sign_id = tv_get_number_chk(id_tv, &notanum);
+	if (notanum)
+	    return -1;
+	if (sign_id < 0)
+	{
+	    emsg(_(e_invarg));
+	    return -1;
+	}
+    }
+
+    // sign group
+    if (group_tv == NULL)
+    {
+	di = dict_find(dict, (char_u *)"group", -1);
+	if (di != NULL)
+	    group_tv = &di->di_tv;
+    }
+    if (group_tv == NULL)
+	group = NULL;				// global group
+    else
+    {
+	group = tv_get_string_chk(group_tv);
+	if (group == NULL)
+	    goto cleanup;
+	if (group[0] == '\0')			// global sign group
+	    group = NULL;
+	else
+	{
+	    group = vim_strsave(group);
+	    if (group == NULL)
+		return -1;
+	}
+    }
+
+    // sign name
+    if (name_tv == NULL)
+    {
+	di = dict_find(dict, (char_u *)"name", -1);
+	if (di != NULL)
+	    name_tv = &di->di_tv;
+    }
+    if (name_tv == NULL)
+	goto cleanup;
+    sign_name = tv_get_string_chk(name_tv);
+    if (sign_name == NULL)
+	goto cleanup;
+
+    // buffer to place the sign
+    if (buf_tv == NULL)
+    {
+	di = dict_find(dict, (char_u *)"buffer", -1);
+	if (di != NULL)
+	    buf_tv = &di->di_tv;
+    }
+    if (buf_tv == NULL)
+	goto cleanup;
+    buf = get_buf_arg(buf_tv);
+    if (buf == NULL)
+	goto cleanup;
+
+    // line number of the sign
+    di = dict_find(dict, (char_u *)"lnum", -1);
+    if (di != NULL)
+    {
+	lnum = (int)tv_get_number_chk(&di->di_tv, &notanum);
+	if (notanum)
+	    goto cleanup;
+    }
+
+    // sign priority
+    di = dict_find(dict, (char_u *)"priority", -1);
+    if (di != NULL)
+    {
+	prio = (int)tv_get_number_chk(&di->di_tv, &notanum);
+	if (notanum)
+	    goto cleanup;
+    }
+
+    if (sign_place(&sign_id, group, sign_name, buf, lnum, prio) == OK)
+	ret_sign_id = sign_id;
+
+cleanup:
+    vim_free(group);
+
+    return ret_sign_id;
+}
+
+/*
  * "sign_place()" function
  */
     void
 f_sign_place(typval_T *argvars, typval_T *rettv)
 {
-    int		sign_id;
-    char_u	*group = NULL;
-    char_u	*sign_name;
-    buf_T	*buf;
-    dict_T	*dict;
-    dictitem_T	*di;
-    linenr_T	lnum = 0;
-    int		prio = SIGN_DEF_PRIO;
-    int		notanum = FALSE;
+    dict_T	*dict = NULL;
 
     rettv->vval.v_number = -1;
 
-    // Sign identifier
-    sign_id = (int)tv_get_number_chk(&argvars[0], &notanum);
-    if (notanum)
-	return;
-    if (sign_id < 0)
+    if (argvars[4].v_type != VAR_UNKNOWN
+	    && (argvars[4].v_type != VAR_DICT
+		|| ((dict = argvars[4].vval.v_dict) == NULL)))
     {
-	emsg(_(e_invarg));
+	emsg(_(e_dictreq));
 	return;
     }
 
-    // Sign group
-    group = tv_get_string_chk(&argvars[1]);
-    if (group == NULL)
+    rettv->vval.v_number = sign_place_from_dict(&argvars[0], &argvars[1],
+					&argvars[2], &argvars[3], dict);
+}
+
+/*
+ * "sign_placelist()" function.  Place multiple signs.
+ */
+    void
+f_sign_placelist(typval_T *argvars, typval_T *rettv)
+{
+    listitem_T	*li;
+    int		sign_id;
+
+    if (rettv_list_alloc(rettv) != OK)
 	return;
-    if (group[0] == '\0')
-	group = NULL;			// global sign group
-    else
+
+    if (argvars[0].v_type != VAR_LIST)
     {
-	group = vim_strsave(group);
-	if (group == NULL)
-	    return;
+	emsg(_(e_listreq));
+	return;
     }
 
-    // Sign name
-    sign_name = tv_get_string_chk(&argvars[2]);
-    if (sign_name == NULL)
-	goto cleanup;
-
-    // Buffer to place the sign
-    buf = get_buf_arg(&argvars[3]);
-    if (buf == NULL)
-	goto cleanup;
-
-    if (argvars[4].v_type != VAR_UNKNOWN)
+    // Process the List of sign attributes
+    for (li = argvars[0].vval.v_list->lv_first; li != NULL; li = li->li_next)
     {
-	if (argvars[4].v_type != VAR_DICT ||
-				((dict = argvars[4].vval.v_dict) == NULL))
-	{
+	sign_id = -1;
+	if (li->li_tv.v_type == VAR_DICT)
+	    sign_id = sign_place_from_dict(NULL, NULL, NULL, NULL,
+						li->li_tv.vval.v_dict);
+	else
 	    emsg(_(e_dictreq));
-	    goto cleanup;
-	}
+	list_append_number(rettv->vval.v_list, sign_id);
+    }
+}
 
-	// Line number where the sign is to be placed
-	if ((di = dict_find(dict, (char_u *)"lnum", -1)) != NULL)
-	{
-	    (void)tv_get_number_chk(&di->di_tv, &notanum);
-	    if (notanum)
-		goto cleanup;
-	    lnum = tv_get_lnum(&di->di_tv);
-	}
-	if ((di = dict_find(dict, (char_u *)"priority", -1)) != NULL)
-	{
-	    // Sign priority
-	    prio = (int)tv_get_number_chk(&di->di_tv, &notanum);
-	    if (notanum)
-		goto cleanup;
-	}
+/*
+ * Undefine multiple signs
+ */
+    static void
+sign_undefine_multiple(list_T *l, list_T *retlist)
+{
+    char_u	*name;
+    listitem_T	*li;
+    int		retval;
+
+    for (li = l->lv_first; li != NULL; li = li->li_next)
+    {
+	retval = -1;
+	name = tv_get_string_chk(&li->li_tv);
+	if (name != NULL && (sign_undefine_by_name(name) == OK))
+	    retval = 0;
+	list_append_number(retlist, retval);
     }
-
-    if (sign_place(&sign_id, group, sign_name, buf, lnum, prio) == OK)
-	rettv->vval.v_number = sign_id;
-
-cleanup:
-    vim_free(group);
 }
 
 /*
@@ -2360,6 +2526,16 @@ f_sign_undefine(typval_T *argvars, typva
 {
     char_u *name;
 
+    if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN)
+    {
+	// Undefine multiple signs
+	if (rettv_list_alloc(rettv) != OK)
+	    return;
+
+	sign_undefine_multiple(argvars[0].vval.v_list, rettv->vval.v_list);
+	return;
+    }
+
     rettv->vval.v_number = -1;
 
     if (argvars[0].v_type == VAR_UNKNOWN)
@@ -2381,16 +2557,78 @@ f_sign_undefine(typval_T *argvars, typva
 }
 
 /*
+ * Unplace the sign with attributes specified in 'dict'. Returns 0 on success
+ * and -1 on failure.
+ */
+    static int
+sign_unplace_from_dict(typval_T *group_tv, dict_T *dict)
+{
+    dictitem_T	*di;
+    int		sign_id = 0;
+    buf_T	*buf = NULL;
+    char_u	*group = NULL;
+    int		retval = -1;
+
+    // sign group
+    if (group_tv != NULL)
+	group = tv_get_string(group_tv);
+    else
+	group = dict_get_string(dict, (char_u *)"group", FALSE);
+    if (group != NULL)
+    {
+	if (group[0] == '\0')			// global sign group
+	    group = NULL;
+	else
+	{
+	    group = vim_strsave(group);
+	    if (group == NULL)
+		return -1;
+	}
+    }
+
+    if (dict != NULL)
+    {
+	if ((di = dict_find(dict, (char_u *)"buffer", -1)) != NULL)
+	{
+	    buf = get_buf_arg(&di->di_tv);
+	    if (buf == NULL)
+		goto cleanup;
+	}
+	if (dict_find(dict, (char_u *)"id", -1) != NULL)
+	{
+	    sign_id = dict_get_number(dict, (char_u *)"id");
+	    if (sign_id <= 0)
+	    {
+		emsg(_(e_invarg));
+		goto cleanup;
+	    }
+	}
+    }
+
+    if (buf == NULL)
+    {
+	// Delete the sign in all the buffers
+	retval = 0;
+	FOR_ALL_BUFFERS(buf)
+	    if (sign_unplace(sign_id, group, buf, 0) != OK)
+		retval = -1;
+    }
+    else if (sign_unplace(sign_id, group, buf, 0) == OK)
+	retval = 0;
+
+cleanup:
+    vim_free(group);
+
+    return retval;
+}
+
+/*
  * "sign_unplace()" function
  */
     void
 f_sign_unplace(typval_T *argvars, typval_T *rettv)
 {
-    dict_T	*dict;
-    dictitem_T	*di;
-    int		sign_id = 0;
-    buf_T	*buf = NULL;
-    char_u	*group = NULL;
+    dict_T	*dict = NULL;
 
     rettv->vval.v_number = -1;
 
@@ -2400,50 +2638,46 @@ f_sign_unplace(typval_T *argvars, typval
 	return;
     }
 
-    group = tv_get_string(&argvars[0]);
-    if (group[0] == '\0')
-	group = NULL;			// global sign group
-    else
-    {
-	group = vim_strsave(group);
-	if (group == NULL)
-	    return;
-    }
-
     if (argvars[1].v_type != VAR_UNKNOWN)
     {
 	if (argvars[1].v_type != VAR_DICT)
 	{
 	    emsg(_(e_dictreq));
-	    goto cleanup;
+	    return;
 	}
 	dict = argvars[1].vval.v_dict;
-
-	if ((di = dict_find(dict, (char_u *)"buffer", -1)) != NULL)
-	{
-	    buf = get_buf_arg(&di->di_tv);
-	    if (buf == NULL)
-		goto cleanup;
-	}
-	if (dict_find(dict, (char_u *)"id", -1) != NULL)
-	    sign_id = dict_get_number(dict, (char_u *)"id");
     }
 
-    if (buf == NULL)
+    rettv->vval.v_number = sign_unplace_from_dict(&argvars[0], dict);
+}
+
+/*
+ * "sign_unplacelist()" function
+ */
+    void
+f_sign_unplacelist(typval_T *argvars, typval_T *rettv)
+{
+    listitem_T	*li;
+    int		retval;
+
+    if (rettv_list_alloc(rettv) != OK)
+	return;
+
+    if (argvars[0].v_type != VAR_LIST)
     {
-	// Delete the sign in all the buffers
-	FOR_ALL_BUFFERS(buf)
-	    if (sign_unplace(sign_id, group, buf, 0) == OK)
-		rettv->vval.v_number = 0;
-    }
-    else
-    {
-	if (sign_unplace(sign_id, group, buf, 0) == OK)
-	    rettv->vval.v_number = 0;
+	emsg(_(e_listreq));
+	return;
     }
 
-cleanup:
-    vim_free(group);
+    for (li = argvars[0].vval.v_list->lv_first; li != NULL; li = li->li_next)
+    {
+	retval = -1;
+	if (li->li_tv.v_type == VAR_DICT)
+	    retval = sign_unplace_from_dict(NULL, li->li_tv.vval.v_dict);
+	else
+	    emsg(_(e_dictreq));
+	list_append_number(rettv->vval.v_list, retval);
+    }
 }
 
 #endif /* FEAT_SIGNS */
--- a/src/testdir/test_signs.vim
+++ b/src/testdir/test_signs.vim
@@ -412,7 +412,7 @@ func Test_sign_funcs()
   " Tests for invalid arguments to sign_define()
   call assert_fails('call sign_define("sign4", {"text" : "===>"})', 'E239:')
   call assert_fails('call sign_define("sign5", {"text" : ""})', 'E239:')
-  call assert_fails('call sign_define([])', 'E730:')
+  call assert_fails('call sign_define({})', 'E731:')
   call assert_fails('call sign_define("sign6", [])', 'E715:')
 
   " Tests for sign_getdefined()
@@ -441,7 +441,7 @@ func Test_sign_funcs()
   call assert_fails('call sign_place([], "", "mySign", 1)', 'E745:')
   call assert_fails('call sign_place(5, "", "mySign", -1)', 'E158:')
   call assert_fails('call sign_place(-1, "", "sign1", "Xsign", [])',
-	      \ 'E474:')
+	      \ 'E715:')
   call assert_fails('call sign_place(-1, "", "sign1", "Xsign",
 	      \ {"lnum" : 30})', 'E474:')
   call assert_fails('call sign_place(10, "", "xsign1x", "Xsign",
@@ -501,11 +501,21 @@ func Test_sign_funcs()
 	      \ {'id' : 20, 'buffer' : 200})", 'E158:')
   call assert_fails("call sign_unplace('g1', 'mySign')", 'E715:')
 
+  call sign_unplace('*')
+
+  " Test for modifying a placed sign
+  call assert_equal(15, sign_place(15, '', 'sign1', 'Xsign', {'lnum' : 20}))
+  call assert_equal(15, sign_place(15, '', 'sign2', 'Xsign'))
+  call assert_equal([{'bufnr' : bufnr(''), 'signs' :
+	      \ [{'id' : 15, 'group' : '', 'lnum' : 20, 'name' : 'sign2',
+	      \ 'priority' : 10}]}],
+	      \ sign_getplaced())
+
   " Tests for sign_undefine()
   call assert_equal(0, sign_undefine("sign1"))
   call assert_equal([], sign_getdefined("sign1"))
   call assert_fails('call sign_undefine("none")', 'E155:')
-  call assert_fails('call sign_undefine([])', 'E730:')
+  call assert_fails('call sign_undefine({})', 'E731:')
 
   call delete("Xsign")
   call sign_unplace('*')
@@ -631,7 +641,7 @@ func Test_sign_group()
   call assert_equal([], sign_getplaced(bnum, {'group' : '*'})[0].signs)
 
   " Error case
-  call assert_fails("call sign_unplace([])", 'E474:')
+  call assert_fails("call sign_unplace({})", 'E474:')
 
   " Place a sign in the global group and try to delete it using a group
   call assert_equal(5, sign_place(5, '', 'sign1', bnum, {'lnum' : 10}))
@@ -1117,8 +1127,8 @@ func Test_sign_unplace()
   call delete("Xsign2")
 endfunc
 
-" Tests for auto-generating the sign identifier
-func Test_sign_id_autogen()
+" Tests for auto-generating the sign identifier.
+func Test_aaa_sign_id_autogen()
   enew | only
   call sign_unplace('*')
   call sign_undefine()
@@ -1843,3 +1853,113 @@ func Test_sign_numcol()
   set number&
   enew!  | close
 endfunc
+
+" Test for managing multiple signs using the sign functions
+func Test_sign_funcs_multi()
+  call writefile(repeat(["Sun is shining"], 30), "Xsign")
+  edit Xsign
+  let bnum = bufnr('')
+
+  " Define multiple signs at once
+  call assert_equal([0, 0, 0, 0], sign_define([
+	      \ {'name' : 'sign1', 'text' : '=>', 'linehl' : 'Search',
+	      \ 'texthl' : 'Search'},
+	      \ {'name' : 'sign2', 'text' : '=>', 'linehl' : 'Search',
+	      \ 'texthl' : 'Search'},
+	      \ {'name' : 'sign3', 'text' : '=>', 'linehl' : 'Search',
+	      \ 'texthl' : 'Search'},
+	      \ {'name' : 'sign4', 'text' : '=>', 'linehl' : 'Search',
+	      \ 'texthl' : 'Search'}]))
+
+  " Negative cases for sign_define()
+  call assert_equal([], sign_define([]))
+  call assert_equal([-1], sign_define([{}]))
+  call assert_fails('call sign_define([6])', 'E715:')
+  call assert_fails('call sign_define(["abc"])', 'E715:')
+  call assert_fails('call sign_define([[]])', 'E715:')
+
+  " Place multiple signs at once with specific sign identifier
+  let l = sign_placelist([{'id' : 1, 'group' : 'g1', 'name' : 'sign1',
+	      \ 'buffer' : 'Xsign', 'lnum' : 11, 'priority' : 50},
+	      \ {'id' : 2, 'group' : 'g2', 'name' : 'sign2',
+	      \ 'buffer' : 'Xsign', 'lnum' : 11, 'priority' : 100},
+	      \ {'id' : 3, 'group' : '', 'name' : 'sign3',
+	      \ 'buffer' : 'Xsign', 'lnum' : 11}])
+  call assert_equal([1, 2, 3], l)
+  let s = sign_getplaced('Xsign', {'group' : '*'})
+  call assert_equal([
+	      \ {'id' : 2, 'name' : 'sign2', 'lnum' : 11,
+	      \ 'group' : 'g2', 'priority' : 100},
+	      \ {'id' : 1, 'name' : 'sign1', 'lnum' : 11,
+	      \ 'group' : 'g1', 'priority' : 50},
+	      \ {'id' : 3, 'name' : 'sign3', 'lnum' : 11,
+	      \ 'group' : '', 'priority' : 10}], s[0].signs)
+
+  call sign_unplace('*')
+
+  " Place multiple signs at once with auto-generated sign identifier
+  call assert_equal([1, 1, 5], sign_placelist([
+	      \ {'group' : 'g1', 'name' : 'sign1',
+	      \ 'buffer' : 'Xsign', 'lnum' : 11},
+	      \ {'group' : 'g2', 'name' : 'sign2',
+	      \ 'buffer' : 'Xsign', 'lnum' : 11},
+	      \ {'group' : '', 'name' : 'sign3',
+	      \ 'buffer' : 'Xsign', 'lnum' : 11}]))
+  let s = sign_getplaced('Xsign', {'group' : '*'})
+  call assert_equal([
+	      \ {'id' : 5, 'name' : 'sign3', 'lnum' : 11,
+	      \ 'group' : '', 'priority' : 10},
+	      \ {'id' : 1, 'name' : 'sign2', 'lnum' : 11,
+	      \ 'group' : 'g2', 'priority' : 10},
+	      \ {'id' : 1, 'name' : 'sign1', 'lnum' : 11,
+	      \ 'group' : 'g1', 'priority' : 10}], s[0].signs)
+
+  " Change an existing sign without specifying the group
+  call assert_equal([5], sign_placelist([
+	      \ {'id' : 5, 'name' : 'sign1', 'buffer' : 'Xsign'}]))
+  let s = sign_getplaced('Xsign', {'id' : 5, 'group' : ''})
+  call assert_equal([{'id' : 5, 'name' : 'sign1', 'lnum' : 11,
+	      \ 'group' : '', 'priority' : 10}], s[0].signs)
+
+  " Place sign without a sign name
+  call assert_equal([-1], sign_placelist([{'id' : 10, 'buffer' : 'Xsign',
+	      \ 'lnum' : 12, 'group' : ''}]))
+
+  " Place sign without a buffer
+  call assert_equal([-1], sign_placelist([{'id' : 10, 'name' : 'sign1',
+	      \ 'lnum' : 12, 'group' : ''}]))
+
+  " Invalid arguments
+  call assert_equal([], sign_placelist([]))
+  call assert_fails('call sign_placelist({})', "E714:")
+  call assert_fails('call sign_placelist([[]])', "E715:")
+  call assert_fails('call sign_placelist(["abc"])', "E715:")
+  call assert_fails('call sign_placelist([100])', "E715:")
+
+  " Unplace multiple signs
+  call assert_equal([0, 0, 0], sign_unplacelist([{'id' : 5},
+	      \ {'id' : 1, 'group' : 'g1'}, {'id' : 1, 'group' : 'g2'}]))
+
+  " Invalid arguments
+  call assert_equal([], sign_unplacelist([]))
+  call assert_fails('call sign_unplacelist({})', "E714:")
+  call assert_fails('call sign_unplacelist([[]])', "E715:")
+  call assert_fails('call sign_unplacelist(["abc"])', "E715:")
+  call assert_fails('call sign_unplacelist([100])', "E715:")
+  call assert_fails("call sign_unplacelist([{'id' : -1}])", 'E474')
+
+  call assert_equal([0, 0, 0, 0],
+	      \ sign_undefine(['sign1', 'sign2', 'sign3', 'sign4']))
+  call assert_equal([], sign_getdefined())
+
+  " Invalid arguments
+  call assert_equal([], sign_undefine([]))
+  call assert_fails('call sign_undefine([[]])', 'E730:')
+  call assert_fails('call sign_undefine([{}])', 'E731:')
+  call assert_fails('call sign_undefine(["1abc2"])', 'E155:')
+
+  call sign_unplace('*')
+  call sign_undefine()
+  enew!
+  call delete("Xsign")
+endfunc
--- a/src/version.c
+++ b/src/version.c
@@ -778,6 +778,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1682,
+/**/
     1681,
 /**/
     1680,