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