diff src/ex_cmds.c @ 15209:3a99b2e6d136 v8.1.0614

patch 8.1.0614: placing signs can be complicated commit https://github.com/vim/vim/commit/162b71479bd4dcdb3a2ef9198a1444f6f99e6843 Author: Bram Moolenaar <Bram@vim.org> Date: Fri Dec 21 15:17:36 2018 +0100 patch 8.1.0614: placing signs can be complicated Problem: Placing signs can be complicated. Solution: Add functions for defining and placing signs. Introduce a group name to avoid different plugins using the same signs. (Yegappan Lakshmanan, closes #3652)
author Bram Moolenaar <Bram@vim.org>
date Fri, 21 Dec 2018 15:30:07 +0100
parents b63787182765
children de63593896b3
line wrap: on
line diff
--- a/src/ex_cmds.c
+++ b/src/ex_cmds.c
@@ -7644,6 +7644,289 @@ sign_cmd_idx(
 }
 
 /*
+ * Find a sign by name. Also returns pointer to the previous sign.
+ */
+    static sign_T *
+sign_find(char_u *name, sign_T **sp_prev)
+{
+    sign_T *sp;
+
+    if (sp_prev != NULL)
+	*sp_prev = NULL;
+    for (sp = first_sign; sp != NULL; sp = sp->sn_next)
+    {
+	if (STRCMP(sp->sn_name, name) == 0)
+	    break;
+	if (sp_prev != NULL)
+	    *sp_prev = sp;
+    }
+
+    return sp;
+}
+
+/*
+ * Define a new sign or update an existing sign
+ */
+    int
+sign_define_by_name(
+	char_u	*name,
+	char_u	*icon,
+	char_u	*linehl,
+	char_u	*text,
+	char_u	*texthl)
+{
+    sign_T	*sp_prev;
+    sign_T	*sp;
+
+    sp = sign_find(name, &sp_prev);
+    if (sp == NULL)
+    {
+	sign_T	*lp;
+	int	start = next_sign_typenr;
+
+	// Allocate a new sign.
+	sp = (sign_T *)alloc_clear_id((unsigned)sizeof(sign_T),
+						aid_sign_define_by_name);
+	if (sp == NULL)
+	    return FAIL;
+
+	// Check that next_sign_typenr is not already being used.
+	// This only happens after wrapping around.  Hopefully
+	// another one got deleted and we can use its number.
+	for (lp = first_sign; lp != NULL; )
+	{
+	    if (lp->sn_typenr == next_sign_typenr)
+	    {
+		++next_sign_typenr;
+		if (next_sign_typenr == MAX_TYPENR)
+		    next_sign_typenr = 1;
+		if (next_sign_typenr == start)
+		{
+		    vim_free(sp);
+		    EMSG(_("E612: Too many signs defined"));
+		    return FAIL;
+		}
+		lp = first_sign;  // start all over
+		continue;
+	    }
+	    lp = lp->sn_next;
+	}
+
+	sp->sn_typenr = next_sign_typenr;
+	if (++next_sign_typenr == MAX_TYPENR)
+	    next_sign_typenr = 1; // wrap around
+
+	sp->sn_name = vim_strsave(name);
+	if (sp->sn_name == NULL)  // out of memory
+	{
+	    vim_free(sp);
+	    return FAIL;
+	}
+
+	// add the new sign to the list of signs
+	if (sp_prev == NULL)
+	    first_sign = sp;
+	else
+	    sp_prev->sn_next = sp;
+    }
+
+    // set values for a defined sign.
+    if (icon != NULL)
+    {
+	vim_free(sp->sn_icon);
+	sp->sn_icon = vim_strsave(icon);
+	backslash_halve(sp->sn_icon);
+# ifdef FEAT_SIGN_ICONS
+	if (gui.in_use)
+	{
+	    out_flush();
+	    if (sp->sn_image != NULL)
+		gui_mch_destroy_sign(sp->sn_image);
+	    sp->sn_image = gui_mch_register_sign(sp->sn_icon);
+	}
+# endif
+    }
+
+    if (text != NULL)
+    {
+	char_u	*s;
+	char_u	*endp;
+	int	cells;
+	int	len;
+
+	endp = text + (int)STRLEN(text);
+	for (s = text; s + 1 < endp; ++s)
+	    if (*s == '\\')
+	    {
+		// Remove a backslash, so that it is possible
+		// to use a space.
+		STRMOVE(s, s + 1);
+		--endp;
+	    }
+# ifdef FEAT_MBYTE
+	// Count cells and check for non-printable chars
+	if (has_mbyte)
+	{
+	    cells = 0;
+	    for (s = text; s < endp; s += (*mb_ptr2len)(s))
+	    {
+		if (!vim_isprintc((*mb_ptr2char)(s)))
+		    break;
+		cells += (*mb_ptr2cells)(s);
+	    }
+	}
+	else
+# endif
+	{
+	    for (s = text; s < endp; ++s)
+		if (!vim_isprintc(*s))
+		    break;
+	    cells = (int)(s - text);
+	}
+	// Currently must be one or two display cells
+	if (s != endp || cells < 1 || cells > 2)
+	{
+	    EMSG2(_("E239: Invalid sign text: %s"), text);
+	    return FAIL;
+	}
+
+	vim_free(sp->sn_text);
+	// Allocate one byte more if we need to pad up
+	// with a space.
+	len = (int)(endp - text + ((cells == 1) ? 1 : 0));
+	sp->sn_text = vim_strnsave(text, len);
+
+	if (sp->sn_text != NULL && cells == 1)
+	    STRCPY(sp->sn_text + len - 1, " ");
+    }
+
+    if (linehl != NULL)
+	sp->sn_line_hl = syn_check_group(linehl, (int)STRLEN(linehl));
+
+    if (texthl != NULL)
+	sp->sn_text_hl = syn_check_group(texthl, (int)STRLEN(texthl));
+
+    return OK;
+}
+
+/*
+ * Free the sign specified by 'name'.
+ */
+    int
+sign_undefine_by_name(char_u *name)
+{
+    sign_T	*sp_prev;
+    sign_T	*sp;
+
+    sp = sign_find(name, &sp_prev);
+    if (sp == NULL)
+    {
+	EMSG2(_("E155: Unknown sign: %s"), name);
+	return FAIL;
+    }
+    sign_undefine(sp, sp_prev);
+
+    return OK;
+}
+
+/*
+ * List the signs matching 'name'
+ */
+    static void
+sign_list_by_name(char_u *name)
+{
+    sign_T	*sp;
+
+    sp = sign_find(name, NULL);
+    if (sp != NULL)
+	sign_list_defined(sp);
+    else
+	EMSG2(_("E155: Unknown sign: %s"), name);
+}
+
+/*
+ * Place a sign at the specifed file location or update a sign.
+ */
+    int
+sign_place(
+	int		*sign_id,
+	char_u		*sign_group,
+	char_u		*sign_name,
+	buf_T		*buf,
+	linenr_T	lnum,
+	int		prio)
+{
+    sign_T	*sp;
+
+    // Check for reserved character '*' in group name
+    if (sign_group != NULL && (*sign_group == '*' || *sign_group == '\0'))
+	return FAIL;
+
+    for (sp = first_sign; sp != NULL; sp = sp->sn_next)
+	if (STRCMP(sp->sn_name, sign_name) == 0)
+	    break;
+    if (sp == NULL)
+    {
+	EMSG2(_("E155: Unknown sign: %s"), sign_name);
+	return FAIL;
+    }
+    if (*sign_id == 0)
+    {
+	// Allocate a new sign id
+	int		id = 1;
+	signlist_T	*sign;
+
+	while ((sign = buf_getsign_with_id(buf, id, sign_group)) != NULL)
+	    id++;
+
+	*sign_id = id;
+    }
+
+    if (lnum > 0)
+	// ":sign place {id} line={lnum} name={name} file={fname}":
+	// 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);
+    if (lnum > 0)
+	update_debug_sign(buf, lnum);
+    else
+    {
+	EMSG2(_("E885: Not possible to change sign %s"), sign_name);
+	return FAIL;
+    }
+
+    return OK;
+}
+
+/*
+ * Unplace the specified sign
+ */
+    int
+sign_unplace(int sign_id, char_u *sign_group, buf_T *buf)
+{
+    if (sign_id == 0)
+    {
+	// Delete all the signs in the specified buffer
+	redraw_buf_later(buf, NOT_VALID);
+	buf_delete_signs(buf, sign_group);
+    }
+    else
+    {
+	linenr_T	lnum;
+
+	// Delete only the specified signs
+	lnum = buf_delsign(buf, sign_id, sign_group);
+	if (lnum == 0)
+	    return FAIL;
+	update_debug_sign(buf, lnum);
+    }
+
+    return OK;
+}
+
+/*
  * ":sign" command
  */
     void
