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. |
23 | typedef struct sign sign_T; |
24 | |
25 | struct 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 | |
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 |
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 | /// |
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->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. |
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->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. |
120 | int 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 |
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 | 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. |
168 | static 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. |
212 | static 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. |
241 | char_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 |
254 | dict_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. |
269 | void 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. |
300 | linenr_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 |
332 | int 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. |
377 | linenr_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. |
435 | int 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. |
454 | static 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'. |
472 | int 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". |
489 | void 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. |
521 | void 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. |
569 | void 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. |
601 | static 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. |
620 | static 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 |
640 | static 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 |
680 | static 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 |
688 | static 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 |
732 | int 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'. |
784 | int 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' |
800 | static 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. |
814 | int 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 |
862 | int 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. |
886 | static 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. |
899 | linenr_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 |
932 | static 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 |
984 | static 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 |
1023 | static 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} |
1086 | static 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}. |
1114 | static 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 |
1212 | void 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 |
1289 | static 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. |
1325 | void 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. |
1349 | list_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 |
1364 | static 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. |
1399 | void 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. |
1419 | static 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. |
1464 | static 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". |
1478 | int 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. |
1509 | char_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. |
1522 | void free_signs(void) |
1523 | { |
1524 | while (first_sign != NULL) { |
1525 | sign_undefine(first_sign, NULL); |
1526 | } |
1527 | } |
1528 | |
1529 | static 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. |
1540 | char_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. |
1575 | void 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 | |