diff src/sign.c @ 15330:a6330a49e036 v8.1.0673

patch 8.1.0673: functionality for signs is spread out over several files commit https://github.com/vim/vim/commit/bbea47075cc4e7826e9f8c203e4272ba023ed7b0 Author: Bram Moolenaar <Bram@vim.org> Date: Tue Jan 1 13:20:31 2019 +0100 patch 8.1.0673: functionality for signs is spread out over several files Problem: Functionality for signs is spread out over several files. Solution: Move most of the sign functionality into sign.c. (Yegappan Lakshmanan, closes #3751)
author Bram Moolenaar <Bram@vim.org>
date Tue, 01 Jan 2019 13:30:09 +0100
parents
children 0d76dd701e49
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/src/sign.c
@@ -0,0 +1,1880 @@
+/* vi:set ts=8 sts=4 sw=4 noet:
+ *
+ * VIM - Vi IMproved	by Bram Moolenaar
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+/*
+ * sign.c: functions for managing signs
+ */
+
+#include "vim.h"
+
+#if defined(FEAT_SIGNS) || defined(PROTO)
+
+/*
+ * Struct to hold the sign properties.
+ */
+typedef struct sign sign_T;
+
+struct sign
+{
+    sign_T	*sn_next;	/* next sign in list */
+    int		sn_typenr;	/* type number of sign */
+    char_u	*sn_name;	/* name of sign */
+    char_u	*sn_icon;	/* name of pixmap */
+# ifdef FEAT_SIGN_ICONS
+    void	*sn_image;	/* icon image */
+# endif
+    char_u	*sn_text;	/* text used instead of pixmap */
+    int		sn_line_hl;	/* highlight ID for line */
+    int		sn_text_hl;	/* highlight ID for text */
+};
+
+static sign_T	*first_sign = NULL;
+static int	next_sign_typenr = 1;
+
+static void sign_list_defined(sign_T *sp);
+static void sign_undefine(sign_T *sp, sign_T *sp_prev);
+
+static char *cmds[] = {
+			"define",
+# define SIGNCMD_DEFINE	0
+			"undefine",
+# define SIGNCMD_UNDEFINE 1
+			"list",
+# define SIGNCMD_LIST	2
+			"place",
+# define SIGNCMD_PLACE	3
+			"unplace",
+# define SIGNCMD_UNPLACE 4
+			"jump",
+# define SIGNCMD_JUMP	5
+			NULL
+# define SIGNCMD_LAST	6
+};
+
+static hashtab_T	sg_table;	// sign group (signgroup_T) hashtable
+static int		next_sign_id = 1; // next sign id in the global group
+
+/*
+ * Initialize data needed for managing signs
+ */
+    void
+init_signs(void)
+{
+    hash_init(&sg_table);		// sign group hash table
+}
+
+/*
+ * A new sign in group 'groupname' is added. If the group is not present,
+ * create it. Otherwise reference the group.
+ */
+    static signgroup_T *
+sign_group_ref(char_u *groupname)
+{
+    hash_T		hash;
+    hashitem_T		*hi;
+    signgroup_T		*group;
+
+    hash = hash_hash(groupname);
+    hi = hash_lookup(&sg_table, groupname, hash);
+    if (HASHITEM_EMPTY(hi))
+    {
+	// new group
+	group = (signgroup_T *)alloc(
+		(unsigned)(sizeof(signgroup_T) + STRLEN(groupname)));
+	if (group == NULL)
+	    return NULL;
+	STRCPY(group->sg_name, groupname);
+	group->refcount = 1;
+	group->next_sign_id = 1;
+	hash_add_item(&sg_table, hi, group->sg_name, hash);
+    }
+    else
+    {
+	// existing group
+	group = HI2SG(hi);
+	group->refcount++;
+    }
+
+    return group;
+}
+
+/*
+ * A sign in group 'groupname' is removed. If all the signs in this group are
+ * removed, then remove the group.
+ */
+    static void
+sign_group_unref(char_u *groupname)
+{
+    hashitem_T		*hi;
+    signgroup_T		*group;
+
+    hi = hash_find(&sg_table, groupname);
+    if (!HASHITEM_EMPTY(hi))
+    {
+	group = HI2SG(hi);
+	group->refcount--;
+	if (group->refcount == 0)
+	{
+	    // All the signs in this group are removed
+	    hash_remove(&sg_table, hi);
+	    vim_free(group);
+	}
+    }
+}
+
+/*
+ * Returns TRUE if 'sign' is in 'group'.
+ * A sign can either be in the global group (sign->group == NULL)
+ * or in a named group. If 'group' is '*', then the sign is part of the group.
+ */
+    static int
+sign_in_group(signlist_T *sign, char_u *group)
+{
+    return ((group != NULL && STRCMP(group, "*") == 0)
+	    || (group == NULL && sign->group == NULL)
+	    || (group != NULL && sign->group != NULL
+				 && STRCMP(group, sign->group->sg_name) == 0));
+}
+
+/*
+ * Get the next free sign identifier in the specified group
+ */
+    static int
+sign_group_get_next_signid(buf_T *buf, char_u *groupname)
+{
+    int			id = 1;
+    signgroup_T		*group = NULL;
+    signlist_T		*sign;
+    hashitem_T		*hi;
+    int			found = FALSE;
+
+    if (groupname != NULL)
+    {
+	hi = hash_find(&sg_table, groupname);
+	if (HASHITEM_EMPTY(hi))
+	    return id;
+	group = HI2SG(hi);
+    }
+
+    // Search for the next usuable sign identifier
+    while (!found)
+    {
+	if (group == NULL)
+	    id = next_sign_id++;		// global group
+	else
+	    id = group->next_sign_id++;
+
+	// Check whether this sign is already placed in the buffer
+	found = TRUE;
+	FOR_ALL_SIGNS_IN_BUF(buf, sign)
+	{
+	    if (id == sign->id && sign_in_group(sign, groupname))
+	    {
+		found = FALSE;		// sign identifier is in use
+		break;
+	    }
+	}
+    }
+
+    return id;
+}
+
+/*
+ * Insert a new sign into the signlist for buffer 'buf' between the 'prev' and
+ * 'next' signs.
+ */
+    static void
+insert_sign(
+    buf_T	*buf,		// buffer to store sign in
+    signlist_T	*prev,		// previous sign entry
+    signlist_T	*next,		// next sign entry
+    int		id,		// sign ID
+    char_u	*group,		// sign group; NULL for global group
+    int		prio,		// sign priority
+    linenr_T	lnum,		// line number which gets the mark
+    int		typenr)		// typenr of sign we are adding
+{
+    signlist_T	*newsign;
+
+    newsign = (signlist_T *)lalloc_id((long_u)sizeof(signlist_T), FALSE,
+							aid_insert_sign);
+    if (newsign != NULL)
+    {
+	newsign->id = id;
+	newsign->lnum = lnum;
+	newsign->typenr = typenr;
+	if (group != NULL)
+	{
+	    newsign->group = sign_group_ref(group);
+	    if (newsign->group == NULL)
+	    {
+		vim_free(newsign);
+		return;
+	    }
+	}
+	else
+	    newsign->group = NULL;
+	newsign->priority = prio;
+	newsign->next = next;
+	newsign->prev = prev;
+	if (next != NULL)
+	    next->prev = newsign;
+
+	if (prev == NULL)
+	{
+	    // When adding first sign need to redraw the windows to create the
+	    // column for signs.
+	    if (buf->b_signlist == NULL)
+	    {
+		redraw_buf_later(buf, NOT_VALID);
+		changed_cline_bef_curs();
+	    }
+
+	    // first sign in signlist
+	    buf->b_signlist = newsign;
+#ifdef FEAT_NETBEANS_INTG
+	    if (netbeans_active())
+		buf->b_has_sign_column = TRUE;
+#endif
+	}
+	else
+	    prev->next = newsign;
+    }
+}
+
+/*
+ * Insert a new sign sorted by line number and sign priority.
+ */
+    static void
+insert_sign_by_lnum_prio(
+    buf_T	*buf,		// buffer to store sign in
+    signlist_T	*prev,		// previous sign entry
+    int		id,		// sign ID
+    char_u	*group,		// sign group; NULL for global group
+    int		prio,		// sign priority
+    linenr_T	lnum,		// line number which gets the mark
+    int		typenr)		// typenr of sign we are adding
+{
+    signlist_T	*sign;
+
+    // keep signs sorted by lnum and by priority: insert new sign at
+    // the proper position in the list for this lnum.
+    while (prev != NULL && prev->lnum == lnum && prev->priority <= prio)
+	prev = prev->prev;
+    if (prev == NULL)
+	sign = buf->b_signlist;
+    else
+	sign = prev->next;
+
+    insert_sign(buf, prev, sign, id, group, prio, lnum, typenr);
+}
+
+/*
+ * Get the name of a sign by its typenr.
+ */
+    static char_u *
+sign_typenr2name(int typenr)
+{
+    sign_T	*sp;
+
+    for (sp = first_sign; sp != NULL; sp = sp->sn_next)
+	if (sp->sn_typenr == typenr)
+	    return sp->sn_name;
+    return (char_u *)_("[Deleted]");
+}
+
+/*
+ * Return information about a sign in a Dict
+ */
+    static dict_T *
+sign_get_info(signlist_T *sign)
+{
+    dict_T	*d;
+
+    if ((d = dict_alloc_id(aid_sign_getinfo)) == NULL)
+	return NULL;
+    dict_add_number(d, "id", sign->id);
+    dict_add_string(d, "group", (sign->group == NULL) ?
+					(char_u *)"" : sign->group->sg_name);
+    dict_add_number(d, "lnum", sign->lnum);
+    dict_add_string(d, "name", sign_typenr2name(sign->typenr));
+    dict_add_number(d, "priority", sign->priority);
+
+    return d;
+}
+
+/*
+ * Add the sign into the signlist. Find the right spot to do it though.
+ */
+    static void
+buf_addsign(
+    buf_T	*buf,		// buffer to store sign in
+    int		id,		// sign ID
+    char_u	*groupname,	// sign group
+    int		prio,		// sign priority
+    linenr_T	lnum,		// line number which gets the mark
+    int		typenr)		// typenr of sign we are adding
+{
+    signlist_T	*sign;		// a sign in the signlist
+    signlist_T	*prev;		// the previous sign
+
+    prev = NULL;
+    FOR_ALL_SIGNS_IN_BUF(buf, sign)
+    {
+	if (lnum == sign->lnum && id == sign->id &&
+		sign_in_group(sign, groupname))
+	{
+	    // Update an existing sign
+	    sign->typenr = typenr;
+	    return;
+	}
+	else if (lnum < sign->lnum)
+	{
+	    insert_sign_by_lnum_prio(buf, prev, id, groupname, prio,
+								lnum, typenr);
+	    return;
+	}
+	prev = sign;
+    }
+
+    insert_sign_by_lnum_prio(buf, prev, id, groupname, prio, lnum, typenr);
+    return;
+}
+
+/*
+ * For an existing, placed sign "markId" change the type to "typenr".
+ * Returns the line number of the sign, or zero if the sign is not found.
+ */
+    static linenr_T
+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
+{
+    signlist_T	*sign;		// a sign in the signlist
+
+    FOR_ALL_SIGNS_IN_BUF(buf, sign)
+    {
+	if (sign->id == markId && sign_in_group(sign, group))
+	{
+	    sign->typenr = typenr;
+	    return sign->lnum;
+	}
+    }
+
+    return (linenr_T)0;
+}
+
+/*
+ * Return the type number of the sign at line number 'lnum' in buffer 'buf'
+ * which has the attribute specifed by 'type'. Returns 0 if a sign is not found
+ * at the line number or it doesn't have the specified attribute.
+ */
+    int
+buf_getsigntype(
+    buf_T	*buf,
+    linenr_T	lnum,
+    int		type)	/* SIGN_ICON, SIGN_TEXT, SIGN_ANY, SIGN_LINEHL */
+{
+    signlist_T	*sign;		/* a sign in a b_signlist */
+
+    FOR_ALL_SIGNS_IN_BUF(buf, sign)
+	if (sign->lnum == lnum
+		&& (type == SIGN_ANY
+# ifdef FEAT_SIGN_ICONS
+		    || (type == SIGN_ICON
+			&& sign_get_image(sign->typenr) != NULL)
+# endif
+		    || (type == SIGN_TEXT
+			&& sign_get_text(sign->typenr) != NULL)
+		    || (type == SIGN_LINEHL
+			&& sign_get_attr(sign->typenr, TRUE) != 0)))
+	    return sign->typenr;
+    return 0;
+}
+
+/*
+ * Delete sign 'id' in group 'group' from buffer 'buf'.
+ * If 'id' is zero, then delete all the signs in group 'group'. Otherwise
+ * delete only the specified sign.
+ * If 'group' is '*', then delete the sign in all the groups. If 'group' is
+ * NULL, then delete the sign in the global group. Otherwise delete the sign in
+ * the specified group.
+ * Returns the line number of the deleted sign. If multiple signs are deleted,
+ * then returns the line number of the last sign deleted.
+ */
+    linenr_T
+buf_delsign(
+    buf_T	*buf,		// buffer sign is stored in
+    linenr_T	atlnum,		// sign at this line, 0 - at any line
+    int		id,		// sign id
+    char_u	*group)		// sign group
+{
+    signlist_T	**lastp;	// pointer to pointer to current sign
+    signlist_T	*sign;		// a sign in a b_signlist
+    signlist_T	*next;		// the next sign in a b_signlist
+    linenr_T	lnum;		// line number whose sign was deleted
+
+    lastp = &buf->b_signlist;
+    lnum = 0;
+    for (sign = buf->b_signlist; sign != NULL; sign = next)
+    {
+	next = sign->next;
+	if ((id == 0 || sign->id == id) &&
+		(atlnum == 0 || sign->lnum == atlnum) &&
+		sign_in_group(sign, group))
+
+	{
+	    *lastp = next;
+	    if (next != NULL)
+		next->prev = sign->prev;
+	    lnum = sign->lnum;
+	    if (sign->group != NULL)
+		sign_group_unref(sign->group->sg_name);
+	    vim_free(sign);
+	    update_debug_sign(buf, lnum);
+	    // Check whether only one sign needs to be deleted
+	    // If deleting a sign with a specific identifer in a particular
+	    // group or deleting any sign at a particular line number, delete
+	    // only one sign.
+	    if (group == NULL
+		    || (*group != '*' && id != 0)
+		    || (*group == '*' && atlnum != 0))
+		break;
+	}
+	else
+	    lastp = &sign->next;
+    }
+
+    // When deleted the last sign need to redraw the windows to remove the
+    // sign column.
+    if (buf->b_signlist == NULL)
+    {
+	redraw_buf_later(buf, NOT_VALID);
+	changed_cline_bef_curs();
+    }
+
+    return lnum;
+}
+
+
+/*
+ * Find the line number of the sign with the requested id in group 'group'. If
+ * the sign does not exist, return 0 as the line number. This will still let
+ * the correct file get loaded.
+ */
+    int
+buf_findsign(
+    buf_T	*buf,		// buffer to store sign in
+    int		id,		// sign ID
+    char_u	*group)		// sign group
+{
+    signlist_T	*sign;		// a sign in the signlist
+
+    FOR_ALL_SIGNS_IN_BUF(buf, sign)
+	if (sign->id == id && sign_in_group(sign, group))
+	    return sign->lnum;
+
+    return 0;
+}
+
+/*
+ * Return the sign at line 'lnum' in buffer 'buf'. Returns NULL if a sign is
+ * not found at the line. If 'groupname' is NULL, searches in the global group.
+ */
+    static signlist_T *
+buf_getsign_at_line(
+    buf_T	*buf,		// buffer whose sign we are searching for
+    linenr_T	lnum,		// line number of sign
+    char_u	*groupname)	// sign group name
+{
+    signlist_T	*sign;		// a sign in the signlist
+
+    FOR_ALL_SIGNS_IN_BUF(buf, sign)
+	if (sign->lnum == lnum && sign_in_group(sign, groupname))
+	    return sign;
+
+    return NULL;
+}
+
+/*
+ * Return the identifier of the sign at line number 'lnum' in buffer 'buf'.
+ */
+    int
+buf_findsign_id(
+    buf_T	*buf,		// buffer whose sign we are searching for
+    linenr_T	lnum,		// line number of sign
+    char_u	*groupname)	// sign group name
+{
+    signlist_T	*sign;		// a sign in the signlist
+
+    sign = buf_getsign_at_line(buf, lnum, groupname);
+    if (sign != NULL)
+	return sign->id;
+
+    return 0;
+}
+
+# if defined(FEAT_NETBEANS_INTG) || defined(PROTO)
+/*
+ * See if a given type of sign exists on a specific line.
+ */
+    int
+buf_findsigntype_id(
+    buf_T	*buf,		/* buffer whose sign we are searching for */
+    linenr_T	lnum,		/* line number of sign */
+    int		typenr)		/* sign type number */
+{
+    signlist_T	*sign;		/* a sign in the signlist */
+
+    FOR_ALL_SIGNS_IN_BUF(buf, sign)
+	if (sign->lnum == lnum && sign->typenr == typenr)
+	    return sign->id;
+
+    return 0;
+}
+
+
+#  if defined(FEAT_SIGN_ICONS) || defined(PROTO)
+/*
+ * Return the number of icons on the given line.
+ */
+    int
+buf_signcount(buf_T *buf, linenr_T lnum)
+{
+    signlist_T	*sign;		// a sign in the signlist
+    int		count = 0;
+
+    FOR_ALL_SIGNS_IN_BUF(buf, sign)
+	if (sign->lnum == lnum)
+	    if (sign_get_image(sign->typenr) != NULL)
+		count++;
+
+    return count;
+}
+#  endif /* FEAT_SIGN_ICONS */
+# endif /* FEAT_NETBEANS_INTG */
+
+/*
+ * Delete signs in group 'group' in buffer "buf". If 'group' is '*', then
+ * delete all the signs.
+ */
+    void
+buf_delete_signs(buf_T *buf, char_u *group)
+{
+    signlist_T	*sign;
+    signlist_T	**lastp;	// pointer to pointer to current sign
+    signlist_T	*next;
+
+    // When deleting the last sign need to redraw the windows to remove the
+    // sign column. Not when curwin is NULL (this means we're exiting).
+    if (buf->b_signlist != NULL && curwin != NULL)
+    {
+	redraw_buf_later(buf, NOT_VALID);
+	changed_cline_bef_curs();
+    }
+
+    lastp = &buf->b_signlist;
+    for (sign = buf->b_signlist; sign != NULL; sign = next)
+    {
+	next = sign->next;
+	if (sign_in_group(sign, group))
+	{
+	    *lastp = next;
+	    if (next != NULL)
+		next->prev = sign->prev;
+	    if (sign->group != NULL)
+		sign_group_unref(sign->group->sg_name);
+	    vim_free(sign);
+	}
+	else
+	    lastp = &sign->next;
+    }
+}
+
+/*
+ * List placed signs for "rbuf".  If "rbuf" is NULL do it for all buffers.
+ */
+    static void
+sign_list_placed(buf_T *rbuf, char_u *sign_group)
+{
+    buf_T	*buf;
+    signlist_T	*sign;
+    char	lbuf[BUFSIZ];
+    char	group[BUFSIZ];
+
+    MSG_PUTS_TITLE(_("\n--- Signs ---"));
+    msg_putchar('\n');
+    if (rbuf == NULL)
+	buf = firstbuf;
+    else
+	buf = rbuf;
+    while (buf != NULL && !got_int)
+    {
+	if (buf->b_signlist != NULL)
+	{
+	    vim_snprintf(lbuf, BUFSIZ, _("Signs for %s:"), buf->b_fname);
+	    MSG_PUTS_ATTR(lbuf, HL_ATTR(HLF_D));
+	    msg_putchar('\n');
+	}
+	FOR_ALL_SIGNS_IN_BUF(buf, sign)
+	{
+	    if (got_int)
+		break;
+	    if (!sign_in_group(sign, sign_group))
+		continue;
+	    if (sign->group != NULL)
+		vim_snprintf(group, BUFSIZ, "  group=%s",
+							sign->group->sg_name);
+	    else
+		group[0] = '\0';
+	    vim_snprintf(lbuf, BUFSIZ, _("    line=%ld  id=%d%s  name=%s "
+							"priority=%d"),
+			   (long)sign->lnum, sign->id, group,
+			   sign_typenr2name(sign->typenr), sign->priority);
+	    MSG_PUTS(lbuf);
+	    msg_putchar('\n');
+	}
+	if (rbuf != NULL)
+	    break;
+	buf = buf->b_next;
+    }
+}
+
+/*
+ * Adjust a placed sign for inserted/deleted lines.
+ */
+    void
+sign_mark_adjust(
+    linenr_T	line1,
+    linenr_T	line2,
+    long	amount,
+    long	amount_after)
+{
+    signlist_T	*sign;		/* a sign in a b_signlist */
+
+    FOR_ALL_SIGNS_IN_BUF(curbuf, sign)
+    {
+	if (sign->lnum >= line1 && sign->lnum <= line2)
+	{
+	    if (amount == MAXLNUM)
+		sign->lnum = line1;
+	    else
+		sign->lnum += amount;
+	}
+	else if (sign->lnum > line2)
+	    sign->lnum += amount_after;
+    }
+}
+
+/*
+ * Find index of a ":sign" subcmd from its name.
+ * "*end_cmd" must be writable.
+ */
+    static int
+sign_cmd_idx(
+    char_u	*begin_cmd,	/* begin of sign subcmd */
+    char_u	*end_cmd)	/* just after sign subcmd */
+{
+    int		idx;
+    char	save = *end_cmd;
+
+    *end_cmd = NUL;
+    for (idx = 0; ; ++idx)
+	if (cmds[idx] == NULL || STRCMP(begin_cmd, cmds[idx]) == 0)
+	    break;
+    *end_cmd = save;
+    return 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)
+	*sign_id = sign_group_get_next_signid(buf, sign_group);
+
+    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, linenr_T atlnum)
+{
+    if (buf->b_signlist == NULL)	// No signs in the buffer
+	return OK;
+
+    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, atlnum, sign_id, sign_group);
+	if (lnum == 0)
+	    return FAIL;
+    }
+
+    return OK;
+}
+
+/*
+ * Unplace the sign at the current cursor line.
+ */
+    static void
+sign_unplace_at_cursor(char_u *groupname)
+{
+    int		id = -1;
+
+    id = buf_findsign_id(curwin->w_buffer, curwin->w_cursor.lnum, groupname);
+    if (id > 0)
+	sign_unplace(id, groupname, curwin->w_buffer, curwin->w_cursor.lnum);
+    else
+	EMSG(_("E159: Missing sign number"));
+}
+
+/*
+ * sign define command
+ *   ":sign define {name} ..."
+ */
+    static void
+sign_define_cmd(char_u *sign_name, char_u *cmdline)
+{
+    char_u	*arg;
+    char_u	*p = cmdline;
+    char_u	*icon = NULL;
+    char_u	*text = NULL;
+    char_u	*linehl = NULL;
+    char_u	*texthl = NULL;
+    int failed = FALSE;
+
+    // set values for a defined sign.
+    for (;;)
+    {
+	arg = skipwhite(p);
+	if (*arg == NUL)
+	    break;
+	p = skiptowhite_esc(arg);
+	if (STRNCMP(arg, "icon=", 5) == 0)
+	{
+	    arg += 5;
+	    icon = vim_strnsave(arg, (int)(p - arg));
+	}
+	else if (STRNCMP(arg, "text=", 5) == 0)
+	{
+	    arg += 5;
+	    text = vim_strnsave(arg, (int)(p - arg));
+	}
+	else if (STRNCMP(arg, "linehl=", 7) == 0)
+	{
+	    arg += 7;
+	    linehl = vim_strnsave(arg, (int)(p - arg));
+	}
+	else if (STRNCMP(arg, "texthl=", 7) == 0)
+	{
+	    arg += 7;
+	    texthl = vim_strnsave(arg, (int)(p - arg));
+	}
+	else
+	{
+	    EMSG2(_(e_invarg2), arg);
+	    failed = TRUE;
+	    break;
+	}
+    }
+
+    if (!failed)
+	sign_define_by_name(sign_name, icon, linehl, text, texthl);
+
+    vim_free(icon);
+    vim_free(text);
+    vim_free(linehl);
+    vim_free(texthl);
+}
+
+/*
+ * :sign place command
+ */
+    static void
+sign_place_cmd(
+	buf_T		*buf,
+	linenr_T	lnum,
+	char_u		*sign_name,
+	int		id,
+	char_u		*group,
+	int		prio)
+{
+    if (id <= 0)
+    {
+	// List signs placed in a file/buffer
+	//   :sign place file={fname}
+	//   :sign place group={group} file={fname}
+	//   :sign place group=* file={fname}
+	//   :sign place buffer={nr}
+	//   :sign place group={group} buffer={nr}
+	//   :sign place group=* buffer={nr}
+	//   :sign place
+	//   :sign place group={group}
+	//   :sign place group=*
+	if (lnum >= 0 || sign_name != NULL ||
+		(group != NULL && *group == '\0'))
+	    EMSG(_(e_invarg));
+	else
+	    sign_list_placed(buf, group);
+    }
+    else
+    {
+	// Place a new sign
+	if (sign_name == NULL || buf == NULL ||
+		(group != NULL && *group == '\0'))
+	{
+	    EMSG(_(e_invarg));
+	    return;
+	}
+
+	sign_place(&id, group, sign_name, buf, lnum, prio);
+    }
+}
+
+/*
+ * :sign unplace command
+ */
+    static void
+sign_unplace_cmd(
+	buf_T		*buf,
+	linenr_T	lnum,
+	char_u		*sign_name,
+	int		id,
+	char_u		*group)
+{
+    if (lnum >= 0 || sign_name != NULL || (group != NULL && *group == '\0'))
+    {
+	EMSG(_(e_invarg));
+	return;
+    }
+
+    if (id == -2)
+    {
+	if (buf != NULL)
+	    // :sign unplace * file={fname}
+	    // :sign unplace * group={group} file={fname}
+	    // :sign unplace * group=* file={fname}
+	    // :sign unplace * buffer={nr}
+	    // :sign unplace * group={group} buffer={nr}
+	    // :sign unplace * group=* buffer={nr}
+	    sign_unplace(0, group, buf, 0);
+	else
+	    // :sign unplace *
+	    // :sign unplace * group={group}
+	    // :sign unplace * group=*
+	    FOR_ALL_BUFFERS(buf)
+		if (buf->b_signlist != NULL)
+		    buf_delete_signs(buf, group);
+    }
+    else
+    {
+	if (buf != NULL)
+	    // :sign unplace {id} file={fname}
+	    // :sign unplace {id} group={group} file={fname}
+	    // :sign unplace {id} group=* file={fname}
+	    // :sign unplace {id} buffer={nr}
+	    // :sign unplace {id} group={group} buffer={nr}
+	    // :sign unplace {id} group=* buffer={nr}
+	    sign_unplace(id, group, buf, 0);
+	else
+	{
+	    if (id == -1)
+	    {
+		// :sign unplace group={group}
+		// :sign unplace group=*
+		sign_unplace_at_cursor(group);
+	    }
+	    else
+	    {
+		// :sign unplace {id}
+		// :sign unplace {id} group={group}
+		// :sign unplace {id} group=*
+		FOR_ALL_BUFFERS(buf)
+		    sign_unplace(id, group, buf, 0);
+	    }
+	}
+    }
+}
+
+/*
+ * Jump to a placed sign
+ *   :sign jump {id} file={fname}
+ *   :sign jump {id} buffer={nr}
+ *   :sign jump {id} group={group} file={fname}
+ *   :sign jump {id} group={group} buffer={nr}
+ */
+    static void
+sign_jump_cmd(
+	buf_T		*buf,
+	linenr_T	lnum,
+	char_u		*sign_name,
+	int		id,
+	char_u		*group)
+{
+    if (buf == NULL && sign_name == NULL && group == NULL && id == -1)
+    {
+	EMSG(_(e_argreq));
+	return;
+    }
+
+    if (buf == NULL || (group != NULL && *group == '\0') ||
+					lnum >= 0 || sign_name != NULL)
+    {
+	// File or buffer is not specified or an empty group is used
+	// or a line number or a sign name is specified.
+	EMSG(_(e_invarg));
+	return;
+    }
+
+    if ((lnum = buf_findsign(buf, id, group)) <= 0)
+    {
+	EMSGN(_("E157: Invalid sign ID: %ld"), id);
+	return;
+    }
+
+    // goto a sign ...
+    if (buf_jump_open_win(buf) != NULL)
+    {			// ... in a current window
+	curwin->w_cursor.lnum = lnum;
+	check_cursor_lnum();
+	beginline(BL_WHITE);
+    }
+    else
+    {			// ... not currently in a window
+	char_u	*cmd;
+
+	if (buf->b_fname == NULL)
+	{
+	    EMSG(_("E934: Cannot jump to a buffer that does not have a name"));
+	    return;
+	}
+	cmd = alloc((unsigned)STRLEN(buf->b_fname) + 25);
+	if (cmd == NULL)
+	    return;
+	sprintf((char *)cmd, "e +%ld %s", (long)lnum, buf->b_fname);
+	do_cmdline_cmd(cmd);
+	vim_free(cmd);
+    }
+# ifdef FEAT_FOLDING
+    foldOpenCursor();
+# endif
+}
+
+/*
+ * Parse the command line arguments for the ":sign place", ":sign unplace" and
+ * ":sign jump" commands.
+ * The supported arguments are: line={lnum} name={name} group={group}
+ * priority={prio} and file={fname} or buffer={nr}.
+ */
+    static int
+parse_sign_cmd_args(
+	int	    cmd,
+	char_u	    *arg,
+	char_u	    **sign_name,
+	int	    *signid,
+	char_u	    **group,
+	int	    *prio,
+	buf_T	    **buf,
+	linenr_T    *lnum)
+{
+    char_u	*arg1;
+    char_u	*name;
+    char_u	*filename = NULL;
+
+    // first arg could be placed sign id
+    arg1 = arg;
+    if (VIM_ISDIGIT(*arg))
+    {
+	*signid = getdigits(&arg);
+	if (!VIM_ISWHITE(*arg) && *arg != NUL)
+	{
+	    *signid = -1;
+	    arg = arg1;
+	}
+	else
+	    arg = skipwhite(arg);
+    }
+
+    while (*arg != NUL)
+    {
+	if (STRNCMP(arg, "line=", 5) == 0)
+	{
+	    arg += 5;
+	    *lnum = atoi((char *)arg);
+	    arg = skiptowhite(arg);
+	}
+	else if (STRNCMP(arg, "*", 1) == 0 && cmd == SIGNCMD_UNPLACE)
+	{
+	    if (*signid != -1)
+	    {
+		EMSG(_(e_invarg));
+		return FAIL;
+	    }
+	    *signid = -2;
+	    arg = skiptowhite(arg + 1);
+	}
+	else if (STRNCMP(arg, "name=", 5) == 0)
+	{
+	    arg += 5;
+	    name = arg;
+	    arg = skiptowhite(arg);
+	    if (*arg != NUL)
+		*arg++ = NUL;
+	    while (name[0] == '0' && name[1] != NUL)
+		++name;
+	    *sign_name = 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;
+	    filename = arg;
+	    *buf = buflist_findname_exp(arg);
+	    break;
+	}
+	else if (STRNCMP(arg, "buffer=", 7) == 0)
+	{
+	    arg += 7;
+	    filename = arg;
+	    *buf = buflist_findnr((int)getdigits(&arg));
+	    if (*skipwhite(arg) != NUL)
+		EMSG(_(e_trailing));
+	    break;
+	}
+	else
+	{
+	    EMSG(_(e_invarg));
+	    return FAIL;
+	}
+	arg = skipwhite(arg);
+    }
+
+    if (filename != NULL && *buf == NULL)
+    {
+	EMSG2(_("E158: Invalid buffer name: %s"), filename);
+	return FAIL;
+    }
+
+    return OK;
+}
+
+/*
+ * ":sign" command
+ */
+    void
+ex_sign(exarg_T *eap)
+{
+    char_u	*arg = eap->arg;
+    char_u	*p;
+    int		idx;
+    sign_T	*sp;
+    buf_T	*buf = NULL;
+
+    // Parse the subcommand.
+    p = skiptowhite(arg);
+    idx = sign_cmd_idx(arg, p);
+    if (idx == SIGNCMD_LAST)
+    {
+	EMSG2(_("E160: Unknown sign command: %s"), arg);
+	return;
+    }
+    arg = skipwhite(p);
+
+    if (idx <= SIGNCMD_LIST)
+    {
+	// Define, undefine or list signs.
+	if (idx == SIGNCMD_LIST && *arg == NUL)
+	{
+	    // ":sign list": list all defined signs
+	    for (sp = first_sign; sp != NULL && !got_int; sp = sp->sn_next)
+		sign_list_defined(sp);
+	}
+	else if (*arg == NUL)
+	    EMSG(_("E156: Missing sign name"));
+	else
+	{
+	    char_u	*name;
+
+	    // 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);
+	    if (*p != NUL)
+		*p++ = NUL;
+	    while (arg[0] == '0' && arg[1] != NUL)
+		++arg;
+	    name = vim_strsave(arg);
+
+	    if (idx == SIGNCMD_DEFINE)
+		sign_define_cmd(name, p);
+	    else if (idx == SIGNCMD_LIST)
+		// ":sign list {name}"
+		sign_list_by_name(name);
+	    else
+		// ":sign undefine {name}"
+		sign_undefine_by_name(name);
+
+	    vim_free(name);
+	    return;
+	}
+    }
+    else
+    {
+	int		id = -1;
+	linenr_T	lnum = -1;
+	char_u		*sign_name = NULL;
+	char_u		*group = NULL;
+	int		prio = SIGN_DEF_PRIO;
+
+	// Parse command line arguments
+	if (parse_sign_cmd_args(idx, arg, &sign_name, &id, &group, &prio,
+							  &buf, &lnum) == FAIL)
+	    return;
+
+	if (idx == SIGNCMD_PLACE)
+	    sign_place_cmd(buf, lnum, sign_name, id, group, prio);
+	else if (idx == SIGNCMD_UNPLACE)
+	    sign_unplace_cmd(buf, lnum, sign_name, id, group);
+	else if (idx == SIGNCMD_JUMP)
+	    sign_jump_cmd(buf, lnum, sign_name, id, group);
+    }
+}
+
+/*
+ * 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;
+    }
+}
+
+/*
+ * Returns information about signs placed in a buffer as list of dicts.
+ */
+    void
+get_buffer_signs(buf_T *buf, list_T *l)
+{
+    signlist_T	*sign;
+    dict_T	*d;
+
+    FOR_ALL_SIGNS_IN_BUF(buf, sign)
+    {
+	if ((d = sign_get_info(sign)) != NULL)
+	    list_append_dict(l, d);
+    }
+}
+
+/*
+ * 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, sign)
+    {
+	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
+ * signs before it starts.
+ */
+    void
+sign_gui_started(void)
+{
+    sign_T	*sp;
+
+    for (sp = first_sign; sp != NULL; sp = sp->sn_next)
+	if (sp->sn_icon != NULL)
+	    sp->sn_image = gui_mch_register_sign(sp->sn_icon);
+}
+# endif
+
+/*
+ * List one sign.
+ */
+    static void
+sign_list_defined(sign_T *sp)
+{
+    char_u	*p;
+
+    smsg((char_u *)"sign %s", sp->sn_name);
+    if (sp->sn_icon != NULL)
+    {
+	MSG_PUTS(" icon=");
+	msg_outtrans(sp->sn_icon);
+# ifdef FEAT_SIGN_ICONS
+	if (sp->sn_image == NULL)
+	    MSG_PUTS(_(" (NOT FOUND)"));
+# else
+	MSG_PUTS(_(" (not supported)"));
+# endif
+    }
+    if (sp->sn_text != NULL)
+    {
+	MSG_PUTS(" text=");
+	msg_outtrans(sp->sn_text);
+    }
+    if (sp->sn_line_hl > 0)
+    {
+	MSG_PUTS(" linehl=");
+	p = get_highlight_name_ext(NULL, sp->sn_line_hl - 1, FALSE);
+	if (p == NULL)
+	    MSG_PUTS("NONE");
+	else
+	    msg_puts(p);
+    }
+    if (sp->sn_text_hl > 0)
+    {
+	MSG_PUTS(" texthl=");
+	p = get_highlight_name_ext(NULL, sp->sn_text_hl - 1, FALSE);
+	if (p == NULL)
+	    MSG_PUTS("NONE");
+	else
+	    msg_puts(p);
+    }
+}
+
+/*
+ * Undefine a sign and free its memory.
+ */
+    static void
+sign_undefine(sign_T *sp, sign_T *sp_prev)
+{
+    vim_free(sp->sn_name);
+    vim_free(sp->sn_icon);
+# ifdef FEAT_SIGN_ICONS
+    if (sp->sn_image != NULL)
+    {
+	out_flush();
+	gui_mch_destroy_sign(sp->sn_image);
+    }
+# endif
+    vim_free(sp->sn_text);
+    if (sp_prev == NULL)
+	first_sign = sp->sn_next;
+    else
+	sp_prev->sn_next = sp->sn_next;
+    vim_free(sp);
+}
+
+/*
+ * Get highlighting attribute for sign "typenr".
+ * If "line" is TRUE: line highl, if FALSE: text highl.
+ */
+    int
+sign_get_attr(int typenr, int line)
+{
+    sign_T	*sp;
+
+    for (sp = first_sign; sp != NULL; sp = sp->sn_next)
+	if (sp->sn_typenr == typenr)
+	{
+	    if (line)
+	    {
+		if (sp->sn_line_hl > 0)
+		    return syn_id2attr(sp->sn_line_hl);
+	    }
+	    else
+	    {
+		if (sp->sn_text_hl > 0)
+		    return syn_id2attr(sp->sn_text_hl);
+	    }
+	    break;
+	}
+    return 0;
+}
+
+/*
+ * Get text mark for sign "typenr".
+ * Returns NULL if there isn't one.
+ */
+    char_u *
+sign_get_text(int typenr)
+{
+    sign_T	*sp;
+
+    for (sp = first_sign; sp != NULL; sp = sp->sn_next)
+	if (sp->sn_typenr == typenr)
+	    return sp->sn_text;
+    return NULL;
+}
+
+# if defined(FEAT_SIGN_ICONS) || defined(PROTO)
+    void *
+sign_get_image(
+    int		typenr)		/* the attribute which may have a sign */
+{
+    sign_T	*sp;
+
+    for (sp = first_sign; sp != NULL; sp = sp->sn_next)
+	if (sp->sn_typenr == typenr)
+	    return sp->sn_image;
+    return NULL;
+}
+# endif
+
+/*
+ * Undefine/free all signs.
+ */
+    void
+free_signs(void)
+{
+    while (first_sign != NULL)
+	sign_undefine(first_sign, NULL);
+}
+
+# if defined(FEAT_CMDL_COMPL) || defined(PROTO)
+static enum
+{
+    EXP_SUBCMD,		/* expand :sign sub-commands */
+    EXP_DEFINE,		/* expand :sign define {name} args */
+    EXP_PLACE,		/* expand :sign place {id} args */
+    EXP_UNPLACE,	/* expand :sign unplace" */
+    EXP_SIGN_NAMES	/* expand with name of placed signs */
+} expand_what;
+
+/*
+ * Function given to ExpandGeneric() to obtain the sign command
+ * expansion.
+ */
+    char_u *
+get_sign_name(expand_T *xp UNUSED, int idx)
+{
+    sign_T	*sp;
+    int		current_idx;
+
+    switch (expand_what)
+    {
+    case EXP_SUBCMD:
+	return (char_u *)cmds[idx];
+    case EXP_DEFINE:
+	{
+	    char *define_arg[] =
+	    {
+		"icon=", "linehl=", "text=", "texthl=", NULL
+	    };
+	    return (char_u *)define_arg[idx];
+	}
+    case EXP_PLACE:
+	{
+	    char *place_arg[] =
+	    {
+		"line=", "name=", "group=", "priority=", "file=",
+		"buffer=", NULL
+	    };
+	    return (char_u *)place_arg[idx];
+	}
+    case EXP_UNPLACE:
+	{
+	    char *unplace_arg[] = { "group=", "file=", "buffer=", NULL };
+	    return (char_u *)unplace_arg[idx];
+	}
+    case EXP_SIGN_NAMES:
+	/* Complete with name of signs already defined */
+	current_idx = 0;
+	for (sp = first_sign; sp != NULL; sp = sp->sn_next)
+	    if (current_idx++ == idx)
+		return sp->sn_name;
+	return NULL;
+    default:
+	return NULL;
+    }
+}
+
+/*
+ * Handle command line completion for :sign command.
+ */
+    void
+set_context_in_sign_cmd(expand_T *xp, char_u *arg)
+{
+    char_u	*p;
+    char_u	*end_subcmd;
+    char_u	*last;
+    int		cmd_idx;
+    char_u	*begin_subcmd_args;
+
+    /* Default: expand subcommands. */
+    xp->xp_context = EXPAND_SIGN;
+    expand_what = EXP_SUBCMD;
+    xp->xp_pattern = arg;
+
+    end_subcmd = skiptowhite(arg);
+    if (*end_subcmd == NUL)
+	/* expand subcmd name
+	 * :sign {subcmd}<CTRL-D>*/
+	return;
+
+    cmd_idx = sign_cmd_idx(arg, end_subcmd);
+
+    /* :sign {subcmd} {subcmd_args}
+     *		      |
+     *		      begin_subcmd_args */
+    begin_subcmd_args = skipwhite(end_subcmd);
+    p = skiptowhite(begin_subcmd_args);
+    if (*p == NUL)
+    {
+	/*
+	 * Expand first argument of subcmd when possible.
+	 * For ":jump {id}" and ":unplace {id}", we could
+	 * possibly expand the ids of all signs already placed.
+	 */
+	xp->xp_pattern = begin_subcmd_args;
+	switch (cmd_idx)
+	{
+	    case SIGNCMD_LIST:
+	    case SIGNCMD_UNDEFINE:
+		/* :sign list <CTRL-D>
+		 * :sign undefine <CTRL-D> */
+		expand_what = EXP_SIGN_NAMES;
+		break;
+	    default:
+		xp->xp_context = EXPAND_NOTHING;
+	}
+	return;
+    }
+
+    /* expand last argument of subcmd */
+
+    /* :sign define {name} {args}...
+     *		    |
+     *		    p */
+
+    /* Loop until reaching last argument. */
+    do
+    {
+	p = skipwhite(p);
+	last = p;
+	p = skiptowhite(p);
+    } while (*p != NUL);
+
+    p = vim_strchr(last, '=');
+
+    /* :sign define {name} {args}... {last}=
+     *				     |	   |
+     *				  last	   p */
+    if (p == NULL)
+    {
+	/* Expand last argument name (before equal sign). */
+	xp->xp_pattern = last;
+	switch (cmd_idx)
+	{
+	    case SIGNCMD_DEFINE:
+		expand_what = EXP_DEFINE;
+		break;
+	    case SIGNCMD_PLACE:
+		expand_what = EXP_PLACE;
+		break;
+	    case SIGNCMD_JUMP:
+	    case SIGNCMD_UNPLACE:
+		expand_what = EXP_UNPLACE;
+		break;
+	    default:
+		xp->xp_context = EXPAND_NOTHING;
+	}
+    }
+    else
+    {
+	/* Expand last argument value (after equal sign). */
+	xp->xp_pattern = p + 1;
+	switch (cmd_idx)
+	{
+	    case SIGNCMD_DEFINE:
+		if (STRNCMP(last, "texthl", p - last) == 0 ||
+		    STRNCMP(last, "linehl", p - last) == 0)
+		    xp->xp_context = EXPAND_HIGHLIGHT;
+		else if (STRNCMP(last, "icon", p - last) == 0)
+		    xp->xp_context = EXPAND_FILES;
+		else
+		    xp->xp_context = EXPAND_NOTHING;
+		break;
+	    case SIGNCMD_PLACE:
+		if (STRNCMP(last, "name", p - last) == 0)
+		    expand_what = EXP_SIGN_NAMES;
+		else
+		    xp->xp_context = EXPAND_NOTHING;
+		break;
+	    default:
+		xp->xp_context = EXPAND_NOTHING;
+	}
+    }
+}
+# endif
+
+#endif /* FEAT_SIGNS */