@@ -7653,7 +7936,6 @@ ex_sign(exarg_T *eap)
     char_u	*p;
     int		idx;
     sign_T	*sp;
-    sign_T	*sp_prev;
     buf_T	*buf = NULL;
 
     /* Parse the subcommand. */
@@ -7681,6 +7963,12 @@ ex_sign(exarg_T *eap)
 	    EMSG(_("E156: Missing sign name"));
 	else
 	{
+	    char_u	*name;
+	    char_u	*icon = NULL;
+	    char_u	*text = NULL;
+	    char_u	*linehl = NULL;
+	    char_u	*texthl = NULL;
+
 	    /* Isolate the sign name.  If it's a number skip leading zeroes,
 	     * so that "099" and "99" are the same sign.  But keep "0". */
 	    p = skiptowhite(arg);
@@ -7688,66 +7976,13 @@ ex_sign(exarg_T *eap)
 		*p++ = NUL;
 	    while (arg[0] == '0' && arg[1] != NUL)
 		++arg;
-
-	    sp_prev = NULL;
-	    for (sp = first_sign; sp != NULL; sp = sp->sn_next)
-	    {
-		if (STRCMP(sp->sn_name, arg) == 0)
-		    break;
-		sp_prev = sp;
-	    }
+	    name = vim_strsave(arg);
+
 	    if (idx == SIGNCMD_DEFINE)
 	    {
+		int failed = FALSE;
+
 		/* ":sign define {name} ...": define a sign */
-		if (sp == NULL)
-		{
-		    sign_T	*lp;
-		    int		start = next_sign_typenr;
-
-		    /* Allocate a new sign. */
-		    sp = (sign_T *)alloc_clear((unsigned)sizeof(sign_T));
-		    if (sp == NULL)
-			return;
-
-		    /* Check that next_sign_typenr is not already being used.
-		     * This only happens after wrapping around.  Hopefully
-		     * another one got deleted and we can use its number. */
-		    for (lp = first_sign; lp != NULL; )
-		    {
-			if (lp->sn_typenr == next_sign_typenr)
-			{
-			    ++next_sign_typenr;
-			    if (next_sign_typenr == MAX_TYPENR)
-				next_sign_typenr = 1;
-			    if (next_sign_typenr == start)
-			    {
-				vim_free(sp);
-				EMSG(_("E612: Too many signs defined"));
-				return;
-			    }
-			    lp = first_sign;  /* start all over */
-			    continue;
-			}
-			lp = lp->sn_next;
-		    }
-
-		    sp->sn_typenr = next_sign_typenr;
-		    if (++next_sign_typenr == MAX_TYPENR)
-			next_sign_typenr = 1; /* wrap around */
-
-		    sp->sn_name = vim_strsave(arg);
-		    if (sp->sn_name == NULL)  /* out of memory */
-		    {
-			vim_free(sp);
-			return;
-		    }
-
-		    /* add the new sign to the list of signs */
-		    if (sp_prev == NULL)
-			first_sign = sp;
-		    else
-			sp_prev->sn_next = sp;
-		}
 
 		/* set values for a defined sign. */
 		for (;;)
@@ -7759,96 +7994,48 @@ ex_sign(exarg_T *eap)
 		    if (STRNCMP(arg, "icon=", 5) == 0)
 		    {
 			arg += 5;
-			vim_free(sp->sn_icon);
-			sp->sn_icon = vim_strnsave(arg, (int)(p - arg));
-			backslash_halve(sp->sn_icon);
-# ifdef FEAT_SIGN_ICONS
-			if (gui.in_use)
-			{
-			    out_flush();
-			    if (sp->sn_image != NULL)
-				gui_mch_destroy_sign(sp->sn_image);
-			    sp->sn_image = gui_mch_register_sign(sp->sn_icon);
-			}
-# endif
+			icon = vim_strnsave(arg, (int)(p - arg));
 		    }
 		    else if (STRNCMP(arg, "text=", 5) == 0)
 		    {
-			char_u	*s;
-			int	cells;
-			int	len;
-
 			arg += 5;
-			for (s = arg; s + 1 < p; ++s)
-			    if (*s == '\\')
-			    {
-				// Remove a backslash, so that it is possible
-				// to use a space.
-				STRMOVE(s, s + 1);
-				--p;
-			    }
-# ifdef FEAT_MBYTE
-			/* Count cells and check for non-printable chars */
-			if (has_mbyte)
-			{
-			    cells = 0;
-			    for (s = arg; s < p; s += (*mb_ptr2len)(s))
-			    {
-				if (!vim_isprintc((*mb_ptr2char)(s)))
-				    break;
-				cells += (*mb_ptr2cells)(s);
-			    }
-			}
-			else
-# endif
-			{
-			    for (s = arg; s < p; ++s)
-				if (!vim_isprintc(*s))
-				    break;
-			    cells = (int)(s - arg);
-			}
-			/* Currently must be one or two display cells */
-			if (s != p || cells < 1 || cells > 2)
-			{
-			    *p = NUL;
-			    EMSG2(_("E239: Invalid sign text: %s"), arg);
-			    return;
-			}
-
-			vim_free(sp->sn_text);
-			/* Allocate one byte more if we need to pad up
-			 * with a space. */
-			len = (int)(p - arg + ((cells == 1) ? 1 : 0));
-			sp->sn_text = vim_strnsave(arg, len);
-
-			if (sp->sn_text != NULL && cells == 1)
-			    STRCPY(sp->sn_text + len - 1, " ");
+			text = vim_strnsave(arg, (int)(p - arg));
 		    }
 		    else if (STRNCMP(arg, "linehl=", 7) == 0)
 		    {
 			arg += 7;
-			sp->sn_line_hl = syn_check_group(arg, (int)(p - arg));
+			linehl = vim_strnsave(arg, (int)(p - arg));
 		    }
 		    else if (STRNCMP(arg, "texthl=", 7) == 0)
 		    {
 			arg += 7;
-			sp->sn_text_hl = syn_check_group(arg, (int)(p - arg));
+			texthl = vim_strnsave(arg, (int)(p - arg));
 		    }
 		    else
 		    {
 			EMSG2(_(e_invarg2), arg);
-			return;
+			failed = TRUE;
+			break;
 		    }
 		}
+
+		if (!failed)
+		    sign_define_by_name(name, icon, linehl, text, texthl);
+
+		vim_free(icon);
+		vim_free(text);
+		vim_free(linehl);
+		vim_free(texthl);
 	    }
