xref: /vim-8.2.3635/src/sign.c (revision 820d5525)
1 /* vi:set ts=8 sts=4 sw=4 noet:
2  *
3  * VIM - Vi IMproved	by Bram Moolenaar
4  *
5  * Do ":help uganda"  in Vim to read copying and usage conditions.
6  * Do ":help credits" in Vim to see a list of people who contributed.
7  * See README.txt for an overview of the Vim source code.
8  */
9 
10 /*
11  * sign.c: functions for managing signs
12  */
13 
14 #include "vim.h"
15 
16 #if defined(FEAT_SIGNS) || defined(PROTO)
17 
18 /*
19  * Struct to hold the sign properties.
20  */
21 typedef struct sign sign_T;
22 
23 struct sign
24 {
25     sign_T	*sn_next;	// next sign in list
26     int		sn_typenr;	// type number of sign
27     char_u	*sn_name;	// name of sign
28     char_u	*sn_icon;	// name of pixmap
29 # ifdef FEAT_SIGN_ICONS
30     void	*sn_image;	// icon image
31 # endif
32     char_u	*sn_text;	// text used instead of pixmap
33     int		sn_line_hl;	// highlight ID for line
34     int		sn_text_hl;	// highlight ID for text
35 };
36 
37 static sign_T	*first_sign = NULL;
38 static int	next_sign_typenr = 1;
39 
40 static void sign_list_defined(sign_T *sp);
41 static void sign_undefine(sign_T *sp, sign_T *sp_prev);
42 
43 static char *cmds[] = {
44 			"define",
45 # define SIGNCMD_DEFINE	0
46 			"undefine",
47 # define SIGNCMD_UNDEFINE 1
48 			"list",
49 # define SIGNCMD_LIST	2
50 			"place",
51 # define SIGNCMD_PLACE	3
52 			"unplace",
53 # define SIGNCMD_UNPLACE 4
54 			"jump",
55 # define SIGNCMD_JUMP	5
56 			NULL
57 # define SIGNCMD_LAST	6
58 };
59 
60 #define FOR_ALL_SIGNS(sp)	\
61     for ((sp) = first_sign; (sp) != NULL; (sp) = (sp)->sn_next)
62 
63 static hashtab_T	sg_table;	// sign group (signgroup_T) hashtable
64 static int		next_sign_id = 1; // next sign id in the global group
65 
66 /*
67  * Initialize data needed for managing signs
68  */
69     void
init_signs(void)70 init_signs(void)
71 {
72     hash_init(&sg_table);		// sign group hash table
73 }
74 
75 /*
76  * A new sign in group 'groupname' is added. If the group is not present,
77  * create it. Otherwise reference the group.
78  */
79     static signgroup_T *
sign_group_ref(char_u * groupname)80 sign_group_ref(char_u *groupname)
81 {
82     hash_T		hash;
83     hashitem_T		*hi;
84     signgroup_T		*group;
85 
86     hash = hash_hash(groupname);
87     hi = hash_lookup(&sg_table, groupname, hash);
88     if (HASHITEM_EMPTY(hi))
89     {
90 	// new group
91 	group = alloc(offsetof(signgroup_T, sg_name) + STRLEN(groupname) + 1);
92 	if (group == NULL)
93 	    return NULL;
94 	STRCPY(group->sg_name, groupname);
95 	group->sg_refcount = 1;
96 	group->sg_next_sign_id = 1;
97 	hash_add_item(&sg_table, hi, group->sg_name, hash);
98     }
99     else
100     {
101 	// existing group
102 	group = HI2SG(hi);
103 	group->sg_refcount++;
104     }
105 
106     return group;
107 }
108 
109 /*
110  * A sign in group 'groupname' is removed. If all the signs in this group are
111  * removed, then remove the group.
112  */
113     static void
sign_group_unref(char_u * groupname)114 sign_group_unref(char_u *groupname)
115 {
116     hashitem_T		*hi;
117     signgroup_T		*group;
118 
119     hi = hash_find(&sg_table, groupname);
120     if (!HASHITEM_EMPTY(hi))
121     {
122 	group = HI2SG(hi);
123 	group->sg_refcount--;
124 	if (group->sg_refcount == 0)
125 	{
126 	    // All the signs in this group are removed
127 	    hash_remove(&sg_table, hi);
128 	    vim_free(group);
129 	}
130     }
131 }
132 
133 /*
134  * Returns TRUE if 'sign' is in 'group'.
135  * A sign can either be in the global group (sign->se_group == NULL)
136  * or in a named group. If 'group' is '*', then the sign is part of the group.
137  */
138     static int
sign_in_group(sign_entry_T * sign,char_u * group)139 sign_in_group(sign_entry_T *sign, char_u *group)
140 {
141     return ((group != NULL && STRCMP(group, "*") == 0)
142 	    || (group == NULL && sign->se_group == NULL)
143 	    || (group != NULL && sign->se_group != NULL
144 			      && STRCMP(group, sign->se_group->sg_name) == 0));
145 }
146 
147 /*
148  * Return TRUE if "sign" is to be displayed in window "wp".
149  * If the group name starts with "PopUp" it only shows in a popup window.
150  */
151     static int
sign_group_for_window(sign_entry_T * sign,win_T * wp)152 sign_group_for_window(sign_entry_T *sign, win_T *wp)
153 {
154     int for_popup = sign->se_group != NULL
155 			&& STRNCMP("PopUp", sign->se_group->sg_name, 5) == 0;
156 
157     return WIN_IS_POPUP(wp) ? for_popup : !for_popup;
158 }
159 
160 /*
161  * Get the next free sign identifier in the specified group
162  */
163     static int
sign_group_get_next_signid(buf_T * buf,char_u * groupname)164 sign_group_get_next_signid(buf_T *buf, char_u *groupname)
165 {
166     int			id = 1;
167     signgroup_T		*group = NULL;
168     sign_entry_T	*sign;
169     hashitem_T		*hi;
170     int			found = FALSE;
171 
172     if (groupname != NULL)
173     {
174 	hi = hash_find(&sg_table, groupname);
175 	if (HASHITEM_EMPTY(hi))
176 	    return id;
177 	group = HI2SG(hi);
178     }
179 
180     // Search for the next usable sign identifier
181     while (!found)
182     {
183 	if (group == NULL)
184 	    id = next_sign_id++;		// global group
185 	else
186 	    id = group->sg_next_sign_id++;
187 
188 	// Check whether this sign is already placed in the buffer
189 	found = TRUE;
190 	FOR_ALL_SIGNS_IN_BUF(buf, sign)
191 	{
192 	    if (id == sign->se_id && sign_in_group(sign, groupname))
193 	    {
194 		found = FALSE;		// sign identifier is in use
195 		break;
196 	    }
197 	}
198     }
199 
200     return id;
201 }
202 
203 /*
204  * Insert a new sign into the signlist for buffer 'buf' between the 'prev' and
205  * 'next' signs.
206  */
207     static void
insert_sign(buf_T * buf,sign_entry_T * prev,sign_entry_T * next,int id,char_u * group,int prio,linenr_T lnum,int typenr)208 insert_sign(
209     buf_T	*buf,		// buffer to store sign in
210     sign_entry_T *prev,		// previous sign entry
211     sign_entry_T *next,		// next sign entry
212     int		id,		// sign ID
213     char_u	*group,		// sign group; NULL for global group
214     int		prio,		// sign priority
215     linenr_T	lnum,		// line number which gets the mark
216     int		typenr)		// typenr of sign we are adding
217 {
218     sign_entry_T *newsign;
219 
220     newsign = lalloc_id(sizeof(sign_entry_T), FALSE, aid_insert_sign);
221     if (newsign != NULL)
222     {
223 	newsign->se_id = id;
224 	newsign->se_lnum = lnum;
225 	newsign->se_typenr = typenr;
226 	if (group != NULL)
227 	{
228 	    newsign->se_group = sign_group_ref(group);
229 	    if (newsign->se_group == NULL)
230 	    {
231 		vim_free(newsign);
232 		return;
233 	    }
234 	}
235 	else
236 	    newsign->se_group = NULL;
237 	newsign->se_priority = prio;
238 	newsign->se_next = next;
239 	newsign->se_prev = prev;
240 	if (next != NULL)
241 	    next->se_prev = newsign;
242 
243 	if (prev == NULL)
244 	{
245 	    // When adding first sign need to redraw the windows to create the
246 	    // column for signs.
247 	    if (buf->b_signlist == NULL)
248 	    {
249 		redraw_buf_later(buf, NOT_VALID);
250 		changed_line_abv_curs();
251 	    }
252 
253 	    // first sign in signlist
254 	    buf->b_signlist = newsign;
255 #ifdef FEAT_NETBEANS_INTG
256 	    if (netbeans_active())
257 		buf->b_has_sign_column = TRUE;
258 #endif
259 	}
260 	else
261 	    prev->se_next = newsign;
262     }
263 }
264 
265 /*
266  * Insert a new sign sorted by line number and sign priority.
267  */
268     static void
insert_sign_by_lnum_prio(buf_T * buf,sign_entry_T * prev,int id,char_u * group,int prio,linenr_T lnum,int typenr)269 insert_sign_by_lnum_prio(
270     buf_T	*buf,		// buffer to store sign in
271     sign_entry_T *prev,		// previous sign entry
272     int		id,		// sign ID
273     char_u	*group,		// sign group; NULL for global group
274     int		prio,		// sign priority
275     linenr_T	lnum,		// line number which gets the mark
276     int		typenr)		// typenr of sign we are adding
277 {
278     sign_entry_T	*sign;
279 
280     // keep signs sorted by lnum and by priority: insert new sign at
281     // the proper position in the list for this lnum.
282     while (prev != NULL && prev->se_lnum == lnum && prev->se_priority <= prio)
283 	prev = prev->se_prev;
284     if (prev == NULL)
285 	sign = buf->b_signlist;
286     else
287 	sign = prev->se_next;
288 
289     insert_sign(buf, prev, sign, id, group, prio, lnum, typenr);
290 }
291 
292 /*
293  * Lookup a sign by typenr. Returns NULL if sign is not found.
294  */
295     static sign_T *
find_sign_by_typenr(int typenr)296 find_sign_by_typenr(int typenr)
297 {
298     sign_T	*sp;
299 
300     FOR_ALL_SIGNS(sp)
301 	if (sp->sn_typenr == typenr)
302 	    return sp;
303     return NULL;
304 }
305 
306 /*
307  * Get the name of a sign by its typenr.
308  */
309     static char_u *
sign_typenr2name(int typenr)310 sign_typenr2name(int typenr)
311 {
312     sign_T	*sp;
313 
314     FOR_ALL_SIGNS(sp)
315 	if (sp->sn_typenr == typenr)
316 	    return sp->sn_name;
317     return (char_u *)_("[Deleted]");
318 }
319 
320 /*
321  * Return information about a sign in a Dict
322  */
323     static dict_T *
sign_get_info(sign_entry_T * sign)324 sign_get_info(sign_entry_T *sign)
325 {
326     dict_T	*d;
327 
328     if ((d = dict_alloc_id(aid_sign_getinfo)) == NULL)
329 	return NULL;
330     dict_add_number(d, "id", sign->se_id);
331     dict_add_string(d, "group", (sign->se_group == NULL) ?
332 				       (char_u *)"" : sign->se_group->sg_name);
333     dict_add_number(d, "lnum", sign->se_lnum);
334     dict_add_string(d, "name", sign_typenr2name(sign->se_typenr));
335     dict_add_number(d, "priority", sign->se_priority);
336 
337     return d;
338 }
339 
340 /*
341  * Sort the signs placed on the same line as "sign" by priority.  Invoked after
342  * changing the priority of an already placed sign.  Assumes the signs in the
343  * buffer are sorted by line number and priority.
344  */
345     static void
sign_sort_by_prio_on_line(buf_T * buf,sign_entry_T * sign)346 sign_sort_by_prio_on_line(buf_T *buf, sign_entry_T *sign)
347 {
348     sign_entry_T *p = NULL;
349 
350     // If there is only one sign in the buffer or only one sign on the line or
351     // the sign is already sorted by priority, then return.
352     if ((sign->se_prev == NULL
353 		|| sign->se_prev->se_lnum != sign->se_lnum
354 		|| sign->se_prev->se_priority > sign->se_priority)
355 	    && (sign->se_next == NULL
356 		|| sign->se_next->se_lnum != sign->se_lnum
357 		|| sign->se_next->se_priority < sign->se_priority))
358 	return;
359 
360     // One or more signs on the same line as 'sign'
361     // Find a sign after which 'sign' should be inserted
362 
363     // First search backward for a sign with higher priority on the same line
364     p = sign;
365     while (p->se_prev != NULL && p->se_prev->se_lnum == sign->se_lnum
366 			       && p->se_prev->se_priority <= sign->se_priority)
367 	p = p->se_prev;
368 
369     if (p == sign)
370     {
371 	// Sign not found. Search forward for a sign with priority just before
372 	// 'sign'.
373 	p = sign->se_next;
374 	while (p->se_next != NULL && p->se_next->se_lnum == sign->se_lnum
375 				&& p->se_next->se_priority > sign->se_priority)
376 	    p = p->se_next;
377     }
378 
379     // Remove 'sign' from the list
380     if (buf->b_signlist == sign)
381 	buf->b_signlist = sign->se_next;
382     if (sign->se_prev != NULL)
383 	sign->se_prev->se_next = sign->se_next;
384     if (sign->se_next != NULL)
385 	sign->se_next->se_prev = sign->se_prev;
386     sign->se_prev = NULL;
387     sign->se_next = NULL;
388 
389     // Re-insert 'sign' at the right place
390     if (p->se_priority <= sign->se_priority)
391     {
392 	// 'sign' has a higher priority and should be inserted before 'p'
393 	sign->se_prev = p->se_prev;
394 	sign->se_next = p;
395 	p->se_prev = sign;
396 	if (sign->se_prev != NULL)
397 	    sign->se_prev->se_next = sign;
398 	if (buf->b_signlist == p)
399 	    buf->b_signlist = sign;
400     }
401     else
402     {
403 	// 'sign' has a lower priority and should be inserted after 'p'
404 	sign->se_prev = p;
405 	sign->se_next = p->se_next;
406 	p->se_next = sign;
407 	if (sign->se_next != NULL)
408 	    sign->se_next->se_prev = sign;
409     }
410 }
411 
412 /*
413  * Add the sign into the signlist. Find the right spot to do it though.
414  */
415     static void
buf_addsign(buf_T * buf,int id,char_u * groupname,int prio,linenr_T lnum,int typenr)416 buf_addsign(
417     buf_T	*buf,		// buffer to store sign in
418     int		id,		// sign ID
419     char_u	*groupname,	// sign group
420     int		prio,		// sign priority
421     linenr_T	lnum,		// line number which gets the mark
422     int		typenr)		// typenr of sign we are adding
423 {
424     sign_entry_T	*sign;		// a sign in the signlist
425     sign_entry_T	*prev;		// the previous sign
426 
427     prev = NULL;
428     FOR_ALL_SIGNS_IN_BUF(buf, sign)
429     {
430 	if (lnum == sign->se_lnum && id == sign->se_id
431 		&& sign_in_group(sign, groupname))
432 	{
433 	    // Update an existing sign
434 	    sign->se_typenr = typenr;
435 	    sign->se_priority = prio;
436 	    sign_sort_by_prio_on_line(buf, sign);
437 	    return;
438 	}
439 	else if (lnum < sign->se_lnum)
440 	{
441 	    insert_sign_by_lnum_prio(buf, prev, id, groupname, prio,
442 								lnum, typenr);
443 	    return;
444 	}
445 	prev = sign;
446     }
447 
448     insert_sign_by_lnum_prio(buf, prev, id, groupname, prio, lnum, typenr);
449     return;
450 }
451 
452 /*
453  * For an existing, placed sign "markId" change the type to "typenr".
454  * Returns the line number of the sign, or zero if the sign is not found.
455  */
456     static linenr_T
buf_change_sign_type(buf_T * buf,int markId,char_u * group,int typenr,int prio)457 buf_change_sign_type(
458     buf_T	*buf,		// buffer to store sign in
459     int		markId,		// sign ID
460     char_u	*group,		// sign group
461     int		typenr,		// typenr of sign we are adding
462     int		prio)		// sign priority
463 {
464     sign_entry_T	*sign;		// a sign in the signlist
465 
466     FOR_ALL_SIGNS_IN_BUF(buf, sign)
467     {
468 	if (sign->se_id == markId && sign_in_group(sign, group))
469 	{
470 	    sign->se_typenr = typenr;
471 	    sign->se_priority = prio;
472 	    sign_sort_by_prio_on_line(buf, sign);
473 	    return sign->se_lnum;
474 	}
475     }
476 
477     return (linenr_T)0;
478 }
479 
480 /*
481  * Return the attributes of the first sign placed on line 'lnum' in buffer
482  * 'buf'. Used when refreshing the screen. Returns TRUE if a sign is found on
483  * 'lnum', FALSE otherwise.
484  */
485     int
buf_get_signattrs(win_T * wp,linenr_T lnum,sign_attrs_T * sattr)486 buf_get_signattrs(win_T *wp, linenr_T lnum, sign_attrs_T *sattr)
487 {
488     sign_entry_T	*sign;
489     sign_T		*sp;
490     buf_T		*buf = wp->w_buffer;
491 
492     CLEAR_POINTER(sattr);
493 
494     FOR_ALL_SIGNS_IN_BUF(buf, sign)
495     {
496 	if (sign->se_lnum > lnum)
497 	    // Signs are sorted by line number in the buffer. No need to check
498 	    // for signs after the specified line number 'lnum'.
499 	    break;
500 
501 	if (sign->se_lnum == lnum
502 # ifdef FEAT_PROP_POPUP
503 		&& sign_group_for_window(sign, wp)
504 # endif
505 		)
506 	{
507 	    sattr->sat_typenr = sign->se_typenr;
508 	    sp = find_sign_by_typenr(sign->se_typenr);
509 	    if (sp == NULL)
510 		return FALSE;
511 
512 # ifdef FEAT_SIGN_ICONS
513 	    sattr->sat_icon = sp->sn_image;
514 # endif
515 	    sattr->sat_text = sp->sn_text;
516 	    if (sattr->sat_text != NULL && sp->sn_text_hl > 0)
517 		sattr->sat_texthl = syn_id2attr(sp->sn_text_hl);
518 	    if (sp->sn_line_hl > 0)
519 		sattr->sat_linehl = syn_id2attr(sp->sn_line_hl);
520 	    sattr->sat_priority = sign->se_priority;
521 
522 	    // If there is another sign next with the same priority, may
523 	    // combine the text and the line highlighting.
524 	    if (sign->se_next != NULL
525 		    && sign->se_next->se_priority == sign->se_priority
526 		    && sign->se_next->se_lnum == sign->se_lnum)
527 	    {
528 		sign_T	*next_sp = find_sign_by_typenr(sign->se_next->se_typenr);
529 
530 		if (next_sp != NULL)
531 		{
532 		    if (sattr->sat_icon == NULL && sattr->sat_text == NULL)
533 		    {
534 # ifdef FEAT_SIGN_ICONS
535 			sattr->sat_icon = next_sp->sn_image;
536 # endif
537 			sattr->sat_text = next_sp->sn_text;
538 		    }
539 		    if (sp->sn_text_hl <= 0 && next_sp->sn_text_hl > 0)
540 			sattr->sat_texthl = syn_id2attr(next_sp->sn_text_hl);
541 		    if (sp->sn_line_hl <= 0 && next_sp->sn_line_hl > 0)
542 			sattr->sat_linehl = syn_id2attr(next_sp->sn_line_hl);
543 		}
544 	    }
545 	    return TRUE;
546 	}
547     }
548     return FALSE;
549 }
550 
551 /*
552  * Delete sign 'id' in group 'group' from buffer 'buf'.
553  * If 'id' is zero, then delete all the signs in group 'group'. Otherwise
554  * delete only the specified sign.
555  * If 'group' is '*', then delete the sign in all the groups. If 'group' is
556  * NULL, then delete the sign in the global group. Otherwise delete the sign in
557  * the specified group.
558  * Returns the line number of the deleted sign. If multiple signs are deleted,
559  * then returns the line number of the last sign deleted.
560  */
561     linenr_T
buf_delsign(buf_T * buf,linenr_T atlnum,int id,char_u * group)562 buf_delsign(
563     buf_T	*buf,		// buffer sign is stored in
564     linenr_T	atlnum,		// sign at this line, 0 - at any line
565     int		id,		// sign id
566     char_u	*group)		// sign group
567 {
568     sign_entry_T	**lastp;	// pointer to pointer to current sign
569     sign_entry_T	*sign;		// a sign in a b_signlist
570     sign_entry_T	*next;		// the next sign in a b_signlist
571     linenr_T		lnum;		// line number whose sign was deleted
572 
573     lastp = &buf->b_signlist;
574     lnum = 0;
575     for (sign = buf->b_signlist; sign != NULL; sign = next)
576     {
577 	next = sign->se_next;
578 	if ((id == 0 || sign->se_id == id)
579 		&& (atlnum == 0 || sign->se_lnum == atlnum)
580 		&& sign_in_group(sign, group))
581 
582 	{
583 	    *lastp = next;
584 	    if (next != NULL)
585 		next->se_prev = sign->se_prev;
586 	    lnum = sign->se_lnum;
587 	    if (sign->se_group != NULL)
588 		sign_group_unref(sign->se_group->sg_name);
589 	    vim_free(sign);
590 	    redraw_buf_line_later(buf, lnum);
591 
592 	    // Check whether only one sign needs to be deleted
593 	    // If deleting a sign with a specific identifier in a particular
594 	    // group or deleting any sign at a particular line number, delete
595 	    // only one sign.
596 	    if (group == NULL
597 		    || (*group != '*' && id != 0)
598 		    || (*group == '*' && atlnum != 0))
599 		break;
600 	}
601 	else
602 	    lastp = &sign->se_next;
603     }
604 
605     // When deleting the last sign the cursor position may change, because the
606     // sign columns no longer shows.  And the 'signcolumn' may be hidden.
607     if (buf->b_signlist == NULL)
608     {
609 	redraw_buf_later(buf, NOT_VALID);
610 	changed_line_abv_curs();
611     }
612 
613     return lnum;
614 }
615 
616 
617 /*
618  * Find the line number of the sign with the requested id in group 'group'. If
619  * the sign does not exist, return 0 as the line number. This will still let
620  * the correct file get loaded.
621  */
622     int
buf_findsign(buf_T * buf,int id,char_u * group)623 buf_findsign(
624     buf_T	*buf,		// buffer to store sign in
625     int		id,		// sign ID
626     char_u	*group)		// sign group
627 {
628     sign_entry_T	*sign;		// a sign in the signlist
629 
630     FOR_ALL_SIGNS_IN_BUF(buf, sign)
631 	if (sign->se_id == id && sign_in_group(sign, group))
632 	    return sign->se_lnum;
633 
634     return 0;
635 }
636 
637 /*
638  * Return the sign at line 'lnum' in buffer 'buf'. Returns NULL if a sign is
639  * not found at the line. If 'groupname' is NULL, searches in the global group.
640  */
641     static sign_entry_T *
buf_getsign_at_line(buf_T * buf,linenr_T lnum,char_u * groupname)642 buf_getsign_at_line(
643     buf_T	*buf,		// buffer whose sign we are searching for
644     linenr_T	lnum,		// line number of sign
645     char_u	*groupname)	// sign group name
646 {
647     sign_entry_T	*sign;		// a sign in the signlist
648 
649     FOR_ALL_SIGNS_IN_BUF(buf, sign)
650     {
651 	if (sign->se_lnum > lnum)
652 	    // Signs are sorted by line number in the buffer. No need to check
653 	    // for signs after the specified line number 'lnum'.
654 	    break;
655 
656 	if (sign->se_lnum == lnum && sign_in_group(sign, groupname))
657 	    return sign;
658     }
659 
660     return NULL;
661 }
662 
663 /*
664  * Return the identifier of the sign at line number 'lnum' in buffer 'buf'.
665  */
666     int
buf_findsign_id(buf_T * buf,linenr_T lnum,char_u * groupname)667 buf_findsign_id(
668     buf_T	*buf,		// buffer whose sign we are searching for
669     linenr_T	lnum,		// line number of sign
670     char_u	*groupname)	// sign group name
671 {
672     sign_entry_T	*sign;		// a sign in the signlist
673 
674     sign = buf_getsign_at_line(buf, lnum, groupname);
675     if (sign != NULL)
676 	return sign->se_id;
677 
678     return 0;
679 }
680 
681 # if defined(FEAT_NETBEANS_INTG) || defined(PROTO)
682 /*
683  * See if a given type of sign exists on a specific line.
684  */
685     int
buf_findsigntype_id(buf_T * buf,linenr_T lnum,int typenr)686 buf_findsigntype_id(
687     buf_T	*buf,		// buffer whose sign we are searching for
688     linenr_T	lnum,		// line number of sign
689     int		typenr)		// sign type number
690 {
691     sign_entry_T	*sign;		// a sign in the signlist
692 
693     FOR_ALL_SIGNS_IN_BUF(buf, sign)
694     {
695 	if (sign->se_lnum > lnum)
696 	    // Signs are sorted by line number in the buffer. No need to check
697 	    // for signs after the specified line number 'lnum'.
698 	    break;
699 
700 	if (sign->se_lnum == lnum && sign->se_typenr == typenr)
701 	    return sign->se_id;
702     }
703 
704     return 0;
705 }
706 
707 
708 #  if defined(FEAT_SIGN_ICONS) || defined(PROTO)
709 /*
710  * Return the number of icons on the given line.
711  */
712     int
buf_signcount(buf_T * buf,linenr_T lnum)713 buf_signcount(buf_T *buf, linenr_T lnum)
714 {
715     sign_entry_T	*sign;		// a sign in the signlist
716     int			count = 0;
717 
718     FOR_ALL_SIGNS_IN_BUF(buf, sign)
719     {
720 	if (sign->se_lnum > lnum)
721 	    // Signs are sorted by line number in the buffer. No need to check
722 	    // for signs after the specified line number 'lnum'.
723 	    break;
724 
725 	if (sign->se_lnum == lnum)
726 	    if (sign_get_image(sign->se_typenr) != NULL)
727 		count++;
728     }
729 
730     return count;
731 }
732 #  endif // FEAT_SIGN_ICONS
733 # endif // FEAT_NETBEANS_INTG
734 
735 /*
736  * Delete signs in group 'group' in buffer "buf". If 'group' is '*', then
737  * delete all the signs.
738  */
739     void
buf_delete_signs(buf_T * buf,char_u * group)740 buf_delete_signs(buf_T *buf, char_u *group)
741 {
742     sign_entry_T	*sign;
743     sign_entry_T	**lastp;	// pointer to pointer to current sign
744     sign_entry_T	*next;
745 
746     // When deleting the last sign need to redraw the windows to remove the
747     // sign column. Not when curwin is NULL (this means we're exiting).
748     if (buf->b_signlist != NULL && curwin != NULL)
749     {
750 	redraw_buf_later(buf, NOT_VALID);
751 	changed_line_abv_curs();
752     }
753 
754     lastp = &buf->b_signlist;
755     for (sign = buf->b_signlist; sign != NULL; sign = next)
756     {
757 	next = sign->se_next;
758 	if (sign_in_group(sign, group))
759 	{
760 	    *lastp = next;
761 	    if (next != NULL)
762 		next->se_prev = sign->se_prev;
763 	    if (sign->se_group != NULL)
764 		sign_group_unref(sign->se_group->sg_name);
765 	    vim_free(sign);
766 	}
767 	else
768 	    lastp = &sign->se_next;
769     }
770 }
771 
772 /*
773  * List placed signs for "rbuf".  If "rbuf" is NULL do it for all buffers.
774  */
775     static void
sign_list_placed(buf_T * rbuf,char_u * sign_group)776 sign_list_placed(buf_T *rbuf, char_u *sign_group)
777 {
778     buf_T		*buf;
779     sign_entry_T	*sign;
780     char		lbuf[MSG_BUF_LEN];
781     char		group[MSG_BUF_LEN];
782 
783     msg_puts_title(_("\n--- Signs ---"));
784     msg_putchar('\n');
785     if (rbuf == NULL)
786 	buf = firstbuf;
787     else
788 	buf = rbuf;
789     while (buf != NULL && !got_int)
790     {
791 	if (buf->b_signlist != NULL)
792 	{
793 	    vim_snprintf(lbuf, MSG_BUF_LEN, _("Signs for %s:"), buf->b_fname);
794 	    msg_puts_attr(lbuf, HL_ATTR(HLF_D));
795 	    msg_putchar('\n');
796 	}
797 	FOR_ALL_SIGNS_IN_BUF(buf, sign)
798 	{
799 	    if (got_int)
800 		break;
801 	    if (!sign_in_group(sign, sign_group))
802 		continue;
803 	    if (sign->se_group != NULL)
804 		vim_snprintf(group, MSG_BUF_LEN, _("  group=%s"),
805 						      sign->se_group->sg_name);
806 	    else
807 		group[0] = '\0';
808 	    vim_snprintf(lbuf, MSG_BUF_LEN,
809 			   _("    line=%ld  id=%d%s  name=%s  priority=%d"),
810 			   (long)sign->se_lnum, sign->se_id, group,
811 			 sign_typenr2name(sign->se_typenr), sign->se_priority);
812 	    msg_puts(lbuf);
813 	    msg_putchar('\n');
814 	}
815 	if (rbuf != NULL)
816 	    break;
817 	buf = buf->b_next;
818     }
819 }
820 
821 /*
822  * Adjust a placed sign for inserted/deleted lines.
823  */
824     void
sign_mark_adjust(linenr_T line1,linenr_T line2,long amount,long amount_after)825 sign_mark_adjust(
826     linenr_T	line1,
827     linenr_T	line2,
828     long	amount,
829     long	amount_after)
830 {
831     sign_entry_T	*sign;		// a sign in a b_signlist
832     linenr_T		new_lnum;
833 
834     FOR_ALL_SIGNS_IN_BUF(curbuf, sign)
835     {
836 	// Ignore changes to lines after the sign
837 	if (sign->se_lnum < line1)
838 	    continue;
839 	new_lnum = sign->se_lnum;
840 	if (sign->se_lnum >= line1 && sign->se_lnum <= line2)
841 	{
842 	    if (amount != MAXLNUM)
843 		new_lnum += amount;
844 	}
845 	else if (sign->se_lnum > line2)
846 	    // Lines inserted or deleted before the sign
847 	    new_lnum += amount_after;
848 
849 	// If the new sign line number is past the last line in the buffer,
850 	// then don't adjust the line number. Otherwise, it will always be past
851 	// the last line and will not be visible.
852 	if (new_lnum <= curbuf->b_ml.ml_line_count)
853 	    sign->se_lnum = new_lnum;
854     }
855 }
856 
857 /*
858  * Find index of a ":sign" subcmd from its name.
859  * "*end_cmd" must be writable.
860  */
861     static int
sign_cmd_idx(char_u * begin_cmd,char_u * end_cmd)862 sign_cmd_idx(
863     char_u	*begin_cmd,	// begin of sign subcmd
864     char_u	*end_cmd)	// just after sign subcmd
865 {
866     int		idx;
867     char	save = *end_cmd;
868 
869     *end_cmd = NUL;
870     for (idx = 0; ; ++idx)
871 	if (cmds[idx] == NULL || STRCMP(begin_cmd, cmds[idx]) == 0)
872 	    break;
873     *end_cmd = save;
874     return idx;
875 }
876 
877 /*
878  * Find a sign by name. Also returns pointer to the previous sign.
879  */
880     static sign_T *
sign_find(char_u * name,sign_T ** sp_prev)881 sign_find(char_u *name, sign_T **sp_prev)
882 {
883     sign_T *sp;
884 
885     if (sp_prev != NULL)
886 	*sp_prev = NULL;
887     FOR_ALL_SIGNS(sp)
888     {
889 	if (STRCMP(sp->sn_name, name) == 0)
890 	    break;
891 	if (sp_prev != NULL)
892 	    *sp_prev = sp;
893     }
894 
895     return sp;
896 }
897 
898 /*
899  * Allocate a new sign
900  */
901     static sign_T *
alloc_new_sign(char_u * name)902 alloc_new_sign(char_u *name)
903 {
904     sign_T	*sp;
905     sign_T	*lp;
906     int	start = next_sign_typenr;
907 
908     // Allocate a new sign.
909     sp = alloc_clear_id(sizeof(sign_T), aid_sign_define_by_name);
910     if (sp == NULL)
911 	return NULL;
912 
913     // Check that next_sign_typenr is not already being used.
914     // This only happens after wrapping around.  Hopefully
915     // another one got deleted and we can use its number.
916     for (lp = first_sign; lp != NULL; )
917     {
918 	if (lp->sn_typenr == next_sign_typenr)
919 	{
920 	    ++next_sign_typenr;
921 	    if (next_sign_typenr == MAX_TYPENR)
922 		next_sign_typenr = 1;
923 	    if (next_sign_typenr == start)
924 	    {
925 		vim_free(sp);
926 		emsg(_("E612: Too many signs defined"));
927 		return NULL;
928 	    }
929 	    lp = first_sign;  // start all over
930 	    continue;
931 	}
932 	lp = lp->sn_next;
933     }
934 
935     sp->sn_typenr = next_sign_typenr;
936     if (++next_sign_typenr == MAX_TYPENR)
937 	next_sign_typenr = 1; // wrap around
938 
939     sp->sn_name = vim_strsave(name);
940     if (sp->sn_name == NULL)  // out of memory
941     {
942 	vim_free(sp);
943 	return NULL;
944     }
945 
946     return sp;
947 }
948 
949 /*
950  * Initialize the icon information for a new sign
951  */
952     static void
sign_define_init_icon(sign_T * sp,char_u * icon)953 sign_define_init_icon(sign_T *sp, char_u *icon)
954 {
955     vim_free(sp->sn_icon);
956     sp->sn_icon = vim_strsave(icon);
957     backslash_halve(sp->sn_icon);
958 # ifdef FEAT_SIGN_ICONS
959     if (gui.in_use)
960     {
961 	out_flush();
962 	if (sp->sn_image != NULL)
963 	    gui_mch_destroy_sign(sp->sn_image);
964 	sp->sn_image = gui_mch_register_sign(sp->sn_icon);
965     }
966 # endif
967 }
968 
969 /*
970  * Initialize the text for a new sign
971  */
972     static int
sign_define_init_text(sign_T * sp,char_u * text)973 sign_define_init_text(sign_T *sp, char_u *text)
974 {
975     char_u	*s;
976     char_u	*endp;
977     int		cells;
978     int		len;
979 
980     endp = text + (int)STRLEN(text);
981 
982     // Remove backslashes so that it is possible to use a space.
983     for (s = text; s + 1 < endp; ++s)
984 	if (*s == '\\')
985 	{
986 	    STRMOVE(s, s + 1);
987 	    --endp;
988 	}
989 
990     // Count cells and check for non-printable chars
991     if (has_mbyte)
992     {
993 	cells = 0;
994 	for (s = text; s < endp; s += (*mb_ptr2len)(s))
995 	{
996 	    if (!vim_isprintc((*mb_ptr2char)(s)))
997 		break;
998 	    cells += (*mb_ptr2cells)(s);
999 	}
1000     }
1001     else
1002     {
1003 	for (s = text; s < endp; ++s)
1004 	    if (!vim_isprintc(*s))
1005 		break;
1006 	cells = (int)(s - text);
1007     }
1008 
1009     // Currently sign text must be one or two display cells
1010     if (s != endp || cells < 1 || cells > 2)
1011     {
1012 	semsg(_("E239: Invalid sign text: %s"), text);
1013 	return FAIL;
1014     }
1015 
1016     vim_free(sp->sn_text);
1017     // Allocate one byte more if we need to pad up
1018     // with a space.
1019     len = (int)(endp - text + ((cells == 1) ? 1 : 0));
1020     sp->sn_text = vim_strnsave(text, len);
1021 
1022     // For single character sign text, pad with a space.
1023     if (sp->sn_text != NULL && cells == 1)
1024 	STRCPY(sp->sn_text + len - 1, " ");
1025 
1026     return OK;
1027 }
1028 
1029 /*
1030  * Define a new sign or update an existing sign
1031  */
1032     int
sign_define_by_name(char_u * name,char_u * icon,char_u * linehl,char_u * text,char_u * texthl)1033 sign_define_by_name(
1034 	char_u	*name,
1035 	char_u	*icon,
1036 	char_u	*linehl,
1037 	char_u	*text,
1038 	char_u	*texthl)
1039 {
1040     sign_T	*sp_prev;
1041     sign_T	*sp;
1042 
1043     sp = sign_find(name, &sp_prev);
1044     if (sp == NULL)
1045     {
1046 	sp = alloc_new_sign(name);
1047 	if (sp == NULL)
1048 	    return FAIL;
1049 
1050 	// add the new sign to the list of signs
1051 	if (sp_prev == NULL)
1052 	    first_sign = sp;
1053 	else
1054 	    sp_prev->sn_next = sp;
1055     }
1056     else
1057     {
1058 	win_T *wp;
1059 
1060 	// Signs may already exist, a redraw is needed in windows with a
1061 	// non-empty sign list.
1062 	FOR_ALL_WINDOWS(wp)
1063 	    if (wp->w_buffer->b_signlist != NULL)
1064 		redraw_buf_later(wp->w_buffer, NOT_VALID);
1065     }
1066 
1067     // set values for a defined sign.
1068     if (icon != NULL)
1069 	sign_define_init_icon(sp, icon);
1070 
1071     if (text != NULL && (sign_define_init_text(sp, text) == FAIL))
1072 	return FAIL;
1073 
1074     if (linehl != NULL)
1075 	sp->sn_line_hl = syn_check_group(linehl, (int)STRLEN(linehl));
1076 
1077     if (texthl != NULL)
1078 	sp->sn_text_hl = syn_check_group(texthl, (int)STRLEN(texthl));
1079 
1080     return OK;
1081 }
1082 
1083 /*
1084  * Return TRUE if sign "name" exists.
1085  */
1086     int
sign_exists_by_name(char_u * name)1087 sign_exists_by_name(char_u *name)
1088 {
1089     return sign_find(name, NULL) != NULL;
1090 }
1091 
1092 /*
1093  * Free the sign specified by 'name'.
1094  */
1095     int
sign_undefine_by_name(char_u * name,int give_error)1096 sign_undefine_by_name(char_u *name, int give_error)
1097 {
1098     sign_T	*sp_prev;
1099     sign_T	*sp;
1100 
1101     sp = sign_find(name, &sp_prev);
1102     if (sp == NULL)
1103     {
1104 	if (give_error)
1105 	    semsg(_("E155: Unknown sign: %s"), name);
1106 	return FAIL;
1107     }
1108     sign_undefine(sp, sp_prev);
1109 
1110     return OK;
1111 }
1112 
1113 /*
1114  * List the signs matching 'name'
1115  */
1116     static void
sign_list_by_name(char_u * name)1117 sign_list_by_name(char_u *name)
1118 {
1119     sign_T	*sp;
1120 
1121     sp = sign_find(name, NULL);
1122     if (sp != NULL)
1123 	sign_list_defined(sp);
1124     else
1125 	semsg(_("E155: Unknown sign: %s"), name);
1126 }
1127 
1128     static void
may_force_numberwidth_recompute(buf_T * buf,int unplace)1129 may_force_numberwidth_recompute(buf_T *buf, int unplace)
1130 {
1131     tabpage_T	*tp;
1132     win_T		*wp;
1133 
1134     FOR_ALL_TAB_WINDOWS(tp, wp)
1135 	if (wp->w_buffer == buf
1136 		&& (wp->w_p_nu || wp->w_p_rnu)
1137 		&& (unplace || wp->w_nrwidth_width < 2)
1138 		&& (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u'))
1139 	    wp->w_nrwidth_line_count = 0;
1140 }
1141 
1142 /*
1143  * Place a sign at the specified file location or update a sign.
1144  */
1145     int
sign_place(int * sign_id,char_u * sign_group,char_u * sign_name,buf_T * buf,linenr_T lnum,int prio)1146 sign_place(
1147 	int		*sign_id,
1148 	char_u		*sign_group,
1149 	char_u		*sign_name,
1150 	buf_T		*buf,
1151 	linenr_T	lnum,
1152 	int		prio)
1153 {
1154     sign_T	*sp;
1155 
1156     // Check for reserved character '*' in group name
1157     if (sign_group != NULL && (*sign_group == '*' || *sign_group == '\0'))
1158 	return FAIL;
1159 
1160     FOR_ALL_SIGNS(sp)
1161 	if (STRCMP(sp->sn_name, sign_name) == 0)
1162 	    break;
1163     if (sp == NULL)
1164     {
1165 	semsg(_("E155: Unknown sign: %s"), sign_name);
1166 	return FAIL;
1167     }
1168     if (*sign_id == 0)
1169 	*sign_id = sign_group_get_next_signid(buf, sign_group);
1170 
1171     if (lnum > 0)
1172 	// ":sign place {id} line={lnum} name={name} file={fname}":
1173 	// place a sign
1174 	buf_addsign(buf, *sign_id, sign_group, prio, lnum, sp->sn_typenr);
1175     else
1176 	// ":sign place {id} file={fname}": change sign type and/or priority
1177 	lnum = buf_change_sign_type(buf, *sign_id, sign_group, sp->sn_typenr,
1178 								prio);
1179     if (lnum > 0)
1180     {
1181 	redraw_buf_line_later(buf, lnum);
1182 
1183 	// When displaying signs in the 'number' column, if the width of the
1184 	// number column is less than 2, then force recomputing the width.
1185 	may_force_numberwidth_recompute(buf, FALSE);
1186     }
1187     else
1188     {
1189 	semsg(_("E885: Not possible to change sign %s"), sign_name);
1190 	return FAIL;
1191     }
1192 
1193     return OK;
1194 }
1195 
1196 /*
1197  * Unplace the specified sign
1198  */
1199     static int
sign_unplace(int sign_id,char_u * sign_group,buf_T * buf,linenr_T atlnum)1200 sign_unplace(int sign_id, char_u *sign_group, buf_T *buf, linenr_T atlnum)
1201 {
1202     if (buf->b_signlist == NULL)	// No signs in the buffer
1203 	return OK;
1204 
1205     if (sign_id == 0)
1206     {
1207 	// Delete all the signs in the specified buffer
1208 	redraw_buf_later(buf, NOT_VALID);
1209 	buf_delete_signs(buf, sign_group);
1210     }
1211     else
1212     {
1213 	linenr_T	lnum;
1214 
1215 	// Delete only the specified signs
1216 	lnum = buf_delsign(buf, atlnum, sign_id, sign_group);
1217 	if (lnum == 0)
1218 	    return FAIL;
1219     }
1220 
1221     // When all the signs in a buffer are removed, force recomputing the
1222     // number column width (if enabled) in all the windows displaying the
1223     // buffer if 'signcolumn' is set to 'number' in that window.
1224     if (buf->b_signlist == NULL)
1225 	may_force_numberwidth_recompute(buf, TRUE);
1226 
1227     return OK;
1228 }
1229 
1230 /*
1231  * Unplace the sign at the current cursor line.
1232  */
1233     static void
sign_unplace_at_cursor(char_u * groupname)1234 sign_unplace_at_cursor(char_u *groupname)
1235 {
1236     int		id = -1;
1237 
1238     id = buf_findsign_id(curwin->w_buffer, curwin->w_cursor.lnum, groupname);
1239     if (id > 0)
1240 	sign_unplace(id, groupname, curwin->w_buffer, curwin->w_cursor.lnum);
1241     else
1242 	emsg(_("E159: Missing sign number"));
1243 }
1244 
1245 /*
1246  * Jump to a sign.
1247  */
1248     static linenr_T
sign_jump(int sign_id,char_u * sign_group,buf_T * buf)1249 sign_jump(int sign_id, char_u *sign_group, buf_T *buf)
1250 {
1251     linenr_T	lnum;
1252 
1253     if ((lnum = buf_findsign(buf, sign_id, sign_group)) <= 0)
1254     {
1255 	semsg(_("E157: Invalid sign ID: %d"), sign_id);
1256 	return -1;
1257     }
1258 
1259     // goto a sign ...
1260     if (buf_jump_open_win(buf) != NULL)
1261     {			// ... in a current window
1262 	curwin->w_cursor.lnum = lnum;
1263 	check_cursor_lnum();
1264 	beginline(BL_WHITE);
1265     }
1266     else
1267     {			// ... not currently in a window
1268 	char_u	*cmd;
1269 
1270 	if (buf->b_fname == NULL)
1271 	{
1272 	    emsg(_("E934: Cannot jump to a buffer that does not have a name"));
1273 	    return -1;
1274 	}
1275 	cmd = alloc(STRLEN(buf->b_fname) + 25);
1276 	if (cmd == NULL)
1277 	    return -1;
1278 	sprintf((char *)cmd, "e +%ld %s", (long)lnum, buf->b_fname);
1279 	do_cmdline_cmd(cmd);
1280 	vim_free(cmd);
1281     }
1282 # ifdef FEAT_FOLDING
1283     foldOpenCursor();
1284 # endif
1285 
1286     return lnum;
1287 }
1288 
1289 /*
1290  * ":sign define {name} ..." command
1291  */
1292     static void
sign_define_cmd(char_u * sign_name,char_u * cmdline)1293 sign_define_cmd(char_u *sign_name, char_u *cmdline)
1294 {
1295     char_u	*arg;
1296     char_u	*p = cmdline;
1297     char_u	*icon = NULL;
1298     char_u	*text = NULL;
1299     char_u	*linehl = NULL;
1300     char_u	*texthl = NULL;
1301     int failed = FALSE;
1302 
1303     // set values for a defined sign.
1304     for (;;)
1305     {
1306 	arg = skipwhite(p);
1307 	if (*arg == NUL)
1308 	    break;
1309 	p = skiptowhite_esc(arg);
1310 	if (STRNCMP(arg, "icon=", 5) == 0)
1311 	{
1312 	    arg += 5;
1313 	    icon = vim_strnsave(arg, p - arg);
1314 	}
1315 	else if (STRNCMP(arg, "text=", 5) == 0)
1316 	{
1317 	    arg += 5;
1318 	    text = vim_strnsave(arg, p - arg);
1319 	}
1320 	else if (STRNCMP(arg, "linehl=", 7) == 0)
1321 	{
1322 	    arg += 7;
1323 	    linehl = vim_strnsave(arg, p - arg);
1324 	}
1325 	else if (STRNCMP(arg, "texthl=", 7) == 0)
1326 	{
1327 	    arg += 7;
1328 	    texthl = vim_strnsave(arg, p - arg);
1329 	}
1330 	else
1331 	{
1332 	    semsg(_(e_invarg2), arg);
1333 	    failed = TRUE;
1334 	    break;
1335 	}
1336     }
1337 
1338     if (!failed)
1339 	sign_define_by_name(sign_name, icon, linehl, text, texthl);
1340 
1341     vim_free(icon);
1342     vim_free(text);
1343     vim_free(linehl);
1344     vim_free(texthl);
1345 }
1346 
1347 /*
1348  * ":sign place" command
1349  */
1350     static void
sign_place_cmd(buf_T * buf,linenr_T lnum,char_u * sign_name,int id,char_u * group,int prio)1351 sign_place_cmd(
1352 	buf_T		*buf,
1353 	linenr_T	lnum,
1354 	char_u		*sign_name,
1355 	int		id,
1356 	char_u		*group,
1357 	int		prio)
1358 {
1359     if (id <= 0)
1360     {
1361 	// List signs placed in a file/buffer
1362 	//   :sign place file={fname}
1363 	//   :sign place group={group} file={fname}
1364 	//   :sign place group=* file={fname}
1365 	//   :sign place buffer={nr}
1366 	//   :sign place group={group} buffer={nr}
1367 	//   :sign place group=* buffer={nr}
1368 	//   :sign place
1369 	//   :sign place group={group}
1370 	//   :sign place group=*
1371 	if (lnum >= 0 || sign_name != NULL
1372 		|| (group != NULL && *group == '\0'))
1373 	    emsg(_(e_invarg));
1374 	else
1375 	    sign_list_placed(buf, group);
1376     }
1377     else
1378     {
1379 	// Place a new sign
1380 	if (sign_name == NULL || buf == NULL
1381 		|| (group != NULL && *group == '\0'))
1382 	{
1383 	    emsg(_(e_invarg));
1384 	    return;
1385 	}
1386 
1387 	sign_place(&id, group, sign_name, buf, lnum, prio);
1388     }
1389 }
1390 
1391 /*
1392  * ":sign unplace" command
1393  */
1394     static void
sign_unplace_cmd(buf_T * buf,linenr_T lnum,char_u * sign_name,int id,char_u * group)1395 sign_unplace_cmd(
1396 	buf_T		*buf,
1397 	linenr_T	lnum,
1398 	char_u		*sign_name,
1399 	int		id,
1400 	char_u		*group)
1401 {
1402     if (lnum >= 0 || sign_name != NULL || (group != NULL && *group == '\0'))
1403     {
1404 	emsg(_(e_invarg));
1405 	return;
1406     }
1407 
1408     if (id == -2)
1409     {
1410 	if (buf != NULL)
1411 	    // :sign unplace * file={fname}
1412 	    // :sign unplace * group={group} file={fname}
1413 	    // :sign unplace * group=* file={fname}
1414 	    // :sign unplace * buffer={nr}
1415 	    // :sign unplace * group={group} buffer={nr}
1416 	    // :sign unplace * group=* buffer={nr}
1417 	    sign_unplace(0, group, buf, 0);
1418 	else
1419 	    // :sign unplace *
1420 	    // :sign unplace * group={group}
1421 	    // :sign unplace * group=*
1422 	    FOR_ALL_BUFFERS(buf)
1423 		if (buf->b_signlist != NULL)
1424 		    buf_delete_signs(buf, group);
1425     }
1426     else
1427     {
1428 	if (buf != NULL)
1429 	    // :sign unplace {id} file={fname}
1430 	    // :sign unplace {id} group={group} file={fname}
1431 	    // :sign unplace {id} group=* file={fname}
1432 	    // :sign unplace {id} buffer={nr}
1433 	    // :sign unplace {id} group={group} buffer={nr}
1434 	    // :sign unplace {id} group=* buffer={nr}
1435 	    sign_unplace(id, group, buf, 0);
1436 	else
1437 	{
1438 	    if (id == -1)
1439 	    {
1440 		// :sign unplace group={group}
1441 		// :sign unplace group=*
1442 		sign_unplace_at_cursor(group);
1443 	    }
1444 	    else
1445 	    {
1446 		// :sign unplace {id}
1447 		// :sign unplace {id} group={group}
1448 		// :sign unplace {id} group=*
1449 		FOR_ALL_BUFFERS(buf)
1450 		    sign_unplace(id, group, buf, 0);
1451 	    }
1452 	}
1453     }
1454 }
1455 
1456 /*
1457  * Jump to a placed sign commands:
1458  *   :sign jump {id} file={fname}
1459  *   :sign jump {id} buffer={nr}
1460  *   :sign jump {id} group={group} file={fname}
1461  *   :sign jump {id} group={group} buffer={nr}
1462  */
1463     static void
sign_jump_cmd(buf_T * buf,linenr_T lnum,char_u * sign_name,int id,char_u * group)1464 sign_jump_cmd(
1465 	buf_T		*buf,
1466 	linenr_T	lnum,
1467 	char_u		*sign_name,
1468 	int		id,
1469 	char_u		*group)
1470 {
1471     if (sign_name == NULL && group == NULL && id == -1)
1472     {
1473 	emsg(_(e_argreq));
1474 	return;
1475     }
1476 
1477     if (buf == NULL || (group != NULL && *group == '\0')
1478 					     || lnum >= 0 || sign_name != NULL)
1479     {
1480 	// File or buffer is not specified or an empty group is used
1481 	// or a line number or a sign name is specified.
1482 	emsg(_(e_invarg));
1483 	return;
1484     }
1485     (void)sign_jump(id, group, buf);
1486 }
1487 
1488 /*
1489  * Parse the command line arguments for the ":sign place", ":sign unplace" and
1490  * ":sign jump" commands.
1491  * The supported arguments are: line={lnum} name={name} group={group}
1492  * priority={prio} and file={fname} or buffer={nr}.
1493  */
1494     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)1495 parse_sign_cmd_args(
1496 	int	    cmd,
1497 	char_u	    *arg,
1498 	char_u	    **sign_name,
1499 	int	    *signid,
1500 	char_u	    **group,
1501 	int	    *prio,
1502 	buf_T	    **buf,
1503 	linenr_T    *lnum)
1504 {
1505     char_u	*arg1;
1506     char_u	*name;
1507     char_u	*filename = NULL;
1508     int		lnum_arg = FALSE;
1509 
1510     // first arg could be placed sign id
1511     arg1 = arg;
1512     if (VIM_ISDIGIT(*arg))
1513     {
1514 	*signid = getdigits(&arg);
1515 	if (!VIM_ISWHITE(*arg) && *arg != NUL)
1516 	{
1517 	    *signid = -1;
1518 	    arg = arg1;
1519 	}
1520 	else
1521 	    arg = skipwhite(arg);
1522     }
1523 
1524     while (*arg != NUL)
1525     {
1526 	if (STRNCMP(arg, "line=", 5) == 0)
1527 	{
1528 	    arg += 5;
1529 	    *lnum = atoi((char *)arg);
1530 	    arg = skiptowhite(arg);
1531 	    lnum_arg = TRUE;
1532 	}
1533 	else if (STRNCMP(arg, "*", 1) == 0 && cmd == SIGNCMD_UNPLACE)
1534 	{
1535 	    if (*signid != -1)
1536 	    {
1537 		emsg(_(e_invarg));
1538 		return FAIL;
1539 	    }
1540 	    *signid = -2;
1541 	    arg = skiptowhite(arg + 1);
1542 	}
1543 	else if (STRNCMP(arg, "name=", 5) == 0)
1544 	{
1545 	    arg += 5;
1546 	    name = arg;
1547 	    arg = skiptowhite(arg);
1548 	    if (*arg != NUL)
1549 		*arg++ = NUL;
1550 	    while (name[0] == '0' && name[1] != NUL)
1551 		++name;
1552 	    *sign_name = name;
1553 	}
1554 	else if (STRNCMP(arg, "group=", 6) == 0)
1555 	{
1556 	    arg += 6;
1557 	    *group = arg;
1558 	    arg = skiptowhite(arg);
1559 	    if (*arg != NUL)
1560 		*arg++ = NUL;
1561 	}
1562 	else if (STRNCMP(arg, "priority=", 9) == 0)
1563 	{
1564 	    arg += 9;
1565 	    *prio = atoi((char *)arg);
1566 	    arg = skiptowhite(arg);
1567 	}
1568 	else if (STRNCMP(arg, "file=", 5) == 0)
1569 	{
1570 	    arg += 5;
1571 	    filename = arg;
1572 	    *buf = buflist_findname_exp(arg);
1573 	    break;
1574 	}
1575 	else if (STRNCMP(arg, "buffer=", 7) == 0)
1576 	{
1577 	    arg += 7;
1578 	    filename = arg;
1579 	    *buf = buflist_findnr((int)getdigits(&arg));
1580 	    if (*skipwhite(arg) != NUL)
1581 		semsg(_(e_trailing_arg), arg);
1582 	    break;
1583 	}
1584 	else
1585 	{
1586 	    emsg(_(e_invarg));
1587 	    return FAIL;
1588 	}
1589 	arg = skipwhite(arg);
1590     }
1591 
1592     if (filename != NULL && *buf == NULL)
1593     {
1594 	semsg(_("E158: Invalid buffer name: %s"), filename);
1595 	return FAIL;
1596     }
1597 
1598     // If the filename is not supplied for the sign place or the sign jump
1599     // command, then use the current buffer.
1600     if (filename == NULL && ((cmd == SIGNCMD_PLACE && lnum_arg)
1601 						       || cmd == SIGNCMD_JUMP))
1602 	*buf = curwin->w_buffer;
1603 
1604     return OK;
1605 }
1606 
1607 /*
1608  * ":sign" command
1609  */
1610     void
ex_sign(exarg_T * eap)1611 ex_sign(exarg_T *eap)
1612 {
1613     char_u	*arg = eap->arg;
1614     char_u	*p;
1615     int		idx;
1616     sign_T	*sp;
1617     buf_T	*buf = NULL;
1618 
1619     // Parse the subcommand.
1620     p = skiptowhite(arg);
1621     idx = sign_cmd_idx(arg, p);
1622     if (idx == SIGNCMD_LAST)
1623     {
1624 	semsg(_("E160: Unknown sign command: %s"), arg);
1625 	return;
1626     }
1627     arg = skipwhite(p);
1628 
1629     if (idx <= SIGNCMD_LIST)
1630     {
1631 	// Define, undefine or list signs.
1632 	if (idx == SIGNCMD_LIST && *arg == NUL)
1633 	{
1634 	    // ":sign list": list all defined signs
1635 	    for (sp = first_sign; sp != NULL && !got_int; sp = sp->sn_next)
1636 		sign_list_defined(sp);
1637 	}
1638 	else if (*arg == NUL)
1639 	    emsg(_("E156: Missing sign name"));
1640 	else
1641 	{
1642 	    char_u	*name;
1643 
1644 	    // Isolate the sign name.  If it's a number skip leading zeroes,
1645 	    // so that "099" and "99" are the same sign.  But keep "0".
1646 	    p = skiptowhite(arg);
1647 	    if (*p != NUL)
1648 		*p++ = NUL;
1649 	    while (arg[0] == '0' && arg[1] != NUL)
1650 		++arg;
1651 	    name = vim_strsave(arg);
1652 
1653 	    if (idx == SIGNCMD_DEFINE)
1654 		sign_define_cmd(name, p);
1655 	    else if (idx == SIGNCMD_LIST)
1656 		// ":sign list {name}"
1657 		sign_list_by_name(name);
1658 	    else
1659 		// ":sign undefine {name}"
1660 		sign_undefine_by_name(name, TRUE);
1661 
1662 	    vim_free(name);
1663 	    return;
1664 	}
1665     }
1666     else
1667     {
1668 	int		id = -1;
1669 	linenr_T	lnum = -1;
1670 	char_u		*sign_name = NULL;
1671 	char_u		*group = NULL;
1672 	int		prio = SIGN_DEF_PRIO;
1673 
1674 	// Parse command line arguments
1675 	if (parse_sign_cmd_args(idx, arg, &sign_name, &id, &group, &prio,
1676 							  &buf, &lnum) == FAIL)
1677 	    return;
1678 
1679 	if (idx == SIGNCMD_PLACE)
1680 	    sign_place_cmd(buf, lnum, sign_name, id, group, prio);
1681 	else if (idx == SIGNCMD_UNPLACE)
1682 	    sign_unplace_cmd(buf, lnum, sign_name, id, group);
1683 	else if (idx == SIGNCMD_JUMP)
1684 	    sign_jump_cmd(buf, lnum, sign_name, id, group);
1685     }
1686 }
1687 
1688 /*
1689  * Return information about a specified sign
1690  */
1691     static void
sign_getinfo(sign_T * sp,dict_T * retdict)1692 sign_getinfo(sign_T *sp, dict_T *retdict)
1693 {
1694     char_u	*p;
1695 
1696     dict_add_string(retdict, "name", (char_u *)sp->sn_name);
1697     if (sp->sn_icon != NULL)
1698 	dict_add_string(retdict, "icon", (char_u *)sp->sn_icon);
1699     if (sp->sn_text != NULL)
1700 	dict_add_string(retdict, "text", (char_u *)sp->sn_text);
1701     if (sp->sn_line_hl > 0)
1702     {
1703 	p = get_highlight_name_ext(NULL, sp->sn_line_hl - 1, FALSE);
1704 	if (p == NULL)
1705 	    p = (char_u *)"NONE";
1706 	dict_add_string(retdict, "linehl", (char_u *)p);
1707     }
1708     if (sp->sn_text_hl > 0)
1709     {
1710 	p = get_highlight_name_ext(NULL, sp->sn_text_hl - 1, FALSE);
1711 	if (p == NULL)
1712 	    p = (char_u *)"NONE";
1713 	dict_add_string(retdict, "texthl", (char_u *)p);
1714     }
1715 }
1716 
1717 /*
1718  * If 'name' is NULL, return a list of all the defined signs.
1719  * Otherwise, return information about the specified sign.
1720  */
1721     static void
sign_getlist(char_u * name,list_T * retlist)1722 sign_getlist(char_u *name, list_T *retlist)
1723 {
1724     sign_T	*sp = first_sign;
1725     dict_T	*dict;
1726 
1727     if (name != NULL)
1728     {
1729 	sp = sign_find(name, NULL);
1730 	if (sp == NULL)
1731 	    return;
1732     }
1733 
1734     for (; sp != NULL && !got_int; sp = sp->sn_next)
1735     {
1736 	if ((dict = dict_alloc_id(aid_sign_getlist)) == NULL)
1737 	    return;
1738 	if (list_append_dict(retlist, dict) == FAIL)
1739 	    return;
1740 	sign_getinfo(sp, dict);
1741 
1742 	if (name != NULL)	    // handle only the specified sign
1743 	    break;
1744     }
1745 }
1746 
1747 /*
1748  * Returns information about signs placed in a buffer as list of dicts.
1749  */
1750     void
get_buffer_signs(buf_T * buf,list_T * l)1751 get_buffer_signs(buf_T *buf, list_T *l)
1752 {
1753     sign_entry_T	*sign;
1754     dict_T		*d;
1755 
1756     FOR_ALL_SIGNS_IN_BUF(buf, sign)
1757     {
1758 	if ((d = sign_get_info(sign)) != NULL)
1759 	    list_append_dict(l, d);
1760     }
1761 }
1762 
1763 /*
1764  * Return information about all the signs placed in a buffer
1765  */
1766     static void
sign_get_placed_in_buf(buf_T * buf,linenr_T lnum,int sign_id,char_u * sign_group,list_T * retlist)1767 sign_get_placed_in_buf(
1768 	buf_T		*buf,
1769 	linenr_T	lnum,
1770 	int		sign_id,
1771 	char_u		*sign_group,
1772 	list_T		*retlist)
1773 {
1774     dict_T		*d;
1775     list_T		*l;
1776     sign_entry_T	*sign;
1777     dict_T		*sdict;
1778 
1779     if ((d = dict_alloc_id(aid_sign_getplaced_dict)) == NULL)
1780 	return;
1781     list_append_dict(retlist, d);
1782 
1783     dict_add_number(d, "bufnr", (long)buf->b_fnum);
1784 
1785     if ((l = list_alloc_id(aid_sign_getplaced_list)) == NULL)
1786 	return;
1787     dict_add_list(d, "signs", l);
1788 
1789     FOR_ALL_SIGNS_IN_BUF(buf, sign)
1790     {
1791 	if (!sign_in_group(sign, sign_group))
1792 	    continue;
1793 	if ((lnum == 0 && sign_id == 0)
1794 		|| (sign_id == 0 && lnum == sign->se_lnum)
1795 		|| (lnum == 0 && sign_id == sign->se_id)
1796 		|| (lnum == sign->se_lnum && sign_id == sign->se_id))
1797 	{
1798 	    if ((sdict = sign_get_info(sign)) != NULL)
1799 		list_append_dict(l, sdict);
1800 	}
1801     }
1802 }
1803 
1804 /*
1805  * Get a list of signs placed in buffer 'buf'. If 'num' is non-zero, return the
1806  * sign placed at the line number. If 'lnum' is zero, return all the signs
1807  * placed in 'buf'. If 'buf' is NULL, return signs placed in all the buffers.
1808  */
1809     static void
sign_get_placed(buf_T * buf,linenr_T lnum,int sign_id,char_u * sign_group,list_T * retlist)1810 sign_get_placed(
1811 	buf_T		*buf,
1812 	linenr_T	lnum,
1813 	int		sign_id,
1814 	char_u		*sign_group,
1815 	list_T		*retlist)
1816 {
1817     if (buf != NULL)
1818 	sign_get_placed_in_buf(buf, lnum, sign_id, sign_group, retlist);
1819     else
1820     {
1821 	FOR_ALL_BUFFERS(buf)
1822 	    if (buf->b_signlist != NULL)
1823 		sign_get_placed_in_buf(buf, 0, sign_id, sign_group, retlist);
1824     }
1825 }
1826 
1827 # if defined(FEAT_SIGN_ICONS) || defined(PROTO)
1828 /*
1829  * Allocate the icons.  Called when the GUI has started.  Allows defining
1830  * signs before it starts.
1831  */
1832     void
sign_gui_started(void)1833 sign_gui_started(void)
1834 {
1835     sign_T	*sp;
1836 
1837     FOR_ALL_SIGNS(sp)
1838 	if (sp->sn_icon != NULL)
1839 	    sp->sn_image = gui_mch_register_sign(sp->sn_icon);
1840 }
1841 # endif
1842 
1843 /*
1844  * List one sign.
1845  */
1846     static void
sign_list_defined(sign_T * sp)1847 sign_list_defined(sign_T *sp)
1848 {
1849     char_u	*p;
1850 
1851     smsg("sign %s", sp->sn_name);
1852     if (sp->sn_icon != NULL)
1853     {
1854 	msg_puts(" icon=");
1855 	msg_outtrans(sp->sn_icon);
1856 # ifdef FEAT_SIGN_ICONS
1857 	if (sp->sn_image == NULL)
1858 	    msg_puts(_(" (NOT FOUND)"));
1859 # else
1860 	msg_puts(_(" (not supported)"));
1861 # endif
1862     }
1863     if (sp->sn_text != NULL)
1864     {
1865 	msg_puts(" text=");
1866 	msg_outtrans(sp->sn_text);
1867     }
1868     if (sp->sn_line_hl > 0)
1869     {
1870 	msg_puts(" linehl=");
1871 	p = get_highlight_name_ext(NULL, sp->sn_line_hl - 1, FALSE);
1872 	if (p == NULL)
1873 	    msg_puts("NONE");
1874 	else
1875 	    msg_puts((char *)p);
1876     }
1877     if (sp->sn_text_hl > 0)
1878     {
1879 	msg_puts(" texthl=");
1880 	p = get_highlight_name_ext(NULL, sp->sn_text_hl - 1, FALSE);
1881 	if (p == NULL)
1882 	    msg_puts("NONE");
1883 	else
1884 	    msg_puts((char *)p);
1885     }
1886 }
1887 
1888 /*
1889  * Undefine a sign and free its memory.
1890  */
1891     static void
sign_undefine(sign_T * sp,sign_T * sp_prev)1892 sign_undefine(sign_T *sp, sign_T *sp_prev)
1893 {
1894     vim_free(sp->sn_name);
1895     vim_free(sp->sn_icon);
1896 # ifdef FEAT_SIGN_ICONS
1897     if (sp->sn_image != NULL)
1898     {
1899 	out_flush();
1900 	gui_mch_destroy_sign(sp->sn_image);
1901     }
1902 # endif
1903     vim_free(sp->sn_text);
1904     if (sp_prev == NULL)
1905 	first_sign = sp->sn_next;
1906     else
1907 	sp_prev->sn_next = sp->sn_next;
1908     vim_free(sp);
1909 }
1910 
1911 # if defined(FEAT_SIGN_ICONS) || defined(PROTO)
1912     void *
sign_get_image(int typenr)1913 sign_get_image(
1914     int		typenr)		// the attribute which may have a sign
1915 {
1916     sign_T	*sp;
1917 
1918     FOR_ALL_SIGNS(sp)
1919 	if (sp->sn_typenr == typenr)
1920 	    return sp->sn_image;
1921     return NULL;
1922 }
1923 # endif
1924 
1925 /*
1926  * Undefine/free all signs.
1927  */
1928     void
free_signs(void)1929 free_signs(void)
1930 {
1931     while (first_sign != NULL)
1932 	sign_undefine(first_sign, NULL);
1933 }
1934 
1935 static enum
1936 {
1937     EXP_SUBCMD,		// expand :sign sub-commands
1938     EXP_DEFINE,		// expand :sign define {name} args
1939     EXP_PLACE,		// expand :sign place {id} args
1940     EXP_LIST,		// expand :sign place args
1941     EXP_UNPLACE,	// expand :sign unplace"
1942     EXP_SIGN_NAMES,	// expand with name of placed signs
1943     EXP_SIGN_GROUPS	// expand with name of placed sign groups
1944 } expand_what;
1945 
1946 /*
1947  * Return the n'th sign name (used for command line completion)
1948  */
1949     static char_u *
get_nth_sign_name(int idx)1950 get_nth_sign_name(int idx)
1951 {
1952     int		current_idx;
1953     sign_T	*sp;
1954 
1955     // Complete with name of signs already defined
1956     current_idx = 0;
1957     FOR_ALL_SIGNS(sp)
1958 	if (current_idx++ == idx)
1959 	    return sp->sn_name;
1960     return NULL;
1961 }
1962 
1963 /*
1964  * Return the n'th sign group name (used for command line completion)
1965  */
1966     static char_u *
get_nth_sign_group_name(int idx)1967 get_nth_sign_group_name(int idx)
1968 {
1969     int		current_idx;
1970     int		todo;
1971     hashitem_T	*hi;
1972     signgroup_T	*group;
1973 
1974     // Complete with name of sign groups already defined
1975     current_idx = 0;
1976     todo = (int)sg_table.ht_used;
1977     for (hi = sg_table.ht_array; todo > 0; ++hi)
1978     {
1979 	if (!HASHITEM_EMPTY(hi))
1980 	{
1981 	    --todo;
1982 	    if (current_idx++ == idx)
1983 	    {
1984 		group = HI2SG(hi);
1985 		return group->sg_name;
1986 	    }
1987 	}
1988     }
1989     return NULL;
1990 }
1991 
1992 /*
1993  * Function given to ExpandGeneric() to obtain the sign command
1994  * expansion.
1995  */
1996     char_u *
get_sign_name(expand_T * xp UNUSED,int idx)1997 get_sign_name(expand_T *xp UNUSED, int idx)
1998 {
1999     switch (expand_what)
2000     {
2001     case EXP_SUBCMD:
2002 	return (char_u *)cmds[idx];
2003     case EXP_DEFINE:
2004 	{
2005 	    char *define_arg[] =
2006 	    {
2007 		"icon=", "linehl=", "text=", "texthl=", NULL
2008 	    };
2009 	    return (char_u *)define_arg[idx];
2010 	}
2011     case EXP_PLACE:
2012 	{
2013 	    char *place_arg[] =
2014 	    {
2015 		"line=", "name=", "group=", "priority=", "file=",
2016 		"buffer=", NULL
2017 	    };
2018 	    return (char_u *)place_arg[idx];
2019 	}
2020     case EXP_LIST:
2021 	{
2022 	    char *list_arg[] =
2023 	    {
2024 		"group=", "file=", "buffer=", NULL
2025 	    };
2026 	    return (char_u *)list_arg[idx];
2027 	}
2028     case EXP_UNPLACE:
2029 	{
2030 	    char *unplace_arg[] = { "group=", "file=", "buffer=", NULL };
2031 	    return (char_u *)unplace_arg[idx];
2032 	}
2033     case EXP_SIGN_NAMES:
2034 	return get_nth_sign_name(idx);
2035     case EXP_SIGN_GROUPS:
2036 	return get_nth_sign_group_name(idx);
2037     default:
2038 	return NULL;
2039     }
2040 }
2041 
2042 /*
2043  * Handle command line completion for :sign command.
2044  */
2045     void
set_context_in_sign_cmd(expand_T * xp,char_u * arg)2046 set_context_in_sign_cmd(expand_T *xp, char_u *arg)
2047 {
2048     char_u	*p;
2049     char_u	*end_subcmd;
2050     char_u	*last;
2051     int		cmd_idx;
2052     char_u	*begin_subcmd_args;
2053 
2054     // Default: expand subcommands.
2055     xp->xp_context = EXPAND_SIGN;
2056     expand_what = EXP_SUBCMD;
2057     xp->xp_pattern = arg;
2058 
2059     end_subcmd = skiptowhite(arg);
2060     if (*end_subcmd == NUL)
2061 	// expand subcmd name
2062 	// :sign {subcmd}<CTRL-D>
2063 	return;
2064 
2065     cmd_idx = sign_cmd_idx(arg, end_subcmd);
2066 
2067     // :sign {subcmd} {subcmd_args}
2068     //		      |
2069     //		      begin_subcmd_args
2070     begin_subcmd_args = skipwhite(end_subcmd);
2071 
2072     // expand last argument of subcmd
2073 
2074     // :sign define {name} {args}...
2075     //		    |
2076     //		    p
2077 
2078     // Loop until reaching last argument.
2079     p = begin_subcmd_args;
2080     do
2081     {
2082 	p = skipwhite(p);
2083 	last = p;
2084 	p = skiptowhite(p);
2085     } while (*p != NUL);
2086 
2087     p = vim_strchr(last, '=');
2088 
2089     // :sign define {name} {args}... {last}=
2090     //				     |	   |
2091     //				  last	   p
2092     if (p == NULL)
2093     {
2094 	// Expand last argument name (before equal sign).
2095 	xp->xp_pattern = last;
2096 	switch (cmd_idx)
2097 	{
2098 	    case SIGNCMD_DEFINE:
2099 		expand_what = EXP_DEFINE;
2100 		break;
2101 	    case SIGNCMD_PLACE:
2102 		// List placed signs
2103 		if (VIM_ISDIGIT(*begin_subcmd_args))
2104 		    //   :sign place {id} {args}...
2105 		    expand_what = EXP_PLACE;
2106 		else
2107 		    //   :sign place {args}...
2108 		    expand_what = EXP_LIST;
2109 		break;
2110 	    case SIGNCMD_LIST:
2111 	    case SIGNCMD_UNDEFINE:
2112 		// :sign list <CTRL-D>
2113 		// :sign undefine <CTRL-D>
2114 		expand_what = EXP_SIGN_NAMES;
2115 		break;
2116 	    case SIGNCMD_JUMP:
2117 	    case SIGNCMD_UNPLACE:
2118 		expand_what = EXP_UNPLACE;
2119 		break;
2120 	    default:
2121 		xp->xp_context = EXPAND_NOTHING;
2122 	}
2123     }
2124     else
2125     {
2126 	// Expand last argument value (after equal sign).
2127 	xp->xp_pattern = p + 1;
2128 	switch (cmd_idx)
2129 	{
2130 	    case SIGNCMD_DEFINE:
2131 		if (STRNCMP(last, "texthl", 6) == 0
2132 			|| STRNCMP(last, "linehl", 6) == 0)
2133 		    xp->xp_context = EXPAND_HIGHLIGHT;
2134 		else if (STRNCMP(last, "icon", 4) == 0)
2135 		    xp->xp_context = EXPAND_FILES;
2136 		else
2137 		    xp->xp_context = EXPAND_NOTHING;
2138 		break;
2139 	    case SIGNCMD_PLACE:
2140 		if (STRNCMP(last, "name", 4) == 0)
2141 		    expand_what = EXP_SIGN_NAMES;
2142 		else if (STRNCMP(last, "group", 5) == 0)
2143 		    expand_what = EXP_SIGN_GROUPS;
2144 		else if (STRNCMP(last, "file", 4) == 0)
2145 		    xp->xp_context = EXPAND_BUFFERS;
2146 		else
2147 		    xp->xp_context = EXPAND_NOTHING;
2148 		break;
2149 	    case SIGNCMD_UNPLACE:
2150 	    case SIGNCMD_JUMP:
2151 		if (STRNCMP(last, "group", 5) == 0)
2152 		    expand_what = EXP_SIGN_GROUPS;
2153 		else if (STRNCMP(last, "file", 4) == 0)
2154 		    xp->xp_context = EXPAND_BUFFERS;
2155 		else
2156 		    xp->xp_context = EXPAND_NOTHING;
2157 		break;
2158 	    default:
2159 		xp->xp_context = EXPAND_NOTHING;
2160 	}
2161     }
2162 }
2163 
2164 /*
2165  * Define a sign using the attributes in 'dict'. Returns 0 on success and -1 on
2166  * failure.
2167  */
2168     static int
sign_define_from_dict(char_u * name_arg,dict_T * dict)2169 sign_define_from_dict(char_u *name_arg, dict_T *dict)
2170 {
2171     char_u	*name = NULL;
2172     char_u	*icon = NULL;
2173     char_u	*linehl = NULL;
2174     char_u	*text = NULL;
2175     char_u	*texthl = NULL;
2176     int		retval = -1;
2177 
2178     if (name_arg == NULL)
2179     {
2180 	if (dict == NULL)
2181 	    return -1;
2182 	name = dict_get_string(dict, (char_u *)"name", TRUE);
2183     }
2184     else
2185 	name = vim_strsave(name_arg);
2186     if (name == NULL || name[0] == NUL)
2187 	goto cleanup;
2188     if (dict != NULL)
2189     {
2190 	icon = dict_get_string(dict, (char_u *)"icon", TRUE);
2191 	linehl = dict_get_string(dict, (char_u *)"linehl", TRUE);
2192 	text = dict_get_string(dict, (char_u *)"text", TRUE);
2193 	texthl = dict_get_string(dict, (char_u *)"texthl", TRUE);
2194     }
2195 
2196     if (sign_define_by_name(name, icon, linehl, text, texthl) == OK)
2197 	retval = 0;
2198 
2199 cleanup:
2200     vim_free(name);
2201     vim_free(icon);
2202     vim_free(linehl);
2203     vim_free(text);
2204     vim_free(texthl);
2205 
2206     return retval;
2207 }
2208 
2209 /*
2210  * Define multiple signs using attributes from list 'l' and store the return
2211  * values in 'retlist'.
2212  */
2213     static void
sign_define_multiple(list_T * l,list_T * retlist)2214 sign_define_multiple(list_T *l, list_T *retlist)
2215 {
2216     listitem_T	*li;
2217     int		retval;
2218 
2219     FOR_ALL_LIST_ITEMS(l, li)
2220     {
2221 	retval = -1;
2222 	if (li->li_tv.v_type == VAR_DICT)
2223 	    retval = sign_define_from_dict(NULL, li->li_tv.vval.v_dict);
2224 	else
2225 	    emsg(_(e_dictreq));
2226 	list_append_number(retlist, retval);
2227     }
2228 }
2229 
2230 /*
2231  * "sign_define()" function
2232  */
2233     void
f_sign_define(typval_T * argvars,typval_T * rettv)2234 f_sign_define(typval_T *argvars, typval_T *rettv)
2235 {
2236     char_u	*name;
2237 
2238     if (in_vim9script()
2239 	    && (check_for_string_or_list_arg(argvars, 0) == FAIL
2240 		|| check_for_opt_dict_arg(argvars, 1) == FAIL))
2241 	return;
2242 
2243     if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN)
2244     {
2245 	// Define multiple signs
2246 	if (rettv_list_alloc(rettv) != OK)
2247 	    return;
2248 
2249 	sign_define_multiple(argvars[0].vval.v_list, rettv->vval.v_list);
2250 	return;
2251     }
2252 
2253     // Define a single sign
2254     rettv->vval.v_number = -1;
2255 
2256     name = tv_get_string_chk(&argvars[0]);
2257     if (name == NULL)
2258 	return;
2259 
2260     if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_DICT)
2261     {
2262 	emsg(_(e_dictreq));
2263 	return;
2264     }
2265 
2266     rettv->vval.v_number = sign_define_from_dict(name,
2267 	    argvars[1].v_type == VAR_DICT ? argvars[1].vval.v_dict : NULL);
2268 }
2269 
2270 /*
2271  * "sign_getdefined()" function
2272  */
2273     void
f_sign_getdefined(typval_T * argvars,typval_T * rettv)2274 f_sign_getdefined(typval_T *argvars, typval_T *rettv)
2275 {
2276     char_u	*name = NULL;
2277 
2278     if (rettv_list_alloc_id(rettv, aid_sign_getdefined) != OK)
2279 	return;
2280 
2281     if (in_vim9script() && check_for_opt_string_arg(argvars, 0) == FAIL)
2282 	return;
2283 
2284     if (argvars[0].v_type != VAR_UNKNOWN)
2285 	name = tv_get_string(&argvars[0]);
2286 
2287     sign_getlist(name, rettv->vval.v_list);
2288 }
2289 
2290 /*
2291  * "sign_getplaced()" function
2292  */
2293     void
f_sign_getplaced(typval_T * argvars,typval_T * rettv)2294 f_sign_getplaced(typval_T *argvars, typval_T *rettv)
2295 {
2296     buf_T	*buf = NULL;
2297     dict_T	*dict;
2298     dictitem_T	*di;
2299     linenr_T	lnum = 0;
2300     int		sign_id = 0;
2301     char_u	*group = NULL;
2302     int		notanum = FALSE;
2303 
2304     if (rettv_list_alloc_id(rettv, aid_sign_getplaced) != OK)
2305 	return;
2306 
2307     if (in_vim9script()
2308 	    && (check_for_opt_buffer_arg(argvars, 0) == FAIL
2309 		|| (argvars[0].v_type != VAR_UNKNOWN
2310 		    && check_for_opt_dict_arg(argvars, 1) == FAIL)))
2311 	return;
2312 
2313     if (argvars[0].v_type != VAR_UNKNOWN)
2314     {
2315 	// get signs placed in the specified buffer
2316 	buf = get_buf_arg(&argvars[0]);
2317 	if (buf == NULL)
2318 	    return;
2319 
2320 	if (argvars[1].v_type != VAR_UNKNOWN)
2321 	{
2322 	    if (argvars[1].v_type != VAR_DICT ||
2323 				((dict = argvars[1].vval.v_dict) == NULL))
2324 	    {
2325 		emsg(_(e_dictreq));
2326 		return;
2327 	    }
2328 	    if ((di = dict_find(dict, (char_u *)"lnum", -1)) != NULL)
2329 	    {
2330 		// get signs placed at this line
2331 		(void)tv_get_number_chk(&di->di_tv, &notanum);
2332 		if (notanum)
2333 		    return;
2334 		lnum = tv_get_lnum(&di->di_tv);
2335 	    }
2336 	    if ((di = dict_find(dict, (char_u *)"id", -1)) != NULL)
2337 	    {
2338 		// get sign placed with this identifier
2339 		sign_id = (int)tv_get_number_chk(&di->di_tv, &notanum);
2340 		if (notanum)
2341 		    return;
2342 	    }
2343 	    if ((di = dict_find(dict, (char_u *)"group", -1)) != NULL)
2344 	    {
2345 		group = tv_get_string_chk(&di->di_tv);
2346 		if (group == NULL)
2347 		    return;
2348 		if (*group == '\0')	// empty string means global group
2349 		    group = NULL;
2350 	    }
2351 	}
2352     }
2353 
2354     sign_get_placed(buf, lnum, sign_id, group, rettv->vval.v_list);
2355 }
2356 
2357 /*
2358  * "sign_jump()" function
2359  */
2360     void
f_sign_jump(typval_T * argvars,typval_T * rettv)2361 f_sign_jump(typval_T *argvars, typval_T *rettv)
2362 {
2363     int		sign_id;
2364     char_u	*sign_group = NULL;
2365     buf_T	*buf;
2366     int		notanum = FALSE;
2367 
2368     rettv->vval.v_number = -1;
2369 
2370     if (in_vim9script()
2371 	    && (check_for_number_arg(argvars, 0) == FAIL
2372 		|| check_for_string_arg(argvars, 1) == FAIL
2373 		|| check_for_buffer_arg(argvars, 2) == FAIL))
2374 	return;
2375 
2376     // Sign identifier
2377     sign_id = (int)tv_get_number_chk(&argvars[0], &notanum);
2378     if (notanum)
2379 	return;
2380     if (sign_id <= 0)
2381     {
2382 	emsg(_(e_invarg));
2383 	return;
2384     }
2385 
2386     // Sign group
2387     sign_group = tv_get_string_chk(&argvars[1]);
2388     if (sign_group == NULL)
2389 	return;
2390     if (sign_group[0] == '\0')
2391 	sign_group = NULL;			// global sign group
2392     else
2393     {
2394 	sign_group = vim_strsave(sign_group);
2395 	if (sign_group == NULL)
2396 	    return;
2397     }
2398 
2399     // Buffer to place the sign
2400     buf = get_buf_arg(&argvars[2]);
2401     if (buf == NULL)
2402 	goto cleanup;
2403 
2404     rettv->vval.v_number = sign_jump(sign_id, sign_group, buf);
2405 
2406 cleanup:
2407     vim_free(sign_group);
2408 }
2409 
2410 /*
2411  * Place a new sign using the values specified in dict 'dict'. Returns the sign
2412  * identifier if successfully placed, otherwise returns 0.
2413  */
2414     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)2415 sign_place_from_dict(
2416 	typval_T	*id_tv,
2417 	typval_T	*group_tv,
2418 	typval_T	*name_tv,
2419 	typval_T	*buf_tv,
2420 	dict_T		*dict)
2421 {
2422     int		sign_id = 0;
2423     char_u	*group = NULL;
2424     char_u	*sign_name = NULL;
2425     buf_T	*buf = NULL;
2426     dictitem_T	*di;
2427     linenr_T	lnum = 0;
2428     int		prio = SIGN_DEF_PRIO;
2429     int		notanum = FALSE;
2430     int		ret_sign_id = -1;
2431 
2432     // sign identifier
2433     if (id_tv == NULL)
2434     {
2435 	di = dict_find(dict, (char_u *)"id", -1);
2436 	if (di != NULL)
2437 	    id_tv = &di->di_tv;
2438     }
2439     if (id_tv == NULL)
2440 	sign_id = 0;
2441     else
2442     {
2443 	sign_id = tv_get_number_chk(id_tv, &notanum);
2444 	if (notanum)
2445 	    return -1;
2446 	if (sign_id < 0)
2447 	{
2448 	    emsg(_(e_invarg));
2449 	    return -1;
2450 	}
2451     }
2452 
2453     // sign group
2454     if (group_tv == NULL)
2455     {
2456 	di = dict_find(dict, (char_u *)"group", -1);
2457 	if (di != NULL)
2458 	    group_tv = &di->di_tv;
2459     }
2460     if (group_tv == NULL)
2461 	group = NULL;				// global group
2462     else
2463     {
2464 	group = tv_get_string_chk(group_tv);
2465 	if (group == NULL)
2466 	    goto cleanup;
2467 	if (group[0] == '\0')			// global sign group
2468 	    group = NULL;
2469 	else
2470 	{
2471 	    group = vim_strsave(group);
2472 	    if (group == NULL)
2473 		return -1;
2474 	}
2475     }
2476 
2477     // sign name
2478     if (name_tv == NULL)
2479     {
2480 	di = dict_find(dict, (char_u *)"name", -1);
2481 	if (di != NULL)
2482 	    name_tv = &di->di_tv;
2483     }
2484     if (name_tv == NULL)
2485 	goto cleanup;
2486     sign_name = tv_get_string_chk(name_tv);
2487     if (sign_name == NULL)
2488 	goto cleanup;
2489 
2490     // buffer to place the sign
2491     if (buf_tv == NULL)
2492     {
2493 	di = dict_find(dict, (char_u *)"buffer", -1);
2494 	if (di != NULL)
2495 	    buf_tv = &di->di_tv;
2496     }
2497     if (buf_tv == NULL)
2498 	goto cleanup;
2499     buf = get_buf_arg(buf_tv);
2500     if (buf == NULL)
2501 	goto cleanup;
2502 
2503     // line number of the sign
2504     di = dict_find(dict, (char_u *)"lnum", -1);
2505     if (di != NULL)
2506     {
2507 	lnum = tv_get_lnum(&di->di_tv);
2508 	if (lnum <= 0)
2509 	{
2510 	    emsg(_(e_invarg));
2511 	    goto cleanup;
2512 	}
2513     }
2514 
2515     // sign priority
2516     di = dict_find(dict, (char_u *)"priority", -1);
2517     if (di != NULL)
2518     {
2519 	prio = (int)tv_get_number_chk(&di->di_tv, &notanum);
2520 	if (notanum)
2521 	    goto cleanup;
2522     }
2523 
2524     if (sign_place(&sign_id, group, sign_name, buf, lnum, prio) == OK)
2525 	ret_sign_id = sign_id;
2526 
2527 cleanup:
2528     vim_free(group);
2529 
2530     return ret_sign_id;
2531 }
2532 
2533 /*
2534  * "sign_place()" function
2535  */
2536     void
f_sign_place(typval_T * argvars,typval_T * rettv)2537 f_sign_place(typval_T *argvars, typval_T *rettv)
2538 {
2539     dict_T	*dict = NULL;
2540 
2541     rettv->vval.v_number = -1;
2542 
2543     if (in_vim9script()
2544 	    && (check_for_number_arg(argvars, 0) == FAIL
2545 		|| check_for_string_arg(argvars, 1) == FAIL
2546 		|| check_for_string_arg(argvars, 2) == FAIL
2547 		|| check_for_buffer_arg(argvars, 3) == FAIL
2548 		|| check_for_opt_dict_arg(argvars, 4) == FAIL))
2549 	return;
2550 
2551     if (argvars[4].v_type != VAR_UNKNOWN
2552 	    && (argvars[4].v_type != VAR_DICT
2553 		|| ((dict = argvars[4].vval.v_dict) == NULL)))
2554     {
2555 	emsg(_(e_dictreq));
2556 	return;
2557     }
2558 
2559     rettv->vval.v_number = sign_place_from_dict(&argvars[0], &argvars[1],
2560 					&argvars[2], &argvars[3], dict);
2561 }
2562 
2563 /*
2564  * "sign_placelist()" function.  Place multiple signs.
2565  */
2566     void
f_sign_placelist(typval_T * argvars,typval_T * rettv)2567 f_sign_placelist(typval_T *argvars, typval_T *rettv)
2568 {
2569     listitem_T	*li;
2570     int		sign_id;
2571 
2572     if (rettv_list_alloc(rettv) != OK)
2573 	return;
2574 
2575     if (in_vim9script() && check_for_list_arg(argvars, 0) == FAIL)
2576 	return;
2577 
2578     if (argvars[0].v_type != VAR_LIST)
2579     {
2580 	emsg(_(e_listreq));
2581 	return;
2582     }
2583 
2584     // Process the List of sign attributes
2585     FOR_ALL_LIST_ITEMS(argvars[0].vval.v_list, li)
2586     {
2587 	sign_id = -1;
2588 	if (li->li_tv.v_type == VAR_DICT)
2589 	    sign_id = sign_place_from_dict(NULL, NULL, NULL, NULL,
2590 						li->li_tv.vval.v_dict);
2591 	else
2592 	    emsg(_(e_dictreq));
2593 	list_append_number(rettv->vval.v_list, sign_id);
2594     }
2595 }
2596 
2597 /*
2598  * Undefine multiple signs
2599  */
2600     static void
sign_undefine_multiple(list_T * l,list_T * retlist)2601 sign_undefine_multiple(list_T *l, list_T *retlist)
2602 {
2603     char_u	*name;
2604     listitem_T	*li;
2605     int		retval;
2606 
2607     FOR_ALL_LIST_ITEMS(l, li)
2608     {
2609 	retval = -1;
2610 	name = tv_get_string_chk(&li->li_tv);
2611 	if (name != NULL && (sign_undefine_by_name(name, TRUE) == OK))
2612 	    retval = 0;
2613 	list_append_number(retlist, retval);
2614     }
2615 }
2616 
2617 /*
2618  * "sign_undefine()" function
2619  */
2620     void
f_sign_undefine(typval_T * argvars,typval_T * rettv)2621 f_sign_undefine(typval_T *argvars, typval_T *rettv)
2622 {
2623     char_u *name;
2624 
2625     if (in_vim9script()
2626 	    && check_for_opt_string_or_list_arg(argvars, 0) == FAIL)
2627 	return;
2628 
2629     if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN)
2630     {
2631 	// Undefine multiple signs
2632 	if (rettv_list_alloc(rettv) != OK)
2633 	    return;
2634 
2635 	sign_undefine_multiple(argvars[0].vval.v_list, rettv->vval.v_list);
2636 	return;
2637     }
2638 
2639     rettv->vval.v_number = -1;
2640 
2641     if (argvars[0].v_type == VAR_UNKNOWN)
2642     {
2643 	// Free all the signs
2644 	free_signs();
2645 	rettv->vval.v_number = 0;
2646     }
2647     else
2648     {
2649 	// Free only the specified sign
2650 	name = tv_get_string_chk(&argvars[0]);
2651 	if (name == NULL)
2652 	    return;
2653 
2654 	if (sign_undefine_by_name(name, TRUE) == OK)
2655 	    rettv->vval.v_number = 0;
2656     }
2657 }
2658 
2659 /*
2660  * Unplace the sign with attributes specified in 'dict'. Returns 0 on success
2661  * and -1 on failure.
2662  */
2663     static int
sign_unplace_from_dict(typval_T * group_tv,dict_T * dict)2664 sign_unplace_from_dict(typval_T *group_tv, dict_T *dict)
2665 {
2666     dictitem_T	*di;
2667     int		sign_id = 0;
2668     buf_T	*buf = NULL;
2669     char_u	*group = NULL;
2670     int		retval = -1;
2671 
2672     // sign group
2673     if (group_tv != NULL)
2674 	group = tv_get_string(group_tv);
2675     else
2676 	group = dict_get_string(dict, (char_u *)"group", FALSE);
2677     if (group != NULL)
2678     {
2679 	if (group[0] == '\0')			// global sign group
2680 	    group = NULL;
2681 	else
2682 	{
2683 	    group = vim_strsave(group);
2684 	    if (group == NULL)
2685 		return -1;
2686 	}
2687     }
2688 
2689     if (dict != NULL)
2690     {
2691 	if ((di = dict_find(dict, (char_u *)"buffer", -1)) != NULL)
2692 	{
2693 	    buf = get_buf_arg(&di->di_tv);
2694 	    if (buf == NULL)
2695 		goto cleanup;
2696 	}
2697 	if (dict_find(dict, (char_u *)"id", -1) != NULL)
2698 	{
2699 	    sign_id = dict_get_number(dict, (char_u *)"id");
2700 	    if (sign_id <= 0)
2701 	    {
2702 		emsg(_(e_invarg));
2703 		goto cleanup;
2704 	    }
2705 	}
2706     }
2707 
2708     if (buf == NULL)
2709     {
2710 	// Delete the sign in all the buffers
2711 	retval = 0;
2712 	FOR_ALL_BUFFERS(buf)
2713 	    if (sign_unplace(sign_id, group, buf, 0) != OK)
2714 		retval = -1;
2715     }
2716     else if (sign_unplace(sign_id, group, buf, 0) == OK)
2717 	retval = 0;
2718 
2719 cleanup:
2720     vim_free(group);
2721 
2722     return retval;
2723 }
2724 
2725     sign_entry_T *
get_first_valid_sign(win_T * wp)2726 get_first_valid_sign(win_T *wp)
2727 {
2728     sign_entry_T *sign = wp->w_buffer->b_signlist;
2729 
2730 # ifdef FEAT_PROP_POPUP
2731     while (sign != NULL && !sign_group_for_window(sign, wp))
2732 	sign = sign->se_next;
2733 # endif
2734     return sign;
2735 }
2736 
2737 /*
2738  * Return TRUE when window "wp" has a column to draw signs in.
2739  */
2740      int
signcolumn_on(win_T * wp)2741 signcolumn_on(win_T *wp)
2742 {
2743     // If 'signcolumn' is set to 'number', signs are displayed in the 'number'
2744     // column (if present). Otherwise signs are to be displayed in the sign
2745     // column.
2746     if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u')
2747 	return get_first_valid_sign(wp) != NULL && !wp->w_p_nu && !wp->w_p_rnu;
2748 
2749     if (*wp->w_p_scl == 'n')
2750 	return FALSE;
2751     if (*wp->w_p_scl == 'y')
2752 	return TRUE;
2753     return (get_first_valid_sign(wp) != NULL
2754 # ifdef FEAT_NETBEANS_INTG
2755 			|| wp->w_buffer->b_has_sign_column
2756 # endif
2757 		    );
2758 }
2759 
2760 /*
2761  * "sign_unplace()" function
2762  */
2763     void
f_sign_unplace(typval_T * argvars,typval_T * rettv)2764 f_sign_unplace(typval_T *argvars, typval_T *rettv)
2765 {
2766     dict_T	*dict = NULL;
2767 
2768     rettv->vval.v_number = -1;
2769 
2770     if (in_vim9script()
2771 	    && (check_for_string_arg(argvars, 0) == FAIL
2772 		|| check_for_opt_dict_arg(argvars, 1) == FAIL))
2773 	return;
2774 
2775     if (argvars[0].v_type != VAR_STRING)
2776     {
2777 	emsg(_(e_invarg));
2778 	return;
2779     }
2780 
2781     if (argvars[1].v_type != VAR_UNKNOWN)
2782     {
2783 	if (argvars[1].v_type != VAR_DICT)
2784 	{
2785 	    emsg(_(e_dictreq));
2786 	    return;
2787 	}
2788 	dict = argvars[1].vval.v_dict;
2789     }
2790 
2791     rettv->vval.v_number = sign_unplace_from_dict(&argvars[0], dict);
2792 }
2793 
2794 /*
2795  * "sign_unplacelist()" function
2796  */
2797     void
f_sign_unplacelist(typval_T * argvars,typval_T * rettv)2798 f_sign_unplacelist(typval_T *argvars, typval_T *rettv)
2799 {
2800     listitem_T	*li;
2801     int		retval;
2802 
2803     if (rettv_list_alloc(rettv) != OK)
2804 	return;
2805 
2806     if (in_vim9script() && check_for_list_arg(argvars, 0) == FAIL)
2807 	return;
2808 
2809     if (argvars[0].v_type != VAR_LIST)
2810     {
2811 	emsg(_(e_listreq));
2812 	return;
2813     }
2814 
2815     FOR_ALL_LIST_ITEMS(argvars[0].vval.v_list, li)
2816     {
2817 	retval = -1;
2818 	if (li->li_tv.v_type == VAR_DICT)
2819 	    retval = sign_unplace_from_dict(NULL, li->li_tv.vval.v_dict);
2820 	else
2821 	    emsg(_(e_dictreq));
2822 	list_append_number(rettv->vval.v_list, retval);
2823     }
2824 }
2825 
2826 #endif // FEAT_SIGNS
2827