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