Files
vim-mirror/src/sign.c
T
Matthias Rader a5b19603e4 patch 9.1.2052: Compile error when disabling linebreak feature
Problem:  Compile error when disabling linebreak feature
Solution: Adjust ifdefs (Matthias Rader)

When compiling with all features except for linebreak, there were some
compiler errors. By slightly modifying some preprocessor conditions,
compiling without the linebreak feature should work as expected.

closes: #19068

Signed-off-by: Matthias Rader <matthias.rader@outlook.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
2026-01-06 09:58:04 +00:00

2950 lines
74 KiB
C

/* vi:set ts=8 sts=4 sw=4 et:
*
* 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)
/*
* 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
int sn_cul_hl; // highlight ID for text on current line when 'cursorline' is set
int sn_num_hl; // highlight ID for line number
int sn_priority; // default priority of this sign, -1 means SIGN_DEF_PRIO
};
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
};
# define FOR_ALL_SIGNS(sp) \
for ((sp) = first_sign; (sp) != NULL; (sp) = (sp)->sn_next)
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 = hash_hash(groupname);
hashitem_T *hi = hash_lookup(&sg_table, groupname, hash);
signgroup_T *group = NULL;
if (HASHITEM_EMPTY(hi))
{
// new group
group = alloc(offsetof(signgroup_T, sg_name) + STRLEN(groupname) + 1);
if (group == NULL)
return NULL;
STRCPY(group->sg_name, groupname);
group->sg_refcount = 1;
group->sg_next_sign_id = 1;
hash_add_item(&sg_table, hi, group->sg_name, hash);
}
else
{
// existing group
group = HI2SG(hi);
group->sg_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 = hash_find(&sg_table, groupname);
if (HASHITEM_EMPTY(hi))
return;
signgroup_T *group = HI2SG(hi);
group->sg_refcount--;
if (group->sg_refcount == 0)
{
// All the signs in this group are removed
hash_remove(&sg_table, hi, "sign remove");
vim_free(group);
}
}
/*
* Returns TRUE if 'sign' is in 'group'.
* A sign can either be in the global group (sign->se_group == NULL)
* or in a named group. If 'group' is '*', then the sign is part of the group.
*/
static int
sign_in_group(sign_entry_T *sign, char_u *group)
{
return ((group != NULL && STRCMP(group, "*") == 0) ||
(group == NULL && sign->se_group == NULL) ||
(group != NULL && sign->se_group != NULL &&
STRCMP(group, sign->se_group->sg_name) == 0));
}
/*
* Return TRUE if "sign" is to be displayed in window "wp".
* If the group name starts with "PopUp" it only shows in a popup window.
*/
static int
sign_group_for_window(sign_entry_T *sign, win_T *wp)
{
int for_popup = sign->se_group != NULL &&
STRNCMP("PopUp", sign->se_group->sg_name, 5) == 0;
return WIN_IS_POPUP(wp) ? for_popup : !for_popup;
}
/*
* 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;
sign_entry_T *sign = NULL;
if (groupname != NULL)
{
hashitem_T *hi = hash_find(&sg_table, groupname);
if (HASHITEM_EMPTY(hi))
return id;
group = HI2SG(hi);
}
// Search for the next usable sign identifier
int found = FALSE;
while (!found)
{
if (group == NULL)
id = next_sign_id++; // global group
else
id = group->sg_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->se_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
sign_entry_T *prev, // previous sign entry
sign_entry_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
{
sign_entry_T *newsign =
lalloc_id(sizeof(sign_entry_T), FALSE, aid_insert_sign);
if (newsign == NULL)
return;
newsign->se_id = id;
newsign->se_lnum = lnum;
newsign->se_typenr = typenr;
if (group != NULL)
{
newsign->se_group = sign_group_ref(group);
if (newsign->se_group == NULL)
{
vim_free(newsign);
return;
}
}
else
{
newsign->se_group = NULL;
}
newsign->se_priority = prio;
newsign->se_next = next;
newsign->se_prev = prev;
if (next != NULL)
next->se_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, UPD_NOT_VALID);
changed_line_abv_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->se_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
sign_entry_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
{
// 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->se_lnum == lnum && prev->se_priority <= prio)
prev = prev->se_prev;
sign_entry_T *sign = (prev == NULL) ? buf->b_signlist : prev->se_next;
insert_sign(buf, prev, sign, id, group, prio, lnum, typenr);
}
/*
* Lookup a sign by typenr. Returns NULL if sign is not found.
*/
static sign_T *
find_sign_by_typenr(int typenr)
{
sign_T *sp = NULL;
FOR_ALL_SIGNS(sp)
if (sp->sn_typenr == typenr)
return sp;
return NULL;
}
/*
* Get the name of a sign by its typenr.
*/
static char_u *
sign_typenr2name(int typenr)
{
sign_T *sp = NULL;
FOR_ALL_SIGNS(sp)
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(sign_entry_T *sign)
{
dict_T *d = dict_alloc_id(aid_sign_getinfo);
if (d == NULL)
return NULL;
dict_add_number(d, "id", sign->se_id);
dict_add_string(d, "group",
(sign->se_group == NULL) ? (char_u *)""
: sign->se_group->sg_name);
dict_add_number(d, "lnum", sign->se_lnum);
dict_add_string(d, "name", sign_typenr2name(sign->se_typenr));
dict_add_number(d, "priority", sign->se_priority);
return d;
}
/*
* Sort the signs placed on the same line as "sign" by priority. Invoked after
* changing the priority of an already placed sign. Assumes the signs in the
* buffer are sorted by line number and priority.
*/
static void
sign_sort_by_prio_on_line(buf_T *buf, sign_entry_T *sign)
{
// If there is only one sign in the buffer or only one sign on the line or
// the sign is already sorted by priority, then return.
if ((sign->se_prev == NULL || sign->se_prev->se_lnum != sign->se_lnum ||
sign->se_prev->se_priority > sign->se_priority) &&
(sign->se_next == NULL || sign->se_next->se_lnum != sign->se_lnum ||
sign->se_next->se_priority < sign->se_priority))
return;
// One or more signs on the same line as 'sign'
// Find a sign after which 'sign' should be inserted
// First search backward for a sign with higher priority on the same line
sign_entry_T *p = sign;
while (p->se_prev != NULL && p->se_prev->se_lnum == sign->se_lnum &&
p->se_prev->se_priority <= sign->se_priority)
p = p->se_prev;
if (p == sign)
{
// Sign not found. Search forward for a sign with priority just before
// 'sign'.
p = sign->se_next;
while (p->se_next != NULL && p->se_next->se_lnum == sign->se_lnum &&
p->se_next->se_priority > sign->se_priority)
p = p->se_next;
}
// Remove 'sign' from the list
if (buf->b_signlist == sign)
buf->b_signlist = sign->se_next;
if (sign->se_prev != NULL)
sign->se_prev->se_next = sign->se_next;
if (sign->se_next != NULL)
sign->se_next->se_prev = sign->se_prev;
sign->se_prev = NULL;
sign->se_next = NULL;
// Re-insert 'sign' at the right place
if (p->se_priority <= sign->se_priority)
{
// 'sign' has a higher priority and should be inserted before 'p'
sign->se_prev = p->se_prev;
sign->se_next = p;
p->se_prev = sign;
if (sign->se_prev != NULL)
sign->se_prev->se_next = sign;
if (buf->b_signlist == p)
buf->b_signlist = sign;
}
else
{
// 'sign' has a lower priority and should be inserted after 'p'
sign->se_prev = p;
sign->se_next = p->se_next;
p->se_next = sign;
if (sign->se_next != NULL)
sign->se_next->se_prev = sign;
}
}
/*
* 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
{
sign_entry_T *sign = NULL; // a sign in the signlist
sign_entry_T *prev = NULL; // the previous sign
FOR_ALL_SIGNS_IN_BUF(buf, sign)
{
if (lnum == sign->se_lnum && id == sign->se_id &&
sign_in_group(sign, groupname))
{
// Update an existing sign
sign->se_typenr = typenr;
sign->se_priority = prio;
sign_sort_by_prio_on_line(buf, sign);
return;
}
else if (lnum < sign->se_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);
}
/*
* 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
int prio) // sign priority
{
sign_entry_T *sign = NULL; // a sign in the signlist
FOR_ALL_SIGNS_IN_BUF(buf, sign)
{
if (sign->se_id == markId && sign_in_group(sign, group))
{
sign->se_typenr = typenr;
sign->se_priority = prio;
sign_sort_by_prio_on_line(buf, sign);
return sign->se_lnum;
}
}
return (linenr_T)0;
}
/*
* Return the attributes of the first sign placed on line 'lnum' in buffer
* 'buf'. Used when refreshing the screen. Returns TRUE if a sign is found on
* 'lnum', FALSE otherwise.
*/
int
buf_get_signattrs(win_T *wp, linenr_T lnum, sign_attrs_T *sattr)
{
CLEAR_POINTER(sattr);
buf_T *buf = wp->w_buffer;
sign_entry_T *sign = NULL;
FOR_ALL_SIGNS_IN_BUF(buf, sign)
{
// Signs are sorted by line number in the buffer. No need to check
// for signs after the specified line number 'lnum'.
if (sign->se_lnum > lnum)
break;
if (sign->se_lnum == lnum
# ifdef FEAT_PROP_POPUP
&& sign_group_for_window(sign, wp)
# endif
)
{
sattr->sat_typenr = sign->se_typenr;
sign_T *sp = find_sign_by_typenr(sign->se_typenr);
if (sp == NULL)
return FALSE;
# ifdef FEAT_SIGN_ICONS
sattr->sat_icon = sp->sn_image;
# endif
sattr->sat_text = sp->sn_text;
if (sattr->sat_text != NULL && sp->sn_text_hl > 0)
sattr->sat_texthl = syn_id2attr(sp->sn_text_hl);
if (sp->sn_line_hl > 0)
sattr->sat_linehl = syn_id2attr(sp->sn_line_hl);
if (sp->sn_cul_hl > 0)
sattr->sat_culhl = syn_id2attr(sp->sn_cul_hl);
if (sp->sn_num_hl > 0)
sattr->sat_numhl = syn_id2attr(sp->sn_num_hl);
sattr->sat_priority = sign->se_priority;
// If there is another sign next with the same priority, may
// combine the text and the line highlighting.
if (sign->se_next != NULL &&
sign->se_next->se_priority == sign->se_priority &&
sign->se_next->se_lnum == sign->se_lnum)
{
sign_T *next_sp = find_sign_by_typenr(sign->se_next->se_typenr);
if (next_sp == NULL)
return FALSE;
if (sattr->sat_icon == NULL && sattr->sat_text == NULL)
{
# ifdef FEAT_SIGN_ICONS
sattr->sat_icon = next_sp->sn_image;
# endif
sattr->sat_text = next_sp->sn_text;
}
if (sp->sn_text_hl <= 0 && next_sp->sn_text_hl > 0)
sattr->sat_texthl = syn_id2attr(next_sp->sn_text_hl);
if (sp->sn_line_hl <= 0 && next_sp->sn_line_hl > 0)
sattr->sat_linehl = syn_id2attr(next_sp->sn_line_hl);
if (sp->sn_cul_hl <= 0 && next_sp->sn_cul_hl > 0)
sattr->sat_culhl = syn_id2attr(next_sp->sn_cul_hl);
if (sp->sn_num_hl <= 0 && next_sp->sn_num_hl > 0)
sattr->sat_numhl = syn_id2attr(next_sp->sn_num_hl);
}
return TRUE;
}
}
return FALSE;
}
/*
* 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
{
// pointer to pointer to current sign
sign_entry_T **lastp = &buf->b_signlist;
sign_entry_T *next = NULL; // the next sign in a b_signlist
linenr_T lnum = 0; // line number whose sign was deleted
for (sign_entry_T *sign = buf->b_signlist; sign != NULL; sign = next)
{
next = sign->se_next;
if ((id == 0 || sign->se_id == id) &&
(atlnum == 0 || sign->se_lnum == atlnum) &&
sign_in_group(sign, group))
{
*lastp = next;
if (next != NULL)
next->se_prev = sign->se_prev;
lnum = sign->se_lnum;
if (sign->se_group != NULL)
sign_group_unref(sign->se_group->sg_name);
vim_free(sign);
redraw_buf_line_later(buf, lnum);
// Check whether only one sign needs to be deleted
// If deleting a sign with a specific identifier 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->se_next;
}
}
// When deleting the last sign the cursor position may change, because the
// sign columns no longer shows. And the 'signcolumn' may be hidden.
if (buf->b_signlist == NULL)
{
redraw_buf_later(buf, UPD_NOT_VALID);
changed_line_abv_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
{
sign_entry_T *sign = NULL; // a sign in the signlist
FOR_ALL_SIGNS_IN_BUF(buf, sign)
if (sign->se_id == id && sign_in_group(sign, group))
return sign->se_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 sign_entry_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
{
sign_entry_T *sign = NULL; // a sign in the signlist
FOR_ALL_SIGNS_IN_BUF(buf, sign)
{
// Signs are sorted by line number in the buffer. No need to check
// for signs after the specified line number 'lnum'.
if (sign->se_lnum > lnum)
break;
if (sign->se_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
{
// a sign in the signlist
sign_entry_T *sign = buf_getsign_at_line(buf, lnum, groupname);
if (sign != NULL)
return sign->se_id;
return 0;
}
# if defined(FEAT_NETBEANS_INTG)
/*
* 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
{
sign_entry_T *sign = NULL; // a sign in the signlist
FOR_ALL_SIGNS_IN_BUF(buf, sign)
{
// Signs are sorted by line number in the buffer. No need to check
// for signs after the specified line number 'lnum'.
if (sign->se_lnum > lnum)
break;
if (sign->se_lnum == lnum && sign->se_typenr == typenr)
return sign->se_id;
}
return 0;
}
# if defined(FEAT_SIGN_ICONS)
/*
* Return the number of icons on the given line.
*/
int
buf_signcount(buf_T *buf, linenr_T lnum)
{
int count = 0;
sign_entry_T *sign = NULL; // a sign in the signlist
FOR_ALL_SIGNS_IN_BUF(buf, sign)
{
// Signs are sorted by line number in the buffer. No need to check
// for signs after the specified line number 'lnum'.
if (sign->se_lnum > lnum)
break;
if (sign->se_lnum == lnum && sign_get_image(sign->se_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)
{
// 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, UPD_NOT_VALID);
changed_line_abv_curs();
}
// pointer to pointer to current sign
sign_entry_T **lastp = &buf->b_signlist;
sign_entry_T *next = NULL;
for (sign_entry_T *sign = buf->b_signlist; sign != NULL; sign = next)
{
next = sign->se_next;
if (sign_in_group(sign, group))
{
*lastp = next;
if (next != NULL)
next->se_prev = sign->se_prev;
if (sign->se_group != NULL)
sign_group_unref(sign->se_group->sg_name);
vim_free(sign);
}
else
{
lastp = &sign->se_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)
{
char lbuf[MSG_BUF_LEN];
char group[MSG_BUF_LEN];
msg_puts_title(_("\n--- Signs ---"));
msg_putchar('\n');
buf_T *buf = (rbuf == NULL) ? firstbuf : rbuf;
while (buf != NULL && !got_int)
{
if (buf->b_signlist != NULL)
{
vim_snprintf(lbuf, MSG_BUF_LEN, _("Signs for %s:"), buf->b_fname);
msg_puts_attr(lbuf, HL_ATTR(HLF_D));
msg_putchar('\n');
}
sign_entry_T *sign = NULL;
FOR_ALL_SIGNS_IN_BUF(buf, sign)
{
if (got_int)
break;
if (!sign_in_group(sign, sign_group))
continue;
if (sign->se_group != NULL)
vim_snprintf(group, MSG_BUF_LEN, _(" group=%s"),
sign->se_group->sg_name);
else
group[0] = '\0';
vim_snprintf(lbuf, MSG_BUF_LEN,
_(" line=%ld id=%d%s name=%s priority=%d"),
(long)sign->se_lnum, sign->se_id, group,
sign_typenr2name(sign->se_typenr), sign->se_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)
{
sign_entry_T *sign = NULL; // a sign in a b_signlist
FOR_ALL_SIGNS_IN_BUF(curbuf, sign)
{
// Ignore changes to lines after the sign
if (sign->se_lnum < line1)
continue;
linenr_T new_lnum = sign->se_lnum;
if (sign->se_lnum <= line2)
{
if (amount != MAXLNUM)
new_lnum += amount;
}
else if (sign->se_lnum > line2)
{
// Lines inserted or deleted before the sign
new_lnum += amount_after;
}
// If the new sign line number is past the last line in the buffer,
// then don't adjust the line number. Otherwise, it will always be past
// the last line and will not be visible.
if (new_lnum <= curbuf->b_ml.ml_line_count)
sign->se_lnum = new_lnum;
}
}
/*
* 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 = 0;
char save = *end_cmd;
*end_cmd = NUL;
while (cmds[idx] != NULL && STRCMP(begin_cmd, cmds[idx]) != 0)
++idx;
*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)
{
if (sp_prev != NULL)
*sp_prev = NULL;
sign_T *sp = NULL;
FOR_ALL_SIGNS(sp)
{
if (STRCMP(sp->sn_name, name) == 0)
break;
if (sp_prev != NULL)
*sp_prev = sp;
}
return sp;
}
/*
* Allocate a new sign
*/
static sign_T *
alloc_new_sign(char_u *name)
{
int start = next_sign_typenr;
// Allocate a new sign.
sign_T *sp = alloc_clear_id(sizeof(sign_T), aid_sign_define_by_name);
if (sp == NULL)
return NULL;
// 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.
sign_T *lp = first_sign;
while (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(_(e_too_many_signs_defined));
return NULL;
}
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 NULL;
}
return sp;
}
/*
* Initialize the icon information for a new sign
*/
static void
sign_define_init_icon(sign_T *sp, char_u *icon)
{
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
}
/*
* Initialize the text for a new sign
*/
static int
sign_define_init_text(sign_T *sp, char_u *text)
{
char_u *s = NULL;
char_u *endp = text + (int)STRLEN(text);
int cells = 0;
// Remove backslashes so that it is possible to use a space.
for (s = text; s + 1 < endp; ++s)
{
if (*s == '\\')
{
STRMOVE(s, s + 1);
--endp;
}
}
// Count cells and check for non-printable chars
if (has_mbyte)
{
for (s = text; s < endp; s += (*mb_ptr2len)(s))
{
if (!vim_isprintc((*mb_ptr2char)(s)))
break;
cells += (*mb_ptr2cells)(s);
}
}
else
{
for (s = text; s < endp; ++s)
{
if (!vim_isprintc(*s))
break;
}
cells = (int)(s - text);
}
// Currently sign text must be one or two display cells
if (s != endp || cells < 1 || cells > 2)
{
semsg(_(e_invalid_sign_text_str), text);
return FAIL;
}
vim_free(sp->sn_text);
// Allocate one byte more if we need to pad up
// with a space.
int len = (int)(endp - text + ((cells == 1) ? 1 : 0));
sp->sn_text = vim_strnsave(text, len);
// For single character sign text, pad with a space.
if (sp->sn_text != NULL && cells == 1)
STRCPY(sp->sn_text + len - 1, " ");
return OK;
}
/*
* 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,
char_u *culhl,
char_u *numhl,
int prio)
{
sign_T *sp_prev = NULL;
sign_T *sp = sign_find(name, &sp_prev);
if (sp == NULL)
{
sp = alloc_new_sign(name);
if (sp == NULL)
return FAIL;
// add the new sign to the list of signs
if (sp_prev == NULL)
first_sign = sp;
else
sp_prev->sn_next = sp;
}
else
{
win_T *wp = NULL;
// Signs may already exist, a redraw is needed in windows with a
// non-empty sign list.
FOR_ALL_WINDOWS(wp)
{
if (wp->w_buffer->b_signlist != NULL)
redraw_buf_later(wp->w_buffer, UPD_NOT_VALID);
}
}
// set values for a defined sign.
if (icon != NULL)
sign_define_init_icon(sp, icon);
if (text != NULL && (sign_define_init_text(sp, text) == FAIL))
return FAIL;
sp->sn_priority = prio;
if (linehl != NULL)
{
if (*linehl == NUL)
sp->sn_line_hl = 0;
else
sp->sn_line_hl = syn_check_group(linehl, (int)STRLEN(linehl));
}
if (texthl != NULL)
{
if (*texthl == NUL)
sp->sn_text_hl = 0;
else
sp->sn_text_hl = syn_check_group(texthl, (int)STRLEN(texthl));
}
if (culhl != NULL)
{
if (*culhl == NUL)
sp->sn_cul_hl = 0;
else
sp->sn_cul_hl = syn_check_group(culhl, (int)STRLEN(culhl));
}
if (numhl != NULL)
{
if (*numhl == NUL)
sp->sn_num_hl = 0;
else
sp->sn_num_hl = syn_check_group(numhl, (int)STRLEN(numhl));
}
return OK;
}
/*
* Return TRUE if sign "name" exists.
*/
int
sign_exists_by_name(char_u *name)
{
return sign_find(name, NULL) != NULL;
}
/*
* Free the sign specified by 'name'.
*/
int
sign_undefine_by_name(char_u *name, int give_error)
{
sign_T *sp_prev = NULL;
sign_T *sp = sign_find(name, &sp_prev);
if (sp == NULL)
{
if (give_error)
semsg(_(e_unknown_sign_str), 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 = sign_find(name, NULL);
if (sp != NULL)
sign_list_defined(sp);
else
semsg(_(e_unknown_sign_str), name);
}
static void
may_force_numberwidth_recompute(buf_T *buf, int unplace)
{
# if defined(FEAT_LINEBREAK)
tabpage_T *tp = NULL;
win_T *wp = NULL;
FOR_ALL_TAB_WINDOWS(tp, wp)
{
if (wp->w_buffer == buf && (wp->w_p_nu || wp->w_p_rnu) &&
(unplace || wp->w_nrwidth_width < 2) &&
(*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u'))
wp->w_nrwidth_line_count = 0;
}
# endif
}
/*
* Place a sign at the specified 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)
{
// Check for reserved character '*' in group name
if (sign_group != NULL && (*sign_group == '*' || *sign_group == '\0'))
return FAIL;
sign_T *sp = NULL;
FOR_ALL_SIGNS(sp)
{
if (STRCMP(sp->sn_name, sign_name) == 0)
break;
}
if (sp == NULL)
{
semsg(_(e_unknown_sign_str), sign_name);
return FAIL;
}
if (*sign_id == 0)
*sign_id = sign_group_get_next_signid(buf, sign_group);
// Use the default priority value for this sign.
if (prio == -1)
prio = (sp->sn_priority != -1) ? sp->sn_priority : SIGN_DEF_PRIO;
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 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);
// When displaying signs in the 'number' column, if the width of the
// number column is less than 2, then force recomputing the width.
may_force_numberwidth_recompute(buf, FALSE);
}
else
{
semsg(_(e_not_possible_to_change_sign_str), sign_name);
return FAIL;
}
return OK;
}
/*
* Unplace the specified sign
*/
static 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, UPD_NOT_VALID);
buf_delete_signs(buf, sign_group);
}
else
{
// Delete only the specified signs
linenr_T lnum = buf_delsign(buf, atlnum, sign_id, sign_group);
if (lnum == 0)
return FAIL;
}
// When all the signs in a buffer are removed, force recomputing the
// number column width (if enabled) in all the windows displaying the
// buffer if 'signcolumn' is set to 'number' in that window.
if (buf->b_signlist == NULL)
may_force_numberwidth_recompute(buf, TRUE);
return OK;
}
/*
* Unplace the sign at the current cursor line.
*/
static void
sign_unplace_at_cursor(char_u *groupname)
{
int 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(_(e_missing_sign_number));
}
/*
* Jump to a sign.
*/
static linenr_T
sign_jump(int sign_id, char_u *sign_group, buf_T *buf)
{
linenr_T lnum = buf_findsign(buf, sign_id, sign_group);
if (lnum <= 0)
{
semsg(_(e_invalid_sign_id_nr), sign_id);
return -1;
}
// 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
if (buf->b_fname == NULL)
{
emsg(_(e_cannot_jump_to_buffer_that_does_not_have_name));
return -1;
}
char_u *cmd = alloc(STRLEN(buf->b_fname) + 25);
if (cmd == NULL)
return -1;
sprintf((char *)cmd, "e +%ld %s", (long)lnum, buf->b_fname);
do_cmdline_cmd(cmd);
vim_free(cmd);
}
# ifdef FEAT_FOLDING
foldOpenCursor();
# endif
return lnum;
}
/*
* ":sign define {name} ..." command
*/
static void
sign_define_cmd(char_u *sign_name, char_u *cmdline)
{
char_u *arg = NULL;
char_u *p = cmdline;
char_u *icon = NULL;
char_u *text = NULL;
char_u *linehl = NULL;
char_u *texthl = NULL;
char_u *culhl = NULL;
char_u *numhl = NULL;
int prio = -1;
int failed = FALSE;
// set values for a defined sign.
while (TRUE)
{
arg = skipwhite(p);
if (*arg == NUL)
break;
p = skiptowhite_esc(arg);
if (STRNCMP(arg, "icon=", 5) == 0)
{
arg += 5;
icon = vim_strnsave(arg, p - arg);
}
else if (STRNCMP(arg, "text=", 5) == 0)
{
arg += 5;
text = vim_strnsave(arg, p - arg);
}
else if (STRNCMP(arg, "linehl=", 7) == 0)
{
arg += 7;
linehl = vim_strnsave(arg, p - arg);
}
else if (STRNCMP(arg, "texthl=", 7) == 0)
{
arg += 7;
texthl = vim_strnsave(arg, p - arg);
}
else if (STRNCMP(arg, "culhl=", 6) == 0)
{
arg += 6;
culhl = vim_strnsave(arg, p - arg);
}
else if (STRNCMP(arg, "numhl=", 6) == 0)
{
arg += 6;
numhl = vim_strnsave(arg, p - arg);
}
else if (STRNCMP(arg, "priority=", 9) == 0)
{
arg += 9;
prio = atoi((char *)arg);
}
else
{
semsg(_(e_invalid_argument_str), arg);
failed = TRUE;
break;
}
}
if (!failed)
sign_define_by_name(sign_name, icon, linehl, text, texthl, culhl, numhl,
prio);
vim_free(icon);
vim_free(text);
vim_free(linehl);
vim_free(texthl);
vim_free(culhl);
vim_free(numhl);
}
/*
* ":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_invalid_argument));
else
sign_list_placed(buf, group);
}
else
{
// Place a new sign
if (sign_name == NULL || buf == NULL ||
(group != NULL && *group == '\0'))
{
emsg(_(e_invalid_argument));
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_invalid_argument));
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 commands:
* :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 (sign_name == NULL && group == NULL && id == -1)
{
emsg(_(e_argument_required));
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_invalid_argument));
return;
}
(void)sign_jump(id, group, buf);
}
/*
* 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 = arg;
char_u *filename = NULL;
int lnum_arg = FALSE;
// first arg could be placed sign id
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);
lnum_arg = TRUE;
}
else if (STRNCMP(arg, "*", 1) == 0 && cmd == SIGNCMD_UNPLACE)
{
if (*signid != -1)
{
emsg(_(e_invalid_argument));
return FAIL;
}
*signid = -2;
arg = skiptowhite(arg + 1);
}
else if (STRNCMP(arg, "name=", 5) == 0)
{
arg += 5;
char_u *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)
semsg(_(e_trailing_characters_str), arg);
break;
}
else
{
emsg(_(e_invalid_argument));
return FAIL;
}
arg = skipwhite(arg);
}
if (filename != NULL && *buf == NULL)
{
semsg(_(e_invalid_buffer_name_str), filename);
return FAIL;
}
// If the filename is not supplied for the sign place or the sign jump
// command, then use the current buffer.
if (filename == NULL &&
((cmd == SIGNCMD_PLACE && lnum_arg) || cmd == SIGNCMD_JUMP))
*buf = curwin->w_buffer;
return OK;
}
/*
* ":sign" command
*/
void
ex_sign(exarg_T *eap)
{
char_u *arg = eap->arg;
// Parse the subcommand.
char_u *p = skiptowhite(arg);
int idx = sign_cmd_idx(arg, p);
if (idx == SIGNCMD_LAST)
{
semsg(_(e_unknown_sign_command_str), arg);
return;
}
arg = skipwhite(p);
if (idx > SIGNCMD_LIST)
{
char_u *sign_name = NULL;
int id = -1;
char_u *group = NULL;
int prio = -1;
buf_T *buf = NULL;
linenr_T lnum = -1;
// 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;
}
// Define, undefine or list signs.
if (idx == SIGNCMD_LIST && *arg == NUL)
{
// ":sign list": list all defined signs
for (sign_T *sp = first_sign; sp != NULL && !got_int; sp = sp->sn_next)
sign_list_defined(sp);
}
else if (*arg == NUL)
{
emsg(_(e_missing_sign_name));
}
else
{
// 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;
char_u *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, TRUE);
vim_free(name);
}
}
/*
* Return information about a specified sign
*/
static void
sign_getinfo(sign_T *sp, dict_T *retdict)
{
dict_add_string(retdict, "name", sp->sn_name);
if (sp->sn_icon != NULL)
dict_add_string(retdict, "icon", sp->sn_icon);
if (sp->sn_text != NULL)
dict_add_string(retdict, "text", sp->sn_text);
if (sp->sn_priority > 0)
dict_add_number(retdict, "priority", sp->sn_priority);
if (sp->sn_line_hl > 0)
{
char_u *p = get_highlight_name_ext(NULL, sp->sn_line_hl - 1, FALSE);
if (p == NULL)
p = (char_u *)"NONE";
dict_add_string(retdict, "linehl", p);
}
if (sp->sn_text_hl > 0)
{
char_u *p = get_highlight_name_ext(NULL, sp->sn_text_hl - 1, FALSE);
if (p == NULL)
p = (char_u *)"NONE";
dict_add_string(retdict, "texthl", p);
}
if (sp->sn_cul_hl > 0)
{
char_u *p = get_highlight_name_ext(NULL, sp->sn_cul_hl - 1, FALSE);
if (p == NULL)
p = (char_u *)"NONE";
dict_add_string(retdict, "culhl", p);
}
if (sp->sn_num_hl > 0)
{
char_u *p = get_highlight_name_ext(NULL, sp->sn_num_hl - 1, FALSE);
if (p == NULL)
p = (char_u *)"NONE";
dict_add_string(retdict, "numhl", p);
}
}
/*
* If 'name' is NULL, return a list of all the defined signs.
* Otherwise, return information about the specified sign.
*/
static void
sign_getlist(char_u *name, list_T *retlist)
{
sign_T *sp = first_sign;
if (name != NULL)
{
sp = sign_find(name, NULL);
if (sp == NULL)
return;
}
for (; sp != NULL && !got_int; sp = sp->sn_next)
{
dict_T *dict = dict_alloc_id(aid_sign_getlist);
if (dict == 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)
{
sign_entry_T *sign = NULL;
FOR_ALL_SIGNS_IN_BUF(buf, sign)
{
dict_T *d = sign_get_info(sign);
if (d != 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 = dict_alloc_id(aid_sign_getplaced_dict);
if (d == NULL)
return;
list_append_dict(retlist, d);
dict_add_number(d, "bufnr", (long)buf->b_fnum);
list_T *l = list_alloc_id(aid_sign_getplaced_list);
if (l == NULL)
return;
dict_add_list(d, "signs", l);
sign_entry_T *sign = NULL;
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->se_lnum) ||
(lnum == 0 && sign_id == sign->se_id) ||
(lnum == sign->se_lnum && sign_id == sign->se_id))
{
dict_T *sdict = sign_get_info(sign);
if (sdict != 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.
*/
static 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)
/*
* Allocate the icons. Called when the GUI has started. Allows defining
* signs before it starts.
*/
void
sign_gui_started(void)
{
sign_T *sp = NULL;
FOR_ALL_SIGNS(sp)
{
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 lbuf[MSG_BUF_LEN];
smsg("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_priority > 0)
{
vim_snprintf(lbuf, MSG_BUF_LEN, " priority=%d", sp->sn_priority);
msg_puts(lbuf);
}
if (sp->sn_line_hl > 0)
{
msg_puts(" linehl=");
char_u *p = get_highlight_name_ext(NULL, sp->sn_line_hl - 1, FALSE);
if (p == NULL)
msg_puts("NONE");
else
msg_puts((char *)p);
}
if (sp->sn_text_hl > 0)
{
msg_puts(" texthl=");
char_u *p = get_highlight_name_ext(NULL, sp->sn_text_hl - 1, FALSE);
if (p == NULL)
msg_puts("NONE");
else
msg_puts((char *)p);
}
if (sp->sn_cul_hl > 0)
{
msg_puts(" culhl=");
char_u *p = get_highlight_name_ext(NULL, sp->sn_cul_hl - 1, FALSE);
if (p == NULL)
msg_puts("NONE");
else
msg_puts((char *)p);
}
if (sp->sn_num_hl > 0)
{
msg_puts(" numhl=");
char_u *p = get_highlight_name_ext(NULL, sp->sn_num_hl - 1, FALSE);
if (p == NULL)
msg_puts("NONE");
else
msg_puts((char *)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);
}
# if defined(FEAT_SIGN_ICONS)
void *
sign_get_image(int typenr) // the attribute which may have a sign
{
sign_T *sp = NULL;
FOR_ALL_SIGNS(sp)
{
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);
}
static enum
{
EXP_SUBCMD, // expand :sign sub-commands
EXP_DEFINE, // expand :sign define {name} args
EXP_PLACE, // expand :sign place {id} args
EXP_LIST, // expand :sign place args
EXP_UNPLACE, // expand :sign unplace"
EXP_SIGN_NAMES, // expand with name of placed signs
EXP_SIGN_GROUPS // expand with name of placed sign groups
} expand_what;
/*
* Return the n'th sign name (used for command line completion)
*/
static char_u *
get_nth_sign_name(int idx)
{
int current_idx = 0;
sign_T *sp = NULL;
// Complete with name of signs already defined
FOR_ALL_SIGNS(sp)
{
if (current_idx++ == idx)
return sp->sn_name;
}
return NULL;
}
/*
* Return the n'th sign group name (used for command line completion)
*/
static char_u *
get_nth_sign_group_name(int idx)
{
int current_idx = 0;
int todo = (int)sg_table.ht_used;
hashitem_T *hi = NULL;
// Complete with name of sign groups already defined
FOR_ALL_HASHTAB_ITEMS(&sg_table, hi, todo)
{
if (!HASHITEM_EMPTY(hi))
{
--todo;
if (current_idx++ == idx)
{
signgroup_T *group = HI2SG(hi);
return group->sg_name;
}
}
}
return NULL;
}
/*
* Function given to ExpandGeneric() to obtain the sign command
* expansion.
*/
char_u *
get_sign_name(expand_T *xp UNUSED, int idx)
{
switch (expand_what)
{
case EXP_SUBCMD:
return (char_u *)cmds[idx];
case EXP_DEFINE:
{
char *define_arg[] = { "culhl=", "icon=", "linehl=", "numhl=",
"text=", "texthl=", "priority=", 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_LIST:
{
char *list_arg[] = { "group=", "file=", "buffer=", NULL };
return (char_u *)list_arg[idx];
}
case EXP_UNPLACE:
{
char *unplace_arg[] = { "group=", "file=", "buffer=", NULL };
return (char_u *)unplace_arg[idx];
}
case EXP_SIGN_NAMES:
return get_nth_sign_name(idx);
case EXP_SIGN_GROUPS:
return get_nth_sign_group_name(idx);
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);
// expand subcmd name
// :sign {subcmd}<CTRL-D>
if (*end_subcmd == NUL)
return;
cmd_idx = sign_cmd_idx(arg, end_subcmd);
// :sign {subcmd} {subcmd_args}
// |
// begin_subcmd_args
begin_subcmd_args = skipwhite(end_subcmd);
// expand last argument of subcmd
// :sign define {name} {args}...
// |
// p
// Loop until reaching last argument.
p = begin_subcmd_args;
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:
// List placed signs
if (VIM_ISDIGIT(*begin_subcmd_args))
// :sign place {id} {args}...
expand_what = EXP_PLACE;
else
// :sign place {args}...
expand_what = EXP_LIST;
break;
case SIGNCMD_LIST:
case SIGNCMD_UNDEFINE:
// :sign list <CTRL-D>
// :sign undefine <CTRL-D>
expand_what = EXP_SIGN_NAMES;
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", 6) == 0 ||
STRNCMP(last, "linehl", 6) == 0 ||
STRNCMP(last, "culhl", 5) == 0 ||
STRNCMP(last, "numhl", 5) == 0)
xp->xp_context = EXPAND_HIGHLIGHT;
else if (STRNCMP(last, "icon", 4) == 0)
xp->xp_context = EXPAND_FILES;
else
xp->xp_context = EXPAND_NOTHING;
break;
case SIGNCMD_PLACE:
if (STRNCMP(last, "name", 4) == 0)
expand_what = EXP_SIGN_NAMES;
else if (STRNCMP(last, "group", 5) == 0)
expand_what = EXP_SIGN_GROUPS;
else if (STRNCMP(last, "file", 4) == 0)
xp->xp_context = EXPAND_BUFFERS;
else
xp->xp_context = EXPAND_NOTHING;
break;
case SIGNCMD_UNPLACE:
case SIGNCMD_JUMP:
if (STRNCMP(last, "group", 5) == 0)
expand_what = EXP_SIGN_GROUPS;
else if (STRNCMP(last, "file", 4) == 0)
xp->xp_context = EXPAND_BUFFERS;
else
xp->xp_context = EXPAND_NOTHING;
break;
default:
xp->xp_context = EXPAND_NOTHING;
}
}
}
/*
* 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;
char_u *culhl = NULL;
char_u *numhl = NULL;
int prio = -1;
int retval = -1;
if (name_arg == NULL && dict == NULL)
return retval;
if (name_arg == NULL)
name = dict_get_string(dict, "name", TRUE);
else
name = vim_strsave(name_arg);
if (name == NULL || name[0] == NUL)
goto cleanup;
if (dict != NULL)
{
icon = dict_get_string(dict, "icon", TRUE);
linehl = dict_get_string(dict, "linehl", TRUE);
text = dict_get_string(dict, "text", TRUE);
texthl = dict_get_string(dict, "texthl", TRUE);
culhl = dict_get_string(dict, "culhl", TRUE);
numhl = dict_get_string(dict, "numhl", TRUE);
prio = dict_get_number_def(dict, "priority", -1);
}
if (sign_define_by_name(name, icon, linehl, text, texthl, culhl, numhl,
prio) == OK)
retval = 0;
cleanup:
vim_free(name);
vim_free(icon);
vim_free(linehl);
vim_free(text);
vim_free(texthl);
vim_free(culhl);
vim_free(numhl);
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 = NULL;
FOR_ALL_LIST_ITEMS(l, li)
{
int 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_dictionary_required));
list_append_number(retlist, retval);
}
}
/*
* "sign_define()" function
*/
void
f_sign_define(typval_T *argvars, typval_T *rettv)
{
if (in_vim9script() && (check_for_string_or_list_arg(argvars, 0) == FAIL ||
check_for_opt_dict_arg(argvars, 1) == FAIL))
return;
if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN)
{
// Define multiple signs
if (rettv_list_alloc(rettv) == FAIL)
return;
sign_define_multiple(argvars[0].vval.v_list, rettv->vval.v_list);
return;
}
// Define a single sign
rettv->vval.v_number = -1;
char_u *name = tv_get_string_chk(&argvars[0]);
if (name == NULL)
return;
if (check_for_opt_dict_arg(argvars, 1) == FAIL)
return;
rettv->vval.v_number = sign_define_from_dict(
name, argvars[1].v_type == VAR_DICT ? argvars[1].vval.v_dict : NULL);
}
/*
* "sign_getdefined()" function
*/
void
f_sign_getdefined(typval_T *argvars, typval_T *rettv)
{
if (rettv_list_alloc_id(rettv, aid_sign_getdefined) == FAIL)
return;
if (in_vim9script() && check_for_opt_string_arg(argvars, 0) == FAIL)
return;
char_u *name = NULL;
if (argvars[0].v_type != VAR_UNKNOWN)
name = tv_get_string(&argvars[0]);
sign_getlist(name, rettv->vval.v_list);
}
/*
* "sign_getplaced()" function
*/
void
f_sign_getplaced(typval_T *argvars, typval_T *rettv)
{
buf_T *buf = NULL;
linenr_T lnum = 0;
int sign_id = 0;
char_u *group = NULL;
if (rettv_list_alloc_id(rettv, aid_sign_getplaced) == FAIL)
return;
if (in_vim9script() && (check_for_opt_buffer_arg(argvars, 0) == FAIL ||
(argvars[0].v_type != VAR_UNKNOWN &&
check_for_opt_dict_arg(argvars, 1) == FAIL)))
return;
if (argvars[0].v_type != VAR_UNKNOWN)
{
// get signs placed in the specified buffer
buf = get_buf_arg(&argvars[0]);
if (buf == NULL)
return;
if (argvars[1].v_type != VAR_UNKNOWN)
{
if (check_for_nonnull_dict_arg(argvars, 1) == FAIL)
return;
dictitem_T *di = NULL;
dict_T *dict = argvars[1].vval.v_dict;
if ((di = dict_find(dict, (char_u *)"lnum", -1)) != NULL)
{
// get signs placed at this line
int notanum = FALSE;
tv_get_number_chk(&di->di_tv, &notanum);
if (notanum)
return;
lnum = tv_get_lnum(&di->di_tv);
}
if ((di = dict_find(dict, (char_u *)"id", -1)) != NULL)
{
// get sign placed with this identifier
int notanum = FALSE;
sign_id = (int)tv_get_number_chk(&di->di_tv, &notanum);
if (notanum)
return;
}
if ((di = dict_find(dict, (char_u *)"group", -1)) != NULL)
{
group = tv_get_string_chk(&di->di_tv);
if (group == NULL)
return;
if (*group == '\0') // empty string means global group
group = NULL;
}
}
}
sign_get_placed(buf, lnum, sign_id, group, rettv->vval.v_list);
}
/*
* "sign_jump()" function
*/
void
f_sign_jump(typval_T *argvars, typval_T *rettv)
{
char_u *sign_group = NULL;
rettv->vval.v_number = -1;
if (in_vim9script() && (check_for_number_arg(argvars, 0) == FAIL ||
check_for_string_arg(argvars, 1) == FAIL ||
check_for_buffer_arg(argvars, 2) == FAIL))
return;
int notanum = FALSE;
// Sign identifier
int sign_id = (int)tv_get_number_chk(&argvars[0], &notanum);
if (notanum)
return;
if (sign_id <= 0)
{
emsg(_(e_invalid_argument));
return;
}
// Sign group
sign_group = tv_get_string_chk(&argvars[1]);
if (sign_group == NULL)
return;
if (sign_group[0] == '\0')
{
sign_group = NULL; // global sign group
}
else
{
sign_group = vim_strsave(sign_group);
if (sign_group == NULL)
return;
}
// Buffer to place the sign
buf_T *buf = get_buf_arg(&argvars[2]);
if (buf == NULL)
goto cleanup;
rettv->vval.v_number = sign_jump(sign_id, sign_group, buf);
cleanup:
vim_free(sign_group);
}
/*
* 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 = NULL;
linenr_T lnum = 0;
int prio = -1;
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
{
int notanum = FALSE;
sign_id = tv_get_number_chk(id_tv, &notanum);
if (notanum)
return -1;
if (sign_id < 0)
{
emsg(_(e_invalid_argument));
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 = tv_get_lnum(&di->di_tv);
if (lnum <= 0)
{
emsg(_(e_invalid_argument));
goto cleanup;
}
}
// sign priority
di = dict_find(dict, (char_u *)"priority", -1);
if (di != NULL)
{
int notanum = FALSE;
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)
{
dict_T *dict = NULL;
rettv->vval.v_number = -1;
if (in_vim9script() && (check_for_number_arg(argvars, 0) == FAIL ||
check_for_string_arg(argvars, 1) == FAIL ||
check_for_string_arg(argvars, 2) == FAIL ||
check_for_buffer_arg(argvars, 3) == FAIL ||
check_for_opt_dict_arg(argvars, 4) == FAIL))
return;
if (argvars[4].v_type != VAR_UNKNOWN)
{
if (check_for_nonnull_dict_arg(argvars, 4) == FAIL)
return;
dict = argvars[4].vval.v_dict;
}
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)
{
if (rettv_list_alloc(rettv) == FAIL)
return;
if (in_vim9script() && check_for_list_arg(argvars, 0) == FAIL)
return;
if (check_for_list_arg(argvars, 0) == FAIL)
return;
// Process the List of sign attributes
listitem_T *li = NULL;
FOR_ALL_LIST_ITEMS(argvars[0].vval.v_list, li)
{
int 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_dictionary_required));
list_append_number(rettv->vval.v_list, sign_id);
}
}
/*
* Undefine multiple signs
*/
static void
sign_undefine_multiple(list_T *l, list_T *retlist)
{
listitem_T *li = NULL;
FOR_ALL_LIST_ITEMS(l, li)
{
int retval = -1;
char_u *name = tv_get_string_chk(&li->li_tv);
if (name != NULL && (sign_undefine_by_name(name, TRUE) == OK))
retval = 0;
list_append_number(retlist, retval);
}
}
/*
* "sign_undefine()" function
*/
void
f_sign_undefine(typval_T *argvars, typval_T *rettv)
{
if (in_vim9script() && check_for_opt_string_or_list_arg(argvars, 0) == FAIL)
return;
if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN)
{
// Undefine multiple signs
if (rettv_list_alloc(rettv) == FAIL)
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)
{
// Free all the signs
free_signs();
rettv->vval.v_number = 0;
}
else
{
// Free only the specified sign
char_u *name = tv_get_string_chk(&argvars[0]);
if (name == NULL)
return;
if (sign_undefine_by_name(name, TRUE) == OK)
rettv->vval.v_number = 0;
}
}
/*
* 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)
{
int sign_id = 0;
buf_T *buf = NULL;
char_u *group = NULL;
int retval = -1;
// sign group
group = (group_tv != NULL) ? tv_get_string(group_tv)
: dict_get_string(dict, "group", FALSE);
if (group != NULL)
{
if (group[0] == '\0') // global sign group
{
group = NULL;
}
else
{
group = vim_strsave(group);
if (group == NULL)
return retval;
}
}
if (dict != NULL)
{
dictitem_T *di = dict_find(dict, (char_u *)"buffer", -1);
if (di != NULL)
{
buf = get_buf_arg(&di->di_tv);
if (buf == NULL)
goto cleanup;
}
if (dict_has_key(dict, "id"))
{
sign_id = dict_get_number(dict, "id");
if (sign_id <= 0)
{
emsg(_(e_invalid_argument));
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_entry_T *
get_first_valid_sign(win_T *wp)
{
sign_entry_T *sign = wp->w_buffer->b_signlist;
# ifdef FEAT_PROP_POPUP
while (sign != NULL && !sign_group_for_window(sign, wp))
sign = sign->se_next;
# endif
return sign;
}
/*
* Return TRUE when window "wp" has a column to draw signs in.
*/
int
signcolumn_on(win_T *wp)
{
// If 'signcolumn' is set to 'number', signs are displayed in the 'number'
// column (if present). Otherwise signs are to be displayed in the sign
// column.
if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u')
return get_first_valid_sign(wp) != NULL && !wp->w_p_nu && !wp->w_p_rnu;
if (*wp->w_p_scl == 'n')
return FALSE;
if (*wp->w_p_scl == 'y')
return TRUE;
return (get_first_valid_sign(wp) != NULL
# ifdef FEAT_NETBEANS_INTG
|| wp->w_buffer->b_has_sign_column
# endif
);
}
/*
* "sign_unplace()" function
*/
void
f_sign_unplace(typval_T *argvars, typval_T *rettv)
{
dict_T *dict = NULL;
rettv->vval.v_number = -1;
if ((check_for_string_arg(argvars, 0) == FAIL ||
check_for_opt_dict_arg(argvars, 1) == FAIL))
return;
if (argvars[1].v_type != VAR_UNKNOWN)
dict = argvars[1].vval.v_dict;
rettv->vval.v_number = sign_unplace_from_dict(&argvars[0], dict);
}
/*
* "sign_unplacelist()" function
*/
void
f_sign_unplacelist(typval_T *argvars, typval_T *rettv)
{
if (rettv_list_alloc(rettv) == FAIL)
return;
if (in_vim9script() && check_for_list_arg(argvars, 0) == FAIL)
return;
if (check_for_list_arg(argvars, 0) == FAIL)
return;
listitem_T *li = NULL;
FOR_ALL_LIST_ITEMS(argvars[0].vval.v_list, li)
{
int 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_dictionary_required));
list_append_number(rettv->vval.v_list, retval);
}
}
#endif // FEAT_SIGNS