xref: /vim-8.2.3635/src/sign.c (revision 94688b8a)
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 static hashtab_T	sg_table;	// sign group (signgroup_T) hashtable
61 static int		next_sign_id = 1; // next sign id in the global group
62 
63 /*
64  * Initialize data needed for managing signs
65  */
66     void
67 init_signs(void)
68 {
69     hash_init(&sg_table);		// sign group hash table
70 }
71 
72 /*
73  * A new sign in group 'groupname' is added. If the group is not present,
74  * create it. Otherwise reference the group.
75  */
76     static signgroup_T *
77 sign_group_ref(char_u *groupname)
78 {
79     hash_T		hash;
80     hashitem_T		*hi;
81     signgroup_T		*group;
82 
83     hash = hash_hash(groupname);
84     hi = hash_lookup(&sg_table, groupname, hash);
85     if (HASHITEM_EMPTY(hi))
86     {
87 	// new group
88 	group = (signgroup_T *)alloc(
89 		(unsigned)(sizeof(signgroup_T) + STRLEN(groupname)));
90 	if (group == NULL)
91 	    return NULL;
92 	STRCPY(group->sg_name, groupname);
93 	group->refcount = 1;
94 	group->next_sign_id = 1;
95 	hash_add_item(&sg_table, hi, group->sg_name, hash);
96     }
97     else
98     {
99 	// existing group
100 	group = HI2SG(hi);
101 	group->refcount++;
102     }
103 
104     return group;
105 }
106 
107 /*
108  * A sign in group 'groupname' is removed. If all the signs in this group are
109  * removed, then remove the group.
110  */
111     static void
112 sign_group_unref(char_u *groupname)
113 {
114     hashitem_T		*hi;
115     signgroup_T		*group;
116 
117     hi = hash_find(&sg_table, groupname);
118     if (!HASHITEM_EMPTY(hi))
119     {
120 	group = HI2SG(hi);
121 	group->refcount--;
122 	if (group->refcount == 0)
123 	{
124 	    // All the signs in this group are removed
125 	    hash_remove(&sg_table, hi);
126 	    vim_free(group);
127 	}
128     }
129 }
130 
131 /*
132  * Returns TRUE if 'sign' is in 'group'.
133  * A sign can either be in the global group (sign->group == NULL)
134  * or in a named group. If 'group' is '*', then the sign is part of the group.
135  */
136     static int
137 sign_in_group(signlist_T *sign, char_u *group)
138 {
139     return ((group != NULL && STRCMP(group, "*") == 0)
140 	    || (group == NULL && sign->group == NULL)
141 	    || (group != NULL && sign->group != NULL
142 				 && STRCMP(group, sign->group->sg_name) == 0));
143 }
144 
145 /*
146  * Get the next free sign identifier in the specified group
147  */
148     static int
149 sign_group_get_next_signid(buf_T *buf, char_u *groupname)
150 {
151     int			id = 1;
152     signgroup_T		*group = NULL;
153     signlist_T		*sign;
154     hashitem_T		*hi;
155     int			found = FALSE;
156 
157     if (groupname != NULL)
158     {
159 	hi = hash_find(&sg_table, groupname);
160 	if (HASHITEM_EMPTY(hi))
161 	    return id;
162 	group = HI2SG(hi);
163     }
164 
165     // Search for the next usable sign identifier
166     while (!found)
167     {
168 	if (group == NULL)
169 	    id = next_sign_id++;		// global group
170 	else
171 	    id = group->next_sign_id++;
172 
173 	// Check whether this sign is already placed in the buffer
174 	found = TRUE;
175 	FOR_ALL_SIGNS_IN_BUF(buf, sign)
176 	{
177 	    if (id == sign->id && sign_in_group(sign, groupname))
178 	    {
179 		found = FALSE;		// sign identifier is in use
180 		break;
181 	    }
182 	}
183     }
184 
185     return id;
186 }
187 
188 /*
189  * Insert a new sign into the signlist for buffer 'buf' between the 'prev' and
190  * 'next' signs.
191  */
192     static void
193 insert_sign(
194     buf_T	*buf,		// buffer to store sign in
195     signlist_T	*prev,		// previous sign entry
196     signlist_T	*next,		// next sign entry
197     int		id,		// sign ID
198     char_u	*group,		// sign group; NULL for global group
199     int		prio,		// sign priority
200     linenr_T	lnum,		// line number which gets the mark
201     int		typenr)		// typenr of sign we are adding
202 {
203     signlist_T	*newsign;
204 
205     newsign = (signlist_T *)lalloc_id((long_u)sizeof(signlist_T), FALSE,
206 							aid_insert_sign);
207     if (newsign != NULL)
208     {
209 	newsign->id = id;
210 	newsign->lnum = lnum;
211 	newsign->typenr = typenr;
212 	if (group != NULL)
213 	{
214 	    newsign->group = sign_group_ref(group);
215 	    if (newsign->group == NULL)
216 	    {
217 		vim_free(newsign);
218 		return;
219 	    }
220 	}
221 	else
222 	    newsign->group = NULL;
223 	newsign->priority = prio;
224 	newsign->next = next;
225 	newsign->prev = prev;
226 	if (next != NULL)
227 	    next->prev = newsign;
228 
229 	if (prev == NULL)
230 	{
231 	    // When adding first sign need to redraw the windows to create the
232 	    // column for signs.
233 	    if (buf->b_signlist == NULL)
234 	    {
235 		redraw_buf_later(buf, NOT_VALID);
236 		changed_cline_bef_curs();
237 	    }
238 
239 	    // first sign in signlist
240 	    buf->b_signlist = newsign;
241 #ifdef FEAT_NETBEANS_INTG
242 	    if (netbeans_active())
243 		buf->b_has_sign_column = TRUE;
244 #endif
245 	}
246 	else
247 	    prev->next = newsign;
248     }
249 }
250 
251 /*
252  * Insert a new sign sorted by line number and sign priority.
253  */
254     static void
255 insert_sign_by_lnum_prio(
256     buf_T	*buf,		// buffer to store sign in
257     signlist_T	*prev,		// previous sign entry
258     int		id,		// sign ID
259     char_u	*group,		// sign group; NULL for global group
260     int		prio,		// sign priority
261     linenr_T	lnum,		// line number which gets the mark
262     int		typenr)		// typenr of sign we are adding
263 {
264     signlist_T	*sign;
265 
266     // keep signs sorted by lnum and by priority: insert new sign at
267     // the proper position in the list for this lnum.
268     while (prev != NULL && prev->lnum == lnum && prev->priority <= prio)
269 	prev = prev->prev;
270     if (prev == NULL)
271 	sign = buf->b_signlist;
272     else
273 	sign = prev->next;
274 
275     insert_sign(buf, prev, sign, id, group, prio, lnum, typenr);
276 }
277 
278 /*
279  * Get the name of a sign by its typenr.
280  */
281     static char_u *
282 sign_typenr2name(int typenr)
283 {
284     sign_T	*sp;
285 
286     for (sp = first_sign; sp != NULL; sp = sp->sn_next)
287 	if (sp->sn_typenr == typenr)
288 	    return sp->sn_name;
289     return (char_u *)_("[Deleted]");
290 }
291 
292 /*
293  * Return information about a sign in a Dict
294  */
295     static dict_T *
296 sign_get_info(signlist_T *sign)
297 {
298     dict_T	*d;
299 
300     if ((d = dict_alloc_id(aid_sign_getinfo)) == NULL)
301 	return NULL;
302     dict_add_number(d, "id", sign->id);
303     dict_add_string(d, "group", (sign->group == NULL) ?
304 					(char_u *)"" : sign->group->sg_name);
305     dict_add_number(d, "lnum", sign->lnum);
306     dict_add_string(d, "name", sign_typenr2name(sign->typenr));
307     dict_add_number(d, "priority", sign->priority);
308 
309     return d;
310 }
311 
312 /*
313  * Add the sign into the signlist. Find the right spot to do it though.
314  */
315     static void
316 buf_addsign(
317     buf_T	*buf,		// buffer to store sign in
318     int		id,		// sign ID
319     char_u	*groupname,	// sign group
320     int		prio,		// sign priority
321     linenr_T	lnum,		// line number which gets the mark
322     int		typenr)		// typenr of sign we are adding
323 {
324     signlist_T	*sign;		// a sign in the signlist
325     signlist_T	*prev;		// the previous sign
326 
327     prev = NULL;
328     FOR_ALL_SIGNS_IN_BUF(buf, sign)
329     {
330 	if (lnum == sign->lnum && id == sign->id
331 		&& sign_in_group(sign, groupname))
332 	{
333 	    // Update an existing sign
334 	    sign->typenr = typenr;
335 	    return;
336 	}
337 	else if (lnum < sign->lnum)
338 	{
339 	    insert_sign_by_lnum_prio(buf, prev, id, groupname, prio,
340 								lnum, typenr);
341 	    return;
342 	}
343 	prev = sign;
344     }
345 
346     insert_sign_by_lnum_prio(buf, prev, id, groupname, prio, lnum, typenr);
347     return;
348 }
349 
350 /*
351  * For an existing, placed sign "markId" change the type to "typenr".
352  * Returns the line number of the sign, or zero if the sign is not found.
353  */
354     static linenr_T
355 buf_change_sign_type(
356     buf_T	*buf,		// buffer to store sign in
357     int		markId,		// sign ID
358     char_u	*group,		// sign group
359     int		typenr)		// typenr of sign we are adding
360 {
361     signlist_T	*sign;		// a sign in the signlist
362 
363     FOR_ALL_SIGNS_IN_BUF(buf, sign)
364     {
365 	if (sign->id == markId && sign_in_group(sign, group))
366 	{
367 	    sign->typenr = typenr;
368 	    return sign->lnum;
369 	}
370     }
371 
372     return (linenr_T)0;
373 }
374 
375 /*
376  * Return the type number of the sign at line number 'lnum' in buffer 'buf'
377  * which has the attribute specified by 'type'. Returns 0 if a sign is not
378  * found at the line number or it doesn't have the specified attribute.
379  */
380     int
381 buf_getsigntype(
382     buf_T	*buf,
383     linenr_T	lnum,
384     int		type)	// SIGN_ICON, SIGN_TEXT, SIGN_ANY, SIGN_LINEHL
385 {
386     signlist_T	*sign;		// a sign in a b_signlist
387 
388     FOR_ALL_SIGNS_IN_BUF(buf, sign)
389 	if (sign->lnum == lnum
390 		&& (type == SIGN_ANY
391 # ifdef FEAT_SIGN_ICONS
392 		    || (type == SIGN_ICON
393 			&& sign_get_image(sign->typenr) != NULL)
394 # endif
395 		    || (type == SIGN_TEXT
396 			&& sign_get_text(sign->typenr) != NULL)
397 		    || (type == SIGN_LINEHL
398 			&& sign_get_attr(sign->typenr, TRUE) != 0)))
399 	    return sign->typenr;
400     return 0;
401 }
402 
403 /*
404  * Delete sign 'id' in group 'group' from buffer 'buf'.
405  * If 'id' is zero, then delete all the signs in group 'group'. Otherwise
406  * delete only the specified sign.
407  * If 'group' is '*', then delete the sign in all the groups. If 'group' is
408  * NULL, then delete the sign in the global group. Otherwise delete the sign in
409  * the specified group.
410  * Returns the line number of the deleted sign. If multiple signs are deleted,
411  * then returns the line number of the last sign deleted.
412  */
413     linenr_T
414 buf_delsign(
415     buf_T	*buf,		// buffer sign is stored in
416     linenr_T	atlnum,		// sign at this line, 0 - at any line
417     int		id,		// sign id
418     char_u	*group)		// sign group
419 {
420     signlist_T	**lastp;	// pointer to pointer to current sign
421     signlist_T	*sign;		// a sign in a b_signlist
422     signlist_T	*next;		// the next sign in a b_signlist
423     linenr_T	lnum;		// line number whose sign was deleted
424 
425     lastp = &buf->b_signlist;
426     lnum = 0;
427     for (sign = buf->b_signlist; sign != NULL; sign = next)
428     {
429 	next = sign->next;
430 	if ((id == 0 || sign->id == id)
431 		&& (atlnum == 0 || sign->lnum == atlnum)
432 		&& sign_in_group(sign, group))
433 
434 	{
435 	    *lastp = next;
436 	    if (next != NULL)
437 		next->prev = sign->prev;
438 	    lnum = sign->lnum;
439 	    if (sign->group != NULL)
440 		sign_group_unref(sign->group->sg_name);
441 	    vim_free(sign);
442 	    redraw_buf_line_later(buf, lnum);
443 
444 	    // Check whether only one sign needs to be deleted
445 	    // If deleting a sign with a specific identifier in a particular
446 	    // group or deleting any sign at a particular line number, delete
447 	    // only one sign.
448 	    if (group == NULL
449 		    || (*group != '*' && id != 0)
450 		    || (*group == '*' && atlnum != 0))
451 		break;
452 	}
453 	else
454 	    lastp = &sign->next;
455     }
456 
457     // When deleting the last sign the cursor position may change, because the
458     // sign columns no longer shows.  And the 'signcolumn' may be hidden.
459     if (buf->b_signlist == NULL)
460     {
461 	redraw_buf_later(buf, NOT_VALID);
462 	changed_cline_bef_curs();
463     }
464 
465     return lnum;
466 }
467 
468 
469 /*
470  * Find the line number of the sign with the requested id in group 'group'. If
471  * the sign does not exist, return 0 as the line number. This will still let
472  * the correct file get loaded.
473  */
474     int
475 buf_findsign(
476     buf_T	*buf,		// buffer to store sign in
477     int		id,		// sign ID
478     char_u	*group)		// sign group
479 {
480     signlist_T	*sign;		// a sign in the signlist
481 
482     FOR_ALL_SIGNS_IN_BUF(buf, sign)
483 	if (sign->id == id && sign_in_group(sign, group))
484 	    return sign->lnum;
485 
486     return 0;
487 }
488 
489 /*
490  * Return the sign at line 'lnum' in buffer 'buf'. Returns NULL if a sign is
491  * not found at the line. If 'groupname' is NULL, searches in the global group.
492  */
493     static signlist_T *
494 buf_getsign_at_line(
495     buf_T	*buf,		// buffer whose sign we are searching for
496     linenr_T	lnum,		// line number of sign
497     char_u	*groupname)	// sign group name
498 {
499     signlist_T	*sign;		// a sign in the signlist
500 
501     FOR_ALL_SIGNS_IN_BUF(buf, sign)
502 	if (sign->lnum == lnum && sign_in_group(sign, groupname))
503 	    return sign;
504 
505     return NULL;
506 }
507 
508 /*
509  * Return the identifier of the sign at line number 'lnum' in buffer 'buf'.
510  */
511     int
512 buf_findsign_id(
513     buf_T	*buf,		// buffer whose sign we are searching for
514     linenr_T	lnum,		// line number of sign
515     char_u	*groupname)	// sign group name
516 {
517     signlist_T	*sign;		// a sign in the signlist
518 
519     sign = buf_getsign_at_line(buf, lnum, groupname);
520     if (sign != NULL)
521 	return sign->id;
522 
523     return 0;
524 }
525 
526 # if defined(FEAT_NETBEANS_INTG) || defined(PROTO)
527 /*
528  * See if a given type of sign exists on a specific line.
529  */
530     int
531 buf_findsigntype_id(
532     buf_T	*buf,		// buffer whose sign we are searching for
533     linenr_T	lnum,		// line number of sign
534     int		typenr)		// sign type number
535 {
536     signlist_T	*sign;		// a sign in the signlist
537 
538     FOR_ALL_SIGNS_IN_BUF(buf, sign)
539 	if (sign->lnum == lnum && sign->typenr == typenr)
540 	    return sign->id;
541 
542     return 0;
543 }
544 
545 
546 #  if defined(FEAT_SIGN_ICONS) || defined(PROTO)
547 /*
548  * Return the number of icons on the given line.
549  */
550     int
551 buf_signcount(buf_T *buf, linenr_T lnum)
552 {
553     signlist_T	*sign;		// a sign in the signlist
554     int		count = 0;
555 
556     FOR_ALL_SIGNS_IN_BUF(buf, sign)
557 	if (sign->lnum == lnum)
558 	    if (sign_get_image(sign->typenr) != NULL)
559 		count++;
560 
561     return count;
562 }
563 #  endif /* FEAT_SIGN_ICONS */
564 # endif /* FEAT_NETBEANS_INTG */
565 
566 /*
567  * Delete signs in group 'group' in buffer "buf". If 'group' is '*', then
568  * delete all the signs.
569  */
570     void
571 buf_delete_signs(buf_T *buf, char_u *group)
572 {
573     signlist_T	*sign;
574     signlist_T	**lastp;	// pointer to pointer to current sign
575     signlist_T	*next;
576 
577     // When deleting the last sign need to redraw the windows to remove the
578     // sign column. Not when curwin is NULL (this means we're exiting).
579     if (buf->b_signlist != NULL && curwin != NULL)
580     {
581 	redraw_buf_later(buf, NOT_VALID);
582 	changed_cline_bef_curs();
583     }
584 
585     lastp = &buf->b_signlist;
586     for (sign = buf->b_signlist; sign != NULL; sign = next)
587     {
588 	next = sign->next;
589 	if (sign_in_group(sign, group))
590 	{
591 	    *lastp = next;
592 	    if (next != NULL)
593 		next->prev = sign->prev;
594 	    if (sign->group != NULL)
595 		sign_group_unref(sign->group->sg_name);
596 	    vim_free(sign);
597 	}
598 	else
599 	    lastp = &sign->next;
600     }
601 }
602 
603 /*
604  * List placed signs for "rbuf".  If "rbuf" is NULL do it for all buffers.
605  */
606     static void
607 sign_list_placed(buf_T *rbuf, char_u *sign_group)
608 {
609     buf_T	*buf;
610     signlist_T	*sign;
611     char	lbuf[MSG_BUF_LEN];
612     char	group[MSG_BUF_LEN];
613 
614     msg_puts_title(_("\n--- Signs ---"));
615     msg_putchar('\n');
616     if (rbuf == NULL)
617 	buf = firstbuf;
618     else
619 	buf = rbuf;
620     while (buf != NULL && !got_int)
621     {
622 	if (buf->b_signlist != NULL)
623 	{
624 	    vim_snprintf(lbuf, MSG_BUF_LEN, _("Signs for %s:"), buf->b_fname);
625 	    msg_puts_attr(lbuf, HL_ATTR(HLF_D));
626 	    msg_putchar('\n');
627 	}
628 	FOR_ALL_SIGNS_IN_BUF(buf, sign)
629 	{
630 	    if (got_int)
631 		break;
632 	    if (!sign_in_group(sign, sign_group))
633 		continue;
634 	    if (sign->group != NULL)
635 		vim_snprintf(group, MSG_BUF_LEN, _("  group=%s"),
636 							sign->group->sg_name);
637 	    else
638 		group[0] = '\0';
639 	    vim_snprintf(lbuf, MSG_BUF_LEN,
640 			   _("    line=%ld  id=%d%s  name=%s  priority=%d"),
641 			   (long)sign->lnum, sign->id, group,
642 			   sign_typenr2name(sign->typenr), sign->priority);
643 	    msg_puts(lbuf);
644 	    msg_putchar('\n');
645 	}
646 	if (rbuf != NULL)
647 	    break;
648 	buf = buf->b_next;
649     }
650 }
651 
652 /*
653  * Adjust a placed sign for inserted/deleted lines.
654  */
655     void
656 sign_mark_adjust(
657     linenr_T	line1,
658     linenr_T	line2,
659     long	amount,
660     long	amount_after)
661 {
662     signlist_T	*sign;		// a sign in a b_signlist
663     linenr_T	new_lnum;
664 
665     FOR_ALL_SIGNS_IN_BUF(curbuf, sign)
666     {
667 	// Ignore changes to lines after the sign
668 	if (sign->lnum < line1)
669 	    continue;
670 	new_lnum = sign->lnum;
671 	if (sign->lnum >= line1 && sign->lnum <= line2)
672 	{
673 	    if (amount != MAXLNUM)
674 		new_lnum += amount;
675 	}
676 	else if (sign->lnum > line2)
677 	    // Lines inserted or deleted before the sign
678 	    new_lnum += amount_after;
679 
680 	// If the new sign line number is past the last line in the buffer,
681 	// then don't adjust the line number. Otherwise, it will always be past
682 	// the last line and will not be visible.
683 	if (new_lnum <= curbuf->b_ml.ml_line_count)
684 	    sign->lnum = new_lnum;
685     }
686 }
687 
688 /*
689  * Find index of a ":sign" subcmd from its name.
690  * "*end_cmd" must be writable.
691  */
692     static int
693 sign_cmd_idx(
694     char_u	*begin_cmd,	// begin of sign subcmd
695     char_u	*end_cmd)	// just after sign subcmd
696 {
697     int		idx;
698     char	save = *end_cmd;
699 
700     *end_cmd = NUL;
701     for (idx = 0; ; ++idx)
702 	if (cmds[idx] == NULL || STRCMP(begin_cmd, cmds[idx]) == 0)
703 	    break;
704     *end_cmd = save;
705     return idx;
706 }
707 
708 /*
709  * Find a sign by name. Also returns pointer to the previous sign.
710  */
711     static sign_T *
712 sign_find(char_u *name, sign_T **sp_prev)
713 {
714     sign_T *sp;
715 
716     if (sp_prev != NULL)
717 	*sp_prev = NULL;
718     for (sp = first_sign; sp != NULL; sp = sp->sn_next)
719     {
720 	if (STRCMP(sp->sn_name, name) == 0)
721 	    break;
722 	if (sp_prev != NULL)
723 	    *sp_prev = sp;
724     }
725 
726     return sp;
727 }
728 
729 /*
730  * Allocate a new sign
731  */
732     static sign_T *
733 alloc_new_sign(char_u *name)
734 {
735     sign_T	*sp;
736     sign_T	*lp;
737     int	start = next_sign_typenr;
738 
739     // Allocate a new sign.
740     sp = (sign_T *)alloc_clear_id((unsigned)sizeof(sign_T),
741 	    aid_sign_define_by_name);
742     if (sp == NULL)
743 	return NULL;
744 
745     // Check that next_sign_typenr is not already being used.
746     // This only happens after wrapping around.  Hopefully
747     // another one got deleted and we can use its number.
748     for (lp = first_sign; lp != NULL; )
749     {
750 	if (lp->sn_typenr == next_sign_typenr)
751 	{
752 	    ++next_sign_typenr;
753 	    if (next_sign_typenr == MAX_TYPENR)
754 		next_sign_typenr = 1;
755 	    if (next_sign_typenr == start)
756 	    {
757 		vim_free(sp);
758 		emsg(_("E612: Too many signs defined"));
759 		return NULL;
760 	    }
761 	    lp = first_sign;  // start all over
762 	    continue;
763 	}
764 	lp = lp->sn_next;
765     }
766 
767     sp->sn_typenr = next_sign_typenr;
768     if (++next_sign_typenr == MAX_TYPENR)
769 	next_sign_typenr = 1; // wrap around
770 
771     sp->sn_name = vim_strsave(name);
772     if (sp->sn_name == NULL)  // out of memory
773     {
774 	vim_free(sp);
775 	return NULL;
776     }
777 
778     return sp;
779 }
780 
781 /*
782  * Initialize the icon information for a new sign
783  */
784     static void
785 sign_define_init_icon(sign_T *sp, char_u *icon)
786 {
787     vim_free(sp->sn_icon);
788     sp->sn_icon = vim_strsave(icon);
789     backslash_halve(sp->sn_icon);
790 # ifdef FEAT_SIGN_ICONS
791     if (gui.in_use)
792     {
793 	out_flush();
794 	if (sp->sn_image != NULL)
795 	    gui_mch_destroy_sign(sp->sn_image);
796 	sp->sn_image = gui_mch_register_sign(sp->sn_icon);
797     }
798 # endif
799 }
800 
801 /*
802  * Initialize the text for a new sign
803  */
804     static int
805 sign_define_init_text(sign_T *sp, char_u *text)
806 {
807     char_u	*s;
808     char_u	*endp;
809     int		cells;
810     int		len;
811 
812     endp = text + (int)STRLEN(text);
813 
814     // Remove backslashes so that it is possible to use a space.
815     for (s = text; s + 1 < endp; ++s)
816 	if (*s == '\\')
817 	{
818 	    STRMOVE(s, s + 1);
819 	    --endp;
820 	}
821 
822     // Count cells and check for non-printable chars
823     if (has_mbyte)
824     {
825 	cells = 0;
826 	for (s = text; s < endp; s += (*mb_ptr2len)(s))
827 	{
828 	    if (!vim_isprintc((*mb_ptr2char)(s)))
829 		break;
830 	    cells += (*mb_ptr2cells)(s);
831 	}
832     }
833     else
834     {
835 	for (s = text; s < endp; ++s)
836 	    if (!vim_isprintc(*s))
837 		break;
838 	cells = (int)(s - text);
839     }
840 
841     // Currently sign text must be one or two display cells
842     if (s != endp || cells < 1 || cells > 2)
843     {
844 	semsg(_("E239: Invalid sign text: %s"), text);
845 	return FAIL;
846     }
847 
848     vim_free(sp->sn_text);
849     // Allocate one byte more if we need to pad up
850     // with a space.
851     len = (int)(endp - text + ((cells == 1) ? 1 : 0));
852     sp->sn_text = vim_strnsave(text, len);
853 
854     // For single character sign text, pad with a space.
855     if (sp->sn_text != NULL && cells == 1)
856 	STRCPY(sp->sn_text + len - 1, " ");
857 
858     return OK;
859 }
860 
861 /*
862  * Define a new sign or update an existing sign
863  */
864     int
865 sign_define_by_name(
866 	char_u	*name,
867 	char_u	*icon,
868 	char_u	*linehl,
869 	char_u	*text,
870 	char_u	*texthl)
871 {
872     sign_T	*sp_prev;
873     sign_T	*sp;
874 
875     sp = sign_find(name, &sp_prev);
876     if (sp == NULL)
877     {
878 	sp = alloc_new_sign(name);
879 	if (sp == NULL)
880 	    return FAIL;
881 
882 	// add the new sign to the list of signs
883 	if (sp_prev == NULL)
884 	    first_sign = sp;
885 	else
886 	    sp_prev->sn_next = sp;
887     }
888 
889     // set values for a defined sign.
890     if (icon != NULL)
891 	sign_define_init_icon(sp, icon);
892 
893     if (text != NULL && (sign_define_init_text(sp, text) == FAIL))
894 	return FAIL;
895 
896     if (linehl != NULL)
897 	sp->sn_line_hl = syn_check_group(linehl, (int)STRLEN(linehl));
898 
899     if (texthl != NULL)
900 	sp->sn_text_hl = syn_check_group(texthl, (int)STRLEN(texthl));
901 
902     return OK;
903 }
904 
905 /*
906  * Free the sign specified by 'name'.
907  */
908     int
909 sign_undefine_by_name(char_u *name)
910 {
911     sign_T	*sp_prev;
912     sign_T	*sp;
913 
914     sp = sign_find(name, &sp_prev);
915     if (sp == NULL)
916     {
917 	semsg(_("E155: Unknown sign: %s"), name);
918 	return FAIL;
919     }
920     sign_undefine(sp, sp_prev);
921 
922     return OK;
923 }
924 
925 /*
926  * List the signs matching 'name'
927  */
928     static void
929 sign_list_by_name(char_u *name)
930 {
931     sign_T	*sp;
932 
933     sp = sign_find(name, NULL);
934     if (sp != NULL)
935 	sign_list_defined(sp);
936     else
937 	semsg(_("E155: Unknown sign: %s"), name);
938 }
939 
940 /*
941  * Place a sign at the specified file location or update a sign.
942  */
943     int
944 sign_place(
945 	int		*sign_id,
946 	char_u		*sign_group,
947 	char_u		*sign_name,
948 	buf_T		*buf,
949 	linenr_T	lnum,
950 	int		prio)
951 {
952     sign_T	*sp;
953 
954     // Check for reserved character '*' in group name
955     if (sign_group != NULL && (*sign_group == '*' || *sign_group == '\0'))
956 	return FAIL;
957 
958     for (sp = first_sign; sp != NULL; sp = sp->sn_next)
959 	if (STRCMP(sp->sn_name, sign_name) == 0)
960 	    break;
961     if (sp == NULL)
962     {
963 	semsg(_("E155: Unknown sign: %s"), sign_name);
964 	return FAIL;
965     }
966     if (*sign_id == 0)
967 	*sign_id = sign_group_get_next_signid(buf, sign_group);
968 
969     if (lnum > 0)
970 	// ":sign place {id} line={lnum} name={name} file={fname}":
971 	// place a sign
972 	buf_addsign(buf, *sign_id, sign_group, prio, lnum, sp->sn_typenr);
973     else
974 	// ":sign place {id} file={fname}": change sign type
975 	lnum = buf_change_sign_type(buf, *sign_id, sign_group, sp->sn_typenr);
976     if (lnum > 0)
977 	redraw_buf_line_later(buf, lnum);
978     else
979     {
980 	semsg(_("E885: Not possible to change sign %s"), sign_name);
981 	return FAIL;
982     }
983 
984     return OK;
985 }
986 
987 /*
988  * Unplace the specified sign
989  */
990     int
991 sign_unplace(int sign_id, char_u *sign_group, buf_T *buf, linenr_T atlnum)
992 {
993     if (buf->b_signlist == NULL)	// No signs in the buffer
994 	return OK;
995 
996     if (sign_id == 0)
997     {
998 	// Delete all the signs in the specified buffer
999 	redraw_buf_later(buf, NOT_VALID);
1000 	buf_delete_signs(buf, sign_group);
1001     }
1002     else
1003     {
1004 	linenr_T	lnum;
1005 
1006 	// Delete only the specified signs
1007 	lnum = buf_delsign(buf, atlnum, sign_id, sign_group);
1008 	if (lnum == 0)
1009 	    return FAIL;
1010     }
1011 
1012     return OK;
1013 }
1014 
1015 /*
1016  * Unplace the sign at the current cursor line.
1017  */
1018     static void
1019 sign_unplace_at_cursor(char_u *groupname)
1020 {
1021     int		id = -1;
1022 
1023     id = buf_findsign_id(curwin->w_buffer, curwin->w_cursor.lnum, groupname);
1024     if (id > 0)
1025 	sign_unplace(id, groupname, curwin->w_buffer, curwin->w_cursor.lnum);
1026     else
1027 	emsg(_("E159: Missing sign number"));
1028 }
1029 
1030 /*
1031  * Jump to a sign.
1032  */
1033     linenr_T
1034 sign_jump(int sign_id, char_u *sign_group, buf_T *buf)
1035 {
1036     linenr_T	lnum;
1037 
1038     if ((lnum = buf_findsign(buf, sign_id, sign_group)) <= 0)
1039     {
1040 	semsg(_("E157: Invalid sign ID: %d"), sign_id);
1041 	return -1;
1042     }
1043 
1044     // goto a sign ...
1045     if (buf_jump_open_win(buf) != NULL)
1046     {			// ... in a current window
1047 	curwin->w_cursor.lnum = lnum;
1048 	check_cursor_lnum();
1049 	beginline(BL_WHITE);
1050     }
1051     else
1052     {			// ... not currently in a window
1053 	char_u	*cmd;
1054 
1055 	if (buf->b_fname == NULL)
1056 	{
1057 	    emsg(_("E934: Cannot jump to a buffer that does not have a name"));
1058 	    return -1;
1059 	}
1060 	cmd = alloc((unsigned)STRLEN(buf->b_fname) + 25);
1061 	if (cmd == NULL)
1062 	    return -1;
1063 	sprintf((char *)cmd, "e +%ld %s", (long)lnum, buf->b_fname);
1064 	do_cmdline_cmd(cmd);
1065 	vim_free(cmd);
1066     }
1067 # ifdef FEAT_FOLDING
1068     foldOpenCursor();
1069 # endif
1070 
1071     return lnum;
1072 }
1073 
1074 /*
1075  * ":sign define {name} ..." command
1076  */
1077     static void
1078 sign_define_cmd(char_u *sign_name, char_u *cmdline)
1079 {
1080     char_u	*arg;
1081     char_u	*p = cmdline;
1082     char_u	*icon = NULL;
1083     char_u	*text = NULL;
1084     char_u	*linehl = NULL;
1085     char_u	*texthl = NULL;
1086     int failed = FALSE;
1087 
1088     // set values for a defined sign.
1089     for (;;)
1090     {
1091 	arg = skipwhite(p);
1092 	if (*arg == NUL)
1093 	    break;
1094 	p = skiptowhite_esc(arg);
1095 	if (STRNCMP(arg, "icon=", 5) == 0)
1096 	{
1097 	    arg += 5;
1098 	    icon = vim_strnsave(arg, (int)(p - arg));
1099 	}
1100 	else if (STRNCMP(arg, "text=", 5) == 0)
1101 	{
1102 	    arg += 5;
1103 	    text = vim_strnsave(arg, (int)(p - arg));
1104 	}
1105 	else if (STRNCMP(arg, "linehl=", 7) == 0)
1106 	{
1107 	    arg += 7;
1108 	    linehl = vim_strnsave(arg, (int)(p - arg));
1109 	}
1110 	else if (STRNCMP(arg, "texthl=", 7) == 0)
1111 	{
1112 	    arg += 7;
1113 	    texthl = vim_strnsave(arg, (int)(p - arg));
1114 	}
1115 	else
1116 	{
1117 	    semsg(_(e_invarg2), arg);
1118 	    failed = TRUE;
1119 	    break;
1120 	}
1121     }
1122 
1123     if (!failed)
1124 	sign_define_by_name(sign_name, icon, linehl, text, texthl);
1125 
1126     vim_free(icon);
1127     vim_free(text);
1128     vim_free(linehl);
1129     vim_free(texthl);
1130 }
1131 
1132 /*
1133  * ":sign place" command
1134  */
1135     static void
1136 sign_place_cmd(
1137 	buf_T		*buf,
1138 	linenr_T	lnum,
1139 	char_u		*sign_name,
1140 	int		id,
1141 	char_u		*group,
1142 	int		prio)
1143 {
1144     if (id <= 0)
1145     {
1146 	// List signs placed in a file/buffer
1147 	//   :sign place file={fname}
1148 	//   :sign place group={group} file={fname}
1149 	//   :sign place group=* file={fname}
1150 	//   :sign place buffer={nr}
1151 	//   :sign place group={group} buffer={nr}
1152 	//   :sign place group=* buffer={nr}
1153 	//   :sign place
1154 	//   :sign place group={group}
1155 	//   :sign place group=*
1156 	if (lnum >= 0 || sign_name != NULL
1157 		|| (group != NULL && *group == '\0'))
1158 	    emsg(_(e_invarg));
1159 	else
1160 	    sign_list_placed(buf, group);
1161     }
1162     else
1163     {
1164 	// Place a new sign
1165 	if (sign_name == NULL || buf == NULL
1166 		|| (group != NULL && *group == '\0'))
1167 	{
1168 	    emsg(_(e_invarg));
1169 	    return;
1170 	}
1171 
1172 	sign_place(&id, group, sign_name, buf, lnum, prio);
1173     }
1174 }
1175 
1176 /*
1177  * ":sign unplace" command
1178  */
1179     static void
1180 sign_unplace_cmd(
1181 	buf_T		*buf,
1182 	linenr_T	lnum,
1183 	char_u		*sign_name,
1184 	int		id,
1185 	char_u		*group)
1186 {
1187     if (lnum >= 0 || sign_name != NULL || (group != NULL && *group == '\0'))
1188     {
1189 	emsg(_(e_invarg));
1190 	return;
1191     }
1192 
1193     if (id == -2)
1194     {
1195 	if (buf != NULL)
1196 	    // :sign unplace * file={fname}
1197 	    // :sign unplace * group={group} file={fname}
1198 	    // :sign unplace * group=* file={fname}
1199 	    // :sign unplace * buffer={nr}
1200 	    // :sign unplace * group={group} buffer={nr}
1201 	    // :sign unplace * group=* buffer={nr}
1202 	    sign_unplace(0, group, buf, 0);
1203 	else
1204 	    // :sign unplace *
1205 	    // :sign unplace * group={group}
1206 	    // :sign unplace * group=*
1207 	    FOR_ALL_BUFFERS(buf)
1208 		if (buf->b_signlist != NULL)
1209 		    buf_delete_signs(buf, group);
1210     }
1211     else
1212     {
1213 	if (buf != NULL)
1214 	    // :sign unplace {id} file={fname}
1215 	    // :sign unplace {id} group={group} file={fname}
1216 	    // :sign unplace {id} group=* file={fname}
1217 	    // :sign unplace {id} buffer={nr}
1218 	    // :sign unplace {id} group={group} buffer={nr}
1219 	    // :sign unplace {id} group=* buffer={nr}
1220 	    sign_unplace(id, group, buf, 0);
1221 	else
1222 	{
1223 	    if (id == -1)
1224 	    {
1225 		// :sign unplace group={group}
1226 		// :sign unplace group=*
1227 		sign_unplace_at_cursor(group);
1228 	    }
1229 	    else
1230 	    {
1231 		// :sign unplace {id}
1232 		// :sign unplace {id} group={group}
1233 		// :sign unplace {id} group=*
1234 		FOR_ALL_BUFFERS(buf)
1235 		    sign_unplace(id, group, buf, 0);
1236 	    }
1237 	}
1238     }
1239 }
1240 
1241 /*
1242  * Jump to a placed sign commands:
1243  *   :sign jump {id} file={fname}
1244  *   :sign jump {id} buffer={nr}
1245  *   :sign jump {id} group={group} file={fname}
1246  *   :sign jump {id} group={group} buffer={nr}
1247  */
1248     static void
1249 sign_jump_cmd(
1250 	buf_T		*buf,
1251 	linenr_T	lnum,
1252 	char_u		*sign_name,
1253 	int		id,
1254 	char_u		*group)
1255 {
1256     if (sign_name == NULL && group == NULL && id == -1)
1257     {
1258 	emsg(_(e_argreq));
1259 	return;
1260     }
1261 
1262     if (buf == NULL || (group != NULL && *group == '\0')
1263 					     || lnum >= 0 || sign_name != NULL)
1264     {
1265 	// File or buffer is not specified or an empty group is used
1266 	// or a line number or a sign name is specified.
1267 	emsg(_(e_invarg));
1268 	return;
1269     }
1270     (void)sign_jump(id, group, buf);
1271 }
1272 
1273 /*
1274  * Parse the command line arguments for the ":sign place", ":sign unplace" and
1275  * ":sign jump" commands.
1276  * The supported arguments are: line={lnum} name={name} group={group}
1277  * priority={prio} and file={fname} or buffer={nr}.
1278  */
1279     static int
1280 parse_sign_cmd_args(
1281 	int	    cmd,
1282 	char_u	    *arg,
1283 	char_u	    **sign_name,
1284 	int	    *signid,
1285 	char_u	    **group,
1286 	int	    *prio,
1287 	buf_T	    **buf,
1288 	linenr_T    *lnum)
1289 {
1290     char_u	*arg1;
1291     char_u	*name;
1292     char_u	*filename = NULL;
1293     int		lnum_arg = FALSE;
1294 
1295     // first arg could be placed sign id
1296     arg1 = arg;
1297     if (VIM_ISDIGIT(*arg))
1298     {
1299 	*signid = getdigits(&arg);
1300 	if (!VIM_ISWHITE(*arg) && *arg != NUL)
1301 	{
1302 	    *signid = -1;
1303 	    arg = arg1;
1304 	}
1305 	else
1306 	    arg = skipwhite(arg);
1307     }
1308 
1309     while (*arg != NUL)
1310     {
1311 	if (STRNCMP(arg, "line=", 5) == 0)
1312 	{
1313 	    arg += 5;
1314 	    *lnum = atoi((char *)arg);
1315 	    arg = skiptowhite(arg);
1316 	    lnum_arg = TRUE;
1317 	}
1318 	else if (STRNCMP(arg, "*", 1) == 0 && cmd == SIGNCMD_UNPLACE)
1319 	{
1320 	    if (*signid != -1)
1321 	    {
1322 		emsg(_(e_invarg));
1323 		return FAIL;
1324 	    }
1325 	    *signid = -2;
1326 	    arg = skiptowhite(arg + 1);
1327 	}
1328 	else if (STRNCMP(arg, "name=", 5) == 0)
1329 	{
1330 	    arg += 5;
1331 	    name = arg;
1332 	    arg = skiptowhite(arg);
1333 	    if (*arg != NUL)
1334 		*arg++ = NUL;
1335 	    while (name[0] == '0' && name[1] != NUL)
1336 		++name;
1337 	    *sign_name = name;
1338 	}
1339 	else if (STRNCMP(arg, "group=", 6) == 0)
1340 	{
1341 	    arg += 6;
1342 	    *group = arg;
1343 	    arg = skiptowhite(arg);
1344 	    if (*arg != NUL)
1345 		*arg++ = NUL;
1346 	}
1347 	else if (STRNCMP(arg, "priority=", 9) == 0)
1348 	{
1349 	    arg += 9;
1350 	    *prio = atoi((char *)arg);
1351 	    arg = skiptowhite(arg);
1352 	}
1353 	else if (STRNCMP(arg, "file=", 5) == 0)
1354 	{
1355 	    arg += 5;
1356 	    filename = arg;
1357 	    *buf = buflist_findname_exp(arg);
1358 	    break;
1359 	}
1360 	else if (STRNCMP(arg, "buffer=", 7) == 0)
1361 	{
1362 	    arg += 7;
1363 	    filename = arg;
1364 	    *buf = buflist_findnr((int)getdigits(&arg));
1365 	    if (*skipwhite(arg) != NUL)
1366 		emsg(_(e_trailing));
1367 	    break;
1368 	}
1369 	else
1370 	{
1371 	    emsg(_(e_invarg));
1372 	    return FAIL;
1373 	}
1374 	arg = skipwhite(arg);
1375     }
1376 
1377     if (filename != NULL && *buf == NULL)
1378     {
1379 	semsg(_("E158: Invalid buffer name: %s"), filename);
1380 	return FAIL;
1381     }
1382 
1383     // If the filename is not supplied for the sign place or the sign jump
1384     // command, then use the current buffer.
1385     if (filename == NULL && ((cmd == SIGNCMD_PLACE && lnum_arg)
1386 						       || cmd == SIGNCMD_JUMP))
1387 	*buf = curwin->w_buffer;
1388 
1389     return OK;
1390 }
1391 
1392 /*
1393  * ":sign" command
1394  */
1395     void
1396 ex_sign(exarg_T *eap)
1397 {
1398     char_u	*arg = eap->arg;
1399     char_u	*p;
1400     int		idx;
1401     sign_T	*sp;
1402     buf_T	*buf = NULL;
1403 
1404     // Parse the subcommand.
1405     p = skiptowhite(arg);
1406     idx = sign_cmd_idx(arg, p);
1407     if (idx == SIGNCMD_LAST)
1408     {
1409 	semsg(_("E160: Unknown sign command: %s"), arg);
1410 	return;
1411     }
1412     arg = skipwhite(p);
1413 
1414     if (idx <= SIGNCMD_LIST)
1415     {
1416 	// Define, undefine or list signs.
1417 	if (idx == SIGNCMD_LIST && *arg == NUL)
1418 	{
1419 	    // ":sign list": list all defined signs
1420 	    for (sp = first_sign; sp != NULL && !got_int; sp = sp->sn_next)
1421 		sign_list_defined(sp);
1422 	}
1423 	else if (*arg == NUL)
1424 	    emsg(_("E156: Missing sign name"));
1425 	else
1426 	{
1427 	    char_u	*name;
1428 
1429 	    // Isolate the sign name.  If it's a number skip leading zeroes,
1430 	    // so that "099" and "99" are the same sign.  But keep "0".
1431 	    p = skiptowhite(arg);
1432 	    if (*p != NUL)
1433 		*p++ = NUL;
1434 	    while (arg[0] == '0' && arg[1] != NUL)
1435 		++arg;
1436 	    name = vim_strsave(arg);
1437 
1438 	    if (idx == SIGNCMD_DEFINE)
1439 		sign_define_cmd(name, p);
1440 	    else if (idx == SIGNCMD_LIST)
1441 		// ":sign list {name}"
1442 		sign_list_by_name(name);
1443 	    else
1444 		// ":sign undefine {name}"
1445 		sign_undefine_by_name(name);
1446 
1447 	    vim_free(name);
1448 	    return;
1449 	}
1450     }
1451     else
1452     {
1453 	int		id = -1;
1454 	linenr_T	lnum = -1;
1455 	char_u		*sign_name = NULL;
1456 	char_u		*group = NULL;
1457 	int		prio = SIGN_DEF_PRIO;
1458 
1459 	// Parse command line arguments
1460 	if (parse_sign_cmd_args(idx, arg, &sign_name, &id, &group, &prio,
1461 							  &buf, &lnum) == FAIL)
1462 	    return;
1463 
1464 	if (idx == SIGNCMD_PLACE)
1465 	    sign_place_cmd(buf, lnum, sign_name, id, group, prio);
1466 	else if (idx == SIGNCMD_UNPLACE)
1467 	    sign_unplace_cmd(buf, lnum, sign_name, id, group);
1468 	else if (idx == SIGNCMD_JUMP)
1469 	    sign_jump_cmd(buf, lnum, sign_name, id, group);
1470     }
1471 }
1472 
1473 /*
1474  * Return information about a specified sign
1475  */
1476     static void
1477 sign_getinfo(sign_T *sp, dict_T *retdict)
1478 {
1479     char_u	*p;
1480 
1481     dict_add_string(retdict, "name", (char_u *)sp->sn_name);
1482     if (sp->sn_icon != NULL)
1483 	dict_add_string(retdict, "icon", (char_u *)sp->sn_icon);
1484     if (sp->sn_text != NULL)
1485 	dict_add_string(retdict, "text", (char_u *)sp->sn_text);
1486     if (sp->sn_line_hl > 0)
1487     {
1488 	p = get_highlight_name_ext(NULL, sp->sn_line_hl - 1, FALSE);
1489 	if (p == NULL)
1490 	    p = (char_u *)"NONE";
1491 	dict_add_string(retdict, "linehl", (char_u *)p);
1492     }
1493     if (sp->sn_text_hl > 0)
1494     {
1495 	p = get_highlight_name_ext(NULL, sp->sn_text_hl - 1, FALSE);
1496 	if (p == NULL)
1497 	    p = (char_u *)"NONE";
1498 	dict_add_string(retdict, "texthl", (char_u *)p);
1499     }
1500 }
1501 
1502 /*
1503  * If 'name' is NULL, return a list of all the defined signs.
1504  * Otherwise, return information about the specified sign.
1505  */
1506     void
1507 sign_getlist(char_u *name, list_T *retlist)
1508 {
1509     sign_T	*sp = first_sign;
1510     dict_T	*dict;
1511 
1512     if (name != NULL)
1513     {
1514 	sp = sign_find(name, NULL);
1515 	if (sp == NULL)
1516 	    return;
1517     }
1518 
1519     for (; sp != NULL && !got_int; sp = sp->sn_next)
1520     {
1521 	if ((dict = dict_alloc_id(aid_sign_getlist)) == NULL)
1522 	    return;
1523 	if (list_append_dict(retlist, dict) == FAIL)
1524 	    return;
1525 	sign_getinfo(sp, dict);
1526 
1527 	if (name != NULL)	    // handle only the specified sign
1528 	    break;
1529     }
1530 }
1531 
1532 /*
1533  * Returns information about signs placed in a buffer as list of dicts.
1534  */
1535     void
1536 get_buffer_signs(buf_T *buf, list_T *l)
1537 {
1538     signlist_T	*sign;
1539     dict_T	*d;
1540 
1541     FOR_ALL_SIGNS_IN_BUF(buf, sign)
1542     {
1543 	if ((d = sign_get_info(sign)) != NULL)
1544 	    list_append_dict(l, d);
1545     }
1546 }
1547 
1548 /*
1549  * Return information about all the signs placed in a buffer
1550  */
1551     static void
1552 sign_get_placed_in_buf(
1553 	buf_T		*buf,
1554 	linenr_T	lnum,
1555 	int		sign_id,
1556 	char_u		*sign_group,
1557 	list_T		*retlist)
1558 {
1559     dict_T	*d;
1560     list_T	*l;
1561     signlist_T	*sign;
1562     dict_T	*sdict;
1563 
1564     if ((d = dict_alloc_id(aid_sign_getplaced_dict)) == NULL)
1565 	return;
1566     list_append_dict(retlist, d);
1567 
1568     dict_add_number(d, "bufnr", (long)buf->b_fnum);
1569 
1570     if ((l = list_alloc_id(aid_sign_getplaced_list)) == NULL)
1571 	return;
1572     dict_add_list(d, "signs", l);
1573 
1574     FOR_ALL_SIGNS_IN_BUF(buf, sign)
1575     {
1576 	if (!sign_in_group(sign, sign_group))
1577 	    continue;
1578 	if ((lnum == 0 && sign_id == 0)
1579 		|| (sign_id == 0 && lnum == sign->lnum)
1580 		|| (lnum == 0 && sign_id == sign->id)
1581 		|| (lnum == sign->lnum && sign_id == sign->id))
1582 	{
1583 	    if ((sdict = sign_get_info(sign)) != NULL)
1584 		list_append_dict(l, sdict);
1585 	}
1586     }
1587 }
1588 
1589 /*
1590  * Get a list of signs placed in buffer 'buf'. If 'num' is non-zero, return the
1591  * sign placed at the line number. If 'lnum' is zero, return all the signs
1592  * placed in 'buf'. If 'buf' is NULL, return signs placed in all the buffers.
1593  */
1594     void
1595 sign_get_placed(
1596 	buf_T		*buf,
1597 	linenr_T	lnum,
1598 	int		sign_id,
1599 	char_u		*sign_group,
1600 	list_T		*retlist)
1601 {
1602     if (buf != NULL)
1603 	sign_get_placed_in_buf(buf, lnum, sign_id, sign_group, retlist);
1604     else
1605     {
1606 	FOR_ALL_BUFFERS(buf)
1607 	{
1608 	    if (buf->b_signlist != NULL)
1609 		sign_get_placed_in_buf(buf, 0, sign_id, sign_group, retlist);
1610 	}
1611     }
1612 }
1613 
1614 # if defined(FEAT_SIGN_ICONS) || defined(PROTO)
1615 /*
1616  * Allocate the icons.  Called when the GUI has started.  Allows defining
1617  * signs before it starts.
1618  */
1619     void
1620 sign_gui_started(void)
1621 {
1622     sign_T	*sp;
1623 
1624     for (sp = first_sign; sp != NULL; sp = sp->sn_next)
1625 	if (sp->sn_icon != NULL)
1626 	    sp->sn_image = gui_mch_register_sign(sp->sn_icon);
1627 }
1628 # endif
1629 
1630 /*
1631  * List one sign.
1632  */
1633     static void
1634 sign_list_defined(sign_T *sp)
1635 {
1636     char_u	*p;
1637 
1638     smsg("sign %s", sp->sn_name);
1639     if (sp->sn_icon != NULL)
1640     {
1641 	msg_puts(" icon=");
1642 	msg_outtrans(sp->sn_icon);
1643 # ifdef FEAT_SIGN_ICONS
1644 	if (sp->sn_image == NULL)
1645 	    msg_puts(_(" (NOT FOUND)"));
1646 # else
1647 	msg_puts(_(" (not supported)"));
1648 # endif
1649     }
1650     if (sp->sn_text != NULL)
1651     {
1652 	msg_puts(" text=");
1653 	msg_outtrans(sp->sn_text);
1654     }
1655     if (sp->sn_line_hl > 0)
1656     {
1657 	msg_puts(" linehl=");
1658 	p = get_highlight_name_ext(NULL, sp->sn_line_hl - 1, FALSE);
1659 	if (p == NULL)
1660 	    msg_puts("NONE");
1661 	else
1662 	    msg_puts((char *)p);
1663     }
1664     if (sp->sn_text_hl > 0)
1665     {
1666 	msg_puts(" texthl=");
1667 	p = get_highlight_name_ext(NULL, sp->sn_text_hl - 1, FALSE);
1668 	if (p == NULL)
1669 	    msg_puts("NONE");
1670 	else
1671 	    msg_puts((char *)p);
1672     }
1673 }
1674 
1675 /*
1676  * Undefine a sign and free its memory.
1677  */
1678     static void
1679 sign_undefine(sign_T *sp, sign_T *sp_prev)
1680 {
1681     vim_free(sp->sn_name);
1682     vim_free(sp->sn_icon);
1683 # ifdef FEAT_SIGN_ICONS
1684     if (sp->sn_image != NULL)
1685     {
1686 	out_flush();
1687 	gui_mch_destroy_sign(sp->sn_image);
1688     }
1689 # endif
1690     vim_free(sp->sn_text);
1691     if (sp_prev == NULL)
1692 	first_sign = sp->sn_next;
1693     else
1694 	sp_prev->sn_next = sp->sn_next;
1695     vim_free(sp);
1696 }
1697 
1698 /*
1699  * Get highlighting attribute for sign "typenr".
1700  * If "line" is TRUE: line highl, if FALSE: text highl.
1701  */
1702     int
1703 sign_get_attr(int typenr, int line)
1704 {
1705     sign_T	*sp;
1706 
1707     for (sp = first_sign; sp != NULL; sp = sp->sn_next)
1708 	if (sp->sn_typenr == typenr)
1709 	{
1710 	    if (line)
1711 	    {
1712 		if (sp->sn_line_hl > 0)
1713 		    return syn_id2attr(sp->sn_line_hl);
1714 	    }
1715 	    else
1716 	    {
1717 		if (sp->sn_text_hl > 0)
1718 		    return syn_id2attr(sp->sn_text_hl);
1719 	    }
1720 	    break;
1721 	}
1722     return 0;
1723 }
1724 
1725 /*
1726  * Get text mark for sign "typenr".
1727  * Returns NULL if there isn't one.
1728  */
1729     char_u *
1730 sign_get_text(int typenr)
1731 {
1732     sign_T	*sp;
1733 
1734     for (sp = first_sign; sp != NULL; sp = sp->sn_next)
1735 	if (sp->sn_typenr == typenr)
1736 	    return sp->sn_text;
1737     return NULL;
1738 }
1739 
1740 # if defined(FEAT_SIGN_ICONS) || defined(PROTO)
1741     void *
1742 sign_get_image(
1743     int		typenr)		// the attribute which may have a sign
1744 {
1745     sign_T	*sp;
1746 
1747     for (sp = first_sign; sp != NULL; sp = sp->sn_next)
1748 	if (sp->sn_typenr == typenr)
1749 	    return sp->sn_image;
1750     return NULL;
1751 }
1752 # endif
1753 
1754 /*
1755  * Undefine/free all signs.
1756  */
1757     void
1758 free_signs(void)
1759 {
1760     while (first_sign != NULL)
1761 	sign_undefine(first_sign, NULL);
1762 }
1763 
1764 # if defined(FEAT_CMDL_COMPL) || defined(PROTO)
1765 static enum
1766 {
1767     EXP_SUBCMD,		// expand :sign sub-commands
1768     EXP_DEFINE,		// expand :sign define {name} args
1769     EXP_PLACE,		// expand :sign place {id} args
1770     EXP_UNPLACE,	// expand :sign unplace"
1771     EXP_SIGN_NAMES	// expand with name of placed signs
1772 } expand_what;
1773 
1774 /*
1775  * Function given to ExpandGeneric() to obtain the sign command
1776  * expansion.
1777  */
1778     char_u *
1779 get_sign_name(expand_T *xp UNUSED, int idx)
1780 {
1781     sign_T	*sp;
1782     int		current_idx;
1783 
1784     switch (expand_what)
1785     {
1786     case EXP_SUBCMD:
1787 	return (char_u *)cmds[idx];
1788     case EXP_DEFINE:
1789 	{
1790 	    char *define_arg[] =
1791 	    {
1792 		"icon=", "linehl=", "text=", "texthl=", NULL
1793 	    };
1794 	    return (char_u *)define_arg[idx];
1795 	}
1796     case EXP_PLACE:
1797 	{
1798 	    char *place_arg[] =
1799 	    {
1800 		"line=", "name=", "group=", "priority=", "file=",
1801 		"buffer=", NULL
1802 	    };
1803 	    return (char_u *)place_arg[idx];
1804 	}
1805     case EXP_UNPLACE:
1806 	{
1807 	    char *unplace_arg[] = { "group=", "file=", "buffer=", NULL };
1808 	    return (char_u *)unplace_arg[idx];
1809 	}
1810     case EXP_SIGN_NAMES:
1811 	// Complete with name of signs already defined
1812 	current_idx = 0;
1813 	for (sp = first_sign; sp != NULL; sp = sp->sn_next)
1814 	    if (current_idx++ == idx)
1815 		return sp->sn_name;
1816 	return NULL;
1817     default:
1818 	return NULL;
1819     }
1820 }
1821 
1822 /*
1823  * Handle command line completion for :sign command.
1824  */
1825     void
1826 set_context_in_sign_cmd(expand_T *xp, char_u *arg)
1827 {
1828     char_u	*p;
1829     char_u	*end_subcmd;
1830     char_u	*last;
1831     int		cmd_idx;
1832     char_u	*begin_subcmd_args;
1833 
1834     // Default: expand subcommands.
1835     xp->xp_context = EXPAND_SIGN;
1836     expand_what = EXP_SUBCMD;
1837     xp->xp_pattern = arg;
1838 
1839     end_subcmd = skiptowhite(arg);
1840     if (*end_subcmd == NUL)
1841 	// expand subcmd name
1842 	// :sign {subcmd}<CTRL-D>
1843 	return;
1844 
1845     cmd_idx = sign_cmd_idx(arg, end_subcmd);
1846 
1847     // :sign {subcmd} {subcmd_args}
1848     //		      |
1849     //		      begin_subcmd_args
1850     begin_subcmd_args = skipwhite(end_subcmd);
1851     p = skiptowhite(begin_subcmd_args);
1852     if (*p == NUL)
1853     {
1854 	//
1855 	// Expand first argument of subcmd when possible.
1856 	// For ":jump {id}" and ":unplace {id}", we could
1857 	// possibly expand the ids of all signs already placed.
1858 	//
1859 	xp->xp_pattern = begin_subcmd_args;
1860 	switch (cmd_idx)
1861 	{
1862 	    case SIGNCMD_LIST:
1863 	    case SIGNCMD_UNDEFINE:
1864 		// :sign list <CTRL-D>
1865 		// :sign undefine <CTRL-D>
1866 		expand_what = EXP_SIGN_NAMES;
1867 		break;
1868 	    default:
1869 		xp->xp_context = EXPAND_NOTHING;
1870 	}
1871 	return;
1872     }
1873 
1874     // expand last argument of subcmd
1875 
1876     // :sign define {name} {args}...
1877     //		    |
1878     //		    p
1879 
1880     // Loop until reaching last argument.
1881     do
1882     {
1883 	p = skipwhite(p);
1884 	last = p;
1885 	p = skiptowhite(p);
1886     } while (*p != NUL);
1887 
1888     p = vim_strchr(last, '=');
1889 
1890     // :sign define {name} {args}... {last}=
1891     //				     |	   |
1892     //				  last	   p
1893     if (p == NULL)
1894     {
1895 	// Expand last argument name (before equal sign).
1896 	xp->xp_pattern = last;
1897 	switch (cmd_idx)
1898 	{
1899 	    case SIGNCMD_DEFINE:
1900 		expand_what = EXP_DEFINE;
1901 		break;
1902 	    case SIGNCMD_PLACE:
1903 		expand_what = EXP_PLACE;
1904 		break;
1905 	    case SIGNCMD_JUMP:
1906 	    case SIGNCMD_UNPLACE:
1907 		expand_what = EXP_UNPLACE;
1908 		break;
1909 	    default:
1910 		xp->xp_context = EXPAND_NOTHING;
1911 	}
1912     }
1913     else
1914     {
1915 	// Expand last argument value (after equal sign).
1916 	xp->xp_pattern = p + 1;
1917 	switch (cmd_idx)
1918 	{
1919 	    case SIGNCMD_DEFINE:
1920 		if (STRNCMP(last, "texthl", p - last) == 0
1921 			|| STRNCMP(last, "linehl", p - last) == 0)
1922 		    xp->xp_context = EXPAND_HIGHLIGHT;
1923 		else if (STRNCMP(last, "icon", p - last) == 0)
1924 		    xp->xp_context = EXPAND_FILES;
1925 		else
1926 		    xp->xp_context = EXPAND_NOTHING;
1927 		break;
1928 	    case SIGNCMD_PLACE:
1929 		if (STRNCMP(last, "name", p - last) == 0)
1930 		    expand_what = EXP_SIGN_NAMES;
1931 		else
1932 		    xp->xp_context = EXPAND_NOTHING;
1933 		break;
1934 	    default:
1935 		xp->xp_context = EXPAND_NOTHING;
1936 	}
1937     }
1938 }
1939 # endif
1940 
1941 #endif /* FEAT_SIGNS */
1942