-	    else if (sp == NULL)
-		EMSG2(_("E155: Unknown sign: %s"), arg);
 	    else if (idx == SIGNCMD_LIST)
 		/* ":sign list {name}" */
-		sign_list_defined(sp);
+		sign_list_by_name(name);
 	    else
 		/* ":sign undefine {name}" */
-		sign_undefine(sp, sp_prev);
+		sign_undefine_by_name(name);
+
+	    vim_free(name);
+	    return;
 	}
     }
     else
@@ -7856,24 +8043,24 @@ ex_sign(exarg_T *eap)
 	int		id = -1;
 	linenr_T	lnum = -1;
 	char_u		*sign_name = NULL;
+	char_u		*group = NULL;
+	int		prio = SIGN_DEF_PRIO;
 	char_u		*arg1;
+	int		bufarg = FALSE;
 
 	if (*arg == NUL)
 	{
 	    if (idx == SIGNCMD_PLACE)
 	    {
 		/* ":sign place": list placed signs in all buffers */
-		sign_list_placed(NULL);
+		sign_list_placed(NULL, NULL);
 	    }
 	    else if (idx == SIGNCMD_UNPLACE)
 	    {
 		/* ":sign unplace": remove placed sign at cursor */
 		id = buf_findsign_id(curwin->w_buffer, curwin->w_cursor.lnum);
 		if (id > 0)
-		{
-		    buf_delsign(curwin->w_buffer, id);
-		    update_debug_sign(curwin->w_buffer, curwin->w_cursor.lnum);
-		}
+		    sign_unplace(id, NULL, curwin->w_buffer);
 		else
 		    EMSG(_("E159: Missing sign number"));
 	    }
@@ -7906,18 +8093,17 @@ ex_sign(exarg_T *eap)
 		{
 		    /* ":sign unplace {id}": remove placed sign by number */
 		    FOR_ALL_BUFFERS(buf)
-			if ((lnum = buf_delsign(buf, id)) != 0)
-			    update_debug_sign(buf, lnum);
+			sign_unplace(id, NULL, buf);
 		    return;
 		}
 	    }
 	}
 
 	/*
-	 * Check for line={lnum} name={name} and file={fname} or buffer={nr}.
-	 * Leave "arg" pointing to {fname}.
+	 * Check for line={lnum} name={name} group={group} priority={prio}
+	 * and file={fname} or buffer={nr}.  Leave "arg" pointing to {fname}.
 	 */
-	for (;;)
+	while (*arg != NUL)
 	{
 	    if (STRNCMP(arg, "line=", 5) == 0)
 	    {
@@ -7945,10 +8131,25 @@ ex_sign(exarg_T *eap)
 		while (sign_name[0] == '0' && sign_name[1] != NUL)
 		    ++sign_name;
 	    }
+	    else if (STRNCMP(arg, "group=", 6) == 0)
+	    {
+		arg += 6;
+		group = arg;
+		arg = skiptowhite(arg);
+		if (*arg != NUL)
+		    *arg++ = NUL;
+	    }
+	    else if (STRNCMP(arg, "priority=", 9) == 0)
+	    {
+		arg += 9;
+		prio = atoi((char *)arg);
+		arg = skiptowhite(arg);
+	    }
 	    else if (STRNCMP(arg, "file=", 5) == 0)
 	    {
 		arg += 5;
-		buf = buflist_findname(arg);
+		buf = buflist_findname_exp(arg);
+		bufarg = TRUE;
 		break;
 	    }
 	    else if (STRNCMP(arg, "buffer=", 7) == 0)
@@ -7957,6 +8158,7 @@ ex_sign(exarg_T *eap)
 		buf = buflist_findnr((int)getdigits(&arg));
 		if (*skipwhite(arg) != NUL)
 		    EMSG(_(e_trailing));
+		bufarg = TRUE;
 		break;
 	    }
 	    else
@@ -7967,24 +8169,33 @@ ex_sign(exarg_T *eap)
 	    arg = skipwhite(arg);
 	}
 
-	if (buf == NULL)
+	if ((!bufarg && group == NULL) || (group != NULL && *group == '\0'))
+	{
+	    // File or buffer is not specified or an empty group is used
+	    EMSG(_(e_invarg));
+	    return;
+	}
+
+	if (bufarg && buf == NULL)
 	{
 	    EMSG2(_("E158: Invalid buffer name: %s"), arg);
 	}
 	else if (id <= 0 && !(idx == SIGNCMD_UNPLACE && id == -2))
 	{
-	    if (lnum >= 0 || sign_name != NULL)
+	    if ((group == NULL) && (lnum >= 0 || sign_name != NULL))
 		EMSG(_(e_invarg));
 	    else
-		/* ":sign place file={fname}": list placed signs in one file */
-		sign_list_placed(buf);
+		// ":sign place file={fname}": list placed signs in one file
+		// ":sign place group={group} file={fname}"
+		// ":sign place group=* file={fname}"
+		sign_list_placed(buf, group);
 	}
 	else if (idx == SIGNCMD_JUMP)
 	{
 	    /* ":sign jump {id} file={fname}" */
 	    if (lnum >= 0 || sign_name != NULL)
 		EMSG(_(e_invarg));
-	    else if ((lnum = buf_findsign(buf, id)) > 0)
+	    else if ((lnum = buf_findsign(buf, id, group)) > 0)
 	    {				/* goto a sign ... */
 		if (buf_jump_open_win(buf) != NULL)
 		{			/* ... in a current window */
@@ -8021,45 +8232,164 @@ ex_sign(exarg_T *eap)
 		EMSG(_(e_invarg));
 	    else if (id == -2)
 	    {
-		/* ":sign unplace * file={fname}" */
-		redraw_buf_later(buf, NOT_VALID);
-		buf_delete_signs(buf);
+		if (buf != NULL)
+		    // ":sign unplace * file={fname}"
+		    sign_unplace(0, group, buf);
+		else
+		    // ":sign unplace * group=*": remove all placed signs
+		    FOR_ALL_BUFFERS(buf)
+			if (buf->b_signlist != NULL)
+			    buf_delete_signs(buf, group);
 	    }
 	    else
 	    {
-		/* ":sign unplace {id} file={fname}" */
-		lnum = buf_delsign(buf, id);
-		update_debug_sign(buf, lnum);
+		if (buf != NULL)
+		    // ":sign unplace {id} file={fname}"
+		    // ":sign unplace {id} group={group} file={fname}"
+		    // ":sign unplace {id} group=* file={fname}"
+		    sign_unplace(id, group, buf);
+		else
+		    // ":sign unplace {id} group={group}":
+		    // ":sign unplace {id} group=*":
+		    //     remove all placed signs in this group.
+		    FOR_ALL_BUFFERS(buf)
+			if (buf->b_signlist != NULL)
+			    sign_unplace(id, group, buf);
 	    }
 	}
 	    /* idx == SIGNCMD_PLACE */
-	else if (sign_name != NULL)
-	{
-	    for (sp = first_sign; sp != NULL; sp = sp->sn_next)
-		if (STRCMP(sp->sn_name, sign_name) == 0)
-		    break;
-	    if (sp == NULL)
-	    {
-		EMSG2(_("E155: Unknown sign: %s"), sign_name);
-		return;
-	    }
-	    if (lnum > 0)
-		/* ":sign place {id} line={lnum} name={name} file={fname}":
-		 * place a sign */
-		buf_addsign(buf, id, lnum, sp->sn_typenr);
-	    else
-		/* ":sign place {id} file={fname}": change sign type */
-		lnum = buf_change_sign_type(buf, id, sp->sn_typenr);
-	    if (lnum > 0)
-		update_debug_sign(buf, lnum);
-	    else
-		EMSG2(_("E885: Not possible to change sign %s"), sign_name);
-	}
+	else if (sign_name != NULL && buf != NULL)
+	    sign_place(&id, group, sign_name, buf, lnum, prio);
 	else
 	    EMSG(_(e_invarg));
     }
 }
 
+/*
+ * Return information about a specified sign
+ */
+    static void
+sign_getinfo(sign_T *sp, dict_T *retdict)
+{
+    char_u	*p;
+
+    dict_add_string(retdict, "name", (char_u *)sp->sn_name);
+    if (sp->sn_icon != NULL)
+	dict_add_string(retdict, "icon", (char_u *)sp->sn_icon);
+    if (sp->sn_text != NULL)
+	dict_add_string(retdict, "text", (char_u *)sp->sn_text);
+    if (sp->sn_line_hl > 0)
+    {
+	p = get_highlight_name_ext(NULL, sp->sn_line_hl - 1, FALSE);
+	if (p == NULL)
+	    p = (char_u *)"NONE";
+	dict_add_string(retdict, "linehl", (char_u *)p);
+    }
+    if (sp->sn_text_hl > 0)
+    {
+	p = get_highlight_name_ext(NULL, sp->sn_text_hl - 1, FALSE);
+	if (p == NULL)
+	    p = (char_u *)"NONE";
+	dict_add_string(retdict, "texthl", (char_u *)p);
+    }
+}
+
+/*
+ * If 'name' is NULL, return a list of all the defined signs.
+ * Otherwise, return information about the specified sign.
+ */
+    void
+sign_getlist(char_u *name, list_T *retlist)
+{
+    sign_T	*sp = first_sign;
+    dict_T	*dict;
+
+    if (name != NULL)
+    {
+	sp = sign_find(name, NULL);
+	if (sp == NULL)
+	    return;
+    }
+
+    for (; sp != NULL && !got_int; sp = sp->sn_next)
+    {
+	if ((dict = dict_alloc_id(aid_sign_getlist)) == NULL)
+	    return;
+	if (list_append_dict(retlist, dict) == FAIL)
+	    return;
+	sign_getinfo(sp, dict);
+
+	if (name != NULL)	    // handle only the specified sign
+	    break;
+    }
+}
+
+/*
+ * Return information about all the signs placed in a buffer
+ */
+    static void
+sign_get_placed_in_buf(
+	buf_T		*buf,
+	linenr_T	lnum,
+	int		sign_id,
+	char_u		*sign_group,
+	list_T		*retlist)
+{
+    dict_T	*d;
+    list_T	*l;
+    signlist_T	*sign;
+    dict_T	*sdict;
+
+    if ((d = dict_alloc_id(aid_sign_getplaced_dict)) == NULL)
+	return;
+    list_append_dict(retlist, d);
+
+    dict_add_number(d, "bufnr", (long)buf->b_fnum);
+
+    if ((l = list_alloc_id(aid_sign_getplaced_list)) == NULL)
+	return;
+    dict_add_list(d, "signs", l);
+
+    FOR_ALL_SIGNS_IN_BUF(buf)
+    {
+	if (!sign_in_group(sign, sign_group))
+	    continue;
+	if ((lnum == 0 && sign_id == 0) ||
+		(sign_id == 0 && lnum == sign->lnum) ||
+		(lnum == 0 && sign_id == sign->id) ||
+		(lnum == sign->lnum && sign_id == sign->id))
+	{
+	    if ((sdict = sign_get_info(sign)) != NULL)
+		list_append_dict(l, sdict);
+	}
+    }
+}
+
+/*
+ * Get a list of signs placed in buffer 'buf'. If 'num' is non-zero, return the
+ * sign placed at the line number. If 'lnum' is zero, return all the signs
+ * placed in 'buf'. If 'buf' is NULL, return signs placed in all the buffers.
+ */
+    void
+sign_get_placed(
+	buf_T		*buf,
+	linenr_T	lnum,
+	int		sign_id,
+	char_u		*sign_group,
+	list_T		*retlist)
+{
+    if (buf != NULL)
+	sign_get_placed_in_buf(buf, lnum, sign_id, sign_group, retlist);
+    else
+    {
+	FOR_ALL_BUFFERS(buf)
+	{
+	    if (buf->b_signlist != NULL)
+		sign_get_placed_in_buf(buf, 0, sign_id, sign_group, retlist);
+	}
+    }
+}
+
 # if defined(FEAT_SIGN_ICONS) || defined(PROTO)
 /*
  * Allocate the icons.  Called when the GUI has started.  Allows defining
@@ -8214,7 +8544,6 @@ sign_typenr2name(int typenr)
     return (char_u *)_("[Deleted]");
 }
 
-# if defined(EXITFREE) || defined(PROTO)
 /*
  * Undefine/free all signs.
  */
@@ -8224,7 +8553,6 @@ free_signs(void)
     while (first_sign != NULL)
 	sign_undefine(first_sign, NULL);
 }
-# endif
 
 # if defined(FEAT_CMDL_COMPL) || defined(PROTO)
 static enum