sign.c (44732B)
1 // sign.c: functions for managing with signs 2 3 #include <assert.h> 4 #include <inttypes.h> 5 #include <stdbool.h> 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <string.h> 9 10 #include "klib/kvec.h" 11 #include "nvim/api/extmark.h" 12 #include "nvim/api/private/defs.h" 13 #include "nvim/api/private/helpers.h" 14 #include "nvim/ascii_defs.h" 15 #include "nvim/buffer.h" 16 #include "nvim/buffer_defs.h" 17 #include "nvim/charset.h" 18 #include "nvim/cmdexpand_defs.h" 19 #include "nvim/cursor.h" 20 #include "nvim/decoration.h" 21 #include "nvim/decoration_defs.h" 22 #include "nvim/drawscreen.h" 23 #include "nvim/edit.h" 24 #include "nvim/errors.h" 25 #include "nvim/eval/funcs.h" 26 #include "nvim/eval/typval.h" 27 #include "nvim/eval/typval_defs.h" 28 #include "nvim/ex_cmds_defs.h" 29 #include "nvim/ex_docmd.h" 30 #include "nvim/extmark.h" 31 #include "nvim/fold.h" 32 #include "nvim/gettext_defs.h" 33 #include "nvim/globals.h" 34 #include "nvim/grid.h" 35 #include "nvim/highlight_defs.h" 36 #include "nvim/highlight_group.h" 37 #include "nvim/macros_defs.h" 38 #include "nvim/map_defs.h" 39 #include "nvim/marktree.h" 40 #include "nvim/marktree_defs.h" 41 #include "nvim/mbyte.h" 42 #include "nvim/memory.h" 43 #include "nvim/message.h" 44 #include "nvim/pos_defs.h" 45 #include "nvim/sign.h" 46 #include "nvim/sign_defs.h" 47 #include "nvim/strings.h" 48 #include "nvim/types_defs.h" 49 #include "nvim/vim_defs.h" 50 #include "nvim/window.h" 51 52 #include "sign.c.generated.h" 53 54 static PMap(cstr_t) sign_map = MAP_INIT; 55 static kvec_t(Integer) sign_ns = KV_INITIAL_VALUE; 56 57 static char *cmds[] = { 58 "define", 59 #define SIGNCMD_DEFINE 0 60 "undefine", 61 #define SIGNCMD_UNDEFINE 1 62 "list", 63 #define SIGNCMD_LIST 2 64 "place", 65 #define SIGNCMD_PLACE 3 66 "unplace", 67 #define SIGNCMD_UNPLACE 4 68 "jump", 69 #define SIGNCMD_JUMP 5 70 NULL 71 #define SIGNCMD_LAST 6 72 }; 73 74 // Convert the supplied "group" to a namespace filter 75 static int64_t group_get_ns(const char *group) 76 { 77 if (group == NULL) { 78 return 0; // Global namespace 79 } else if (strcmp(group, "*") == 0) { 80 return UINT32_MAX; // All namespaces 81 } 82 // Specific or non-existing namespace 83 int ns = map_get(String, int)(&namespace_ids, cstr_as_string(group)); 84 return ns ? ns : -1; 85 } 86 87 static const char *sign_get_name(DecorSignHighlight *sh) 88 { 89 char *name = sh->sign_name; 90 return !name ? "" : map_has(cstr_t, &sign_map, name) ? name : "[Deleted]"; 91 } 92 93 /// Create or update a sign extmark. 94 /// 95 /// @param buf buffer to store sign in 96 /// @param id sign ID 97 /// @param group sign group 98 /// @param prio sign priority 99 /// @param lnum line number which gets the mark 100 /// @param sp sign properties 101 static void buf_set_sign(buf_T *buf, uint32_t *id, char *group, int prio, linenr_T lnum, sign_T *sp) 102 { 103 if (group && !map_get(String, int)(&namespace_ids, cstr_as_string(group))) { 104 kv_push(sign_ns, nvim_create_namespace(cstr_as_string(group))); 105 } 106 107 uint32_t ns = group ? (uint32_t)nvim_create_namespace(cstr_as_string(group)) : 0; 108 DecorSignHighlight sign = DECOR_SIGN_HIGHLIGHT_INIT; 109 110 sign.flags |= kSHIsSign; 111 memcpy(sign.text, sp->sn_text, SIGN_WIDTH * sizeof(schar_T)); 112 sign.sign_name = xstrdup(sp->sn_name); 113 sign.hl_id = sp->sn_text_hl; 114 sign.line_hl_id = sp->sn_line_hl; 115 sign.number_hl_id = sp->sn_num_hl; 116 sign.cursorline_hl_id = sp->sn_cul_hl; 117 sign.priority = (DecorPriority)prio; 118 119 bool has_hl = (sp->sn_line_hl || sp->sn_num_hl || sp->sn_cul_hl); 120 uint16_t decor_flags = (sp->sn_text[0] ? MT_FLAG_DECOR_SIGNTEXT : 0) 121 | (has_hl ? MT_FLAG_DECOR_SIGNHL : 0); 122 123 DecorInline decor = { .ext = true, .data.ext = { .vt = NULL, .sh_idx = decor_put_sh(sign) } }; 124 extmark_set(buf, ns, id, MIN(buf->b_ml.ml_line_count, lnum) - 1, 0, -1, -1, 125 decor, decor_flags, true, false, true, true, NULL); 126 } 127 128 /// For an existing, placed sign with "id", modify the sign, group or priority. 129 /// Returns the line number of the sign, or zero if the sign is not found. 130 /// 131 /// @param buf buffer to store sign in 132 /// @param id sign ID 133 /// @param group sign group 134 /// @param prio sign priority 135 /// @param sp sign pointer 136 static linenr_T buf_mod_sign(buf_T *buf, uint32_t *id, char *group, int prio, sign_T *sp) 137 { 138 int64_t ns = group_get_ns(group); 139 if (ns < 0 || (group && ns == 0)) { 140 return 0; 141 } 142 143 MTKey mark = marktree_lookup_ns(buf->b_marktree, (uint32_t)ns, *id, false, NULL); 144 if (mark.pos.row >= 0) { 145 buf_set_sign(buf, id, group, prio, mark.pos.row + 1, sp); 146 } 147 return mark.pos.row + 1; 148 } 149 150 /// Find the line number of the sign with the requested id in group 'group'. If 151 /// the sign does not exist, return 0 as the line number. This will still let 152 /// the correct file get loaded. 153 /// 154 /// @param buf buffer to store sign in 155 /// @param id sign ID 156 /// @param group sign group 157 static int buf_findsign(buf_T *buf, int id, char *group) 158 { 159 int64_t ns = group_get_ns(group); 160 if (ns < 0 || (group && ns == 0)) { 161 return 0; 162 } 163 return marktree_lookup_ns(buf->b_marktree, (uint32_t)ns, (uint32_t)id, false, NULL).pos.row + 1; 164 } 165 166 /// qsort() function to sort signs by line number, priority, id and recency. 167 static int sign_row_cmp(const void *p1, const void *p2) 168 { 169 const MTKey *s1 = (MTKey *)p1; 170 const MTKey *s2 = (MTKey *)p2; 171 172 if (s1->pos.row != s2->pos.row) { 173 return s1->pos.row > s2->pos.row ? 1 : -1; 174 } 175 176 DecorSignHighlight *sh1 = decor_find_sign(mt_decor(*s1)); 177 DecorSignHighlight *sh2 = decor_find_sign(mt_decor(*s2)); 178 assert(sh1 && sh2); 179 SignItem si1 = { sh1, s1->id }; 180 SignItem si2 = { sh2, s2->id }; 181 182 return sign_item_cmp(&si1, &si2); 183 } 184 185 /// Delete the specified sign(s) 186 /// 187 /// @param buf buffer sign is stored in or NULL for all buffers 188 /// @param group sign group 189 /// @param id sign id 190 /// @param atlnum single sign at this line, specified signs at any line when -1 191 static int buf_delete_signs(buf_T *buf, char *group, int id, linenr_T atlnum) 192 { 193 int64_t ns = group_get_ns(group); 194 if (ns < 0) { 195 return FAIL; 196 } 197 198 MarkTreeIter itr[1]; 199 int row = atlnum > 0 ? atlnum - 1 : 0; 200 kvec_t(MTKey) signs = KV_INITIAL_VALUE; 201 // Store signs at a specific line number to remove one later. 202 if (atlnum > 0) { 203 if (!marktree_itr_get_overlap(buf->b_marktree, row, 0, itr)) { 204 return FAIL; 205 } 206 207 MTPair pair; 208 while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) { 209 if ((ns == UINT32_MAX || ns == pair.start.ns) && mt_decor_sign(pair.start)) { 210 kv_push(signs, pair.start); 211 } 212 } 213 } else { 214 marktree_itr_get(buf->b_marktree, 0, 0, itr); 215 } 216 217 while (itr->x) { 218 MTKey mark = marktree_itr_current(itr); 219 if (row && mark.pos.row > row) { 220 break; 221 } 222 if (!mt_end(mark) && mt_decor_sign(mark) 223 && (id == 0 || (int)mark.id == id) 224 && (ns == UINT32_MAX || ns == mark.ns)) { 225 if (atlnum > 0) { 226 kv_push(signs, mark); 227 marktree_itr_next(buf->b_marktree, itr); 228 } else { 229 extmark_del(buf, itr, mark, true); 230 } 231 } else { 232 marktree_itr_next(buf->b_marktree, itr); 233 } 234 } 235 236 // Sort to remove the highest priority sign at a specific line number. 237 if (kv_size(signs)) { 238 qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_row_cmp); 239 extmark_del_id(buf, kv_A(signs, 0).ns, kv_A(signs, 0).id); 240 kv_destroy(signs); 241 } else if (atlnum > 0) { 242 return FAIL; 243 } 244 245 return OK; 246 } 247 248 bool buf_has_signs(const buf_T *buf) 249 { 250 return (buf_meta_total(buf, kMTMetaSignHL) + buf_meta_total(buf, kMTMetaSignText)); 251 } 252 253 /// List placed signs for "rbuf". If "rbuf" is NULL do it for all buffers. 254 static void sign_list_placed(buf_T *rbuf, char *group) 255 { 256 char lbuf[MSG_BUF_LEN]; 257 char namebuf[MSG_BUF_LEN]; 258 char groupbuf[MSG_BUF_LEN]; 259 buf_T *buf = rbuf ? rbuf : firstbuf; 260 int64_t ns = group_get_ns(group); 261 262 msg_puts_title(_("\n--- Signs ---")); 263 264 while (buf != NULL && !got_int) { 265 if (buf_has_signs(buf)) { 266 msg_putchar('\n'); 267 vim_snprintf(lbuf, MSG_BUF_LEN, _("Signs for %s:"), buf->b_fname); 268 msg_puts_hl(lbuf, HLF_D, false); 269 } 270 271 if (ns >= 0) { 272 MarkTreeIter itr[1]; 273 kvec_t(MTKey) signs = KV_INITIAL_VALUE; 274 marktree_itr_get(buf->b_marktree, 0, 0, itr); 275 276 while (itr->x) { 277 MTKey mark = marktree_itr_current(itr); 278 if (!mt_end(mark) && mt_decor_sign(mark) 279 && (ns == UINT32_MAX || ns == mark.ns)) { 280 kv_push(signs, mark); 281 } 282 marktree_itr_next(buf->b_marktree, itr); 283 } 284 285 if (kv_size(signs)) { 286 qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_row_cmp); 287 msg_putchar('\n'); 288 289 for (size_t i = 0; i < kv_size(signs); i++) { 290 namebuf[0] = NUL; 291 groupbuf[0] = NUL; 292 MTKey mark = kv_A(signs, i); 293 294 DecorSignHighlight *sh = decor_find_sign(mt_decor(mark)); 295 if (sh->sign_name != NULL) { 296 vim_snprintf(namebuf, MSG_BUF_LEN, _(" name=%s"), sign_get_name(sh)); 297 } 298 if (mark.ns != 0) { 299 vim_snprintf(groupbuf, MSG_BUF_LEN, _(" group=%s"), describe_ns((int)mark.ns, "")); 300 } 301 vim_snprintf(lbuf, MSG_BUF_LEN, _(" line=%" PRIdLINENR " id=%u%s%s priority=%d"), 302 mark.pos.row + 1, mark.id, groupbuf, namebuf, sh->priority); 303 msg_puts(lbuf); 304 if (i < kv_size(signs) - 1) { 305 msg_putchar('\n'); 306 } 307 } 308 kv_destroy(signs); 309 } 310 } 311 312 if (rbuf != NULL) { 313 return; 314 } 315 buf = buf->b_next; 316 } 317 } 318 319 /// Find index of a ":sign" subcmd from its name. 320 /// "*end_cmd" must be writable. 321 /// 322 /// @param begin_cmd begin of sign subcmd 323 /// @param end_cmd just after sign subcmd 324 static int sign_cmd_idx(char *begin_cmd, char *end_cmd) 325 { 326 int idx; 327 char save = *end_cmd; 328 329 *end_cmd = NUL; 330 for (idx = 0;; idx++) { 331 if (cmds[idx] == NULL || strcmp(begin_cmd, cmds[idx]) == 0) { 332 break; 333 } 334 } 335 *end_cmd = save; 336 return idx; 337 } 338 339 /// buf must be SIGN_WIDTH * MAX_SCHAR_SIZE (no extra +1 needed) 340 size_t describe_sign_text(char *buf, schar_T *sign_text) 341 { 342 size_t p = 0; 343 for (int i = 0; i < SIGN_WIDTH; i++) { 344 schar_get(buf + p, sign_text[i]); 345 size_t len = strlen(buf + p); 346 if (len == 0) { 347 break; 348 } 349 p += len; 350 } 351 return p; 352 } 353 354 /// Initialize the "text" for a new sign and store in "sign_text". 355 /// "sp" is NULL for signs added through nvim_buf_set_extmark(). 356 int init_sign_text(sign_T *sp, schar_T *sign_text, char *text) 357 { 358 char *s; 359 char *endp = text + (int)strlen(text); 360 361 for (s = sp ? text : endp; s + 1 < endp; s++) { 362 if (*s == '\\') { 363 // Remove a backslash, so that it is possible to use a space. 364 STRMOVE(s, s + 1); 365 endp--; 366 } 367 } 368 // Count cells and check for non-printable chars 369 int cells = 0; 370 for (s = text; s < endp; s += utfc_ptr2len(s)) { 371 int c; 372 sign_text[cells] = utfc_ptr2schar(s, &c); 373 if (!vim_isprintc(c)) { 374 break; 375 } 376 int width = utf_ptr2cells(s); 377 if (width == 2) { 378 sign_text[cells + 1] = 0; 379 } 380 cells += width; 381 } 382 // Currently must be empty, one or two display cells 383 if (s != endp || cells > SIGN_WIDTH) { 384 if (sp != NULL) { 385 semsg(_("E239: Invalid sign text: %s"), text); 386 } 387 return FAIL; 388 } 389 390 if (cells < 1) { 391 sign_text[0] = 0; 392 } else if (cells == 1) { 393 sign_text[1] = schar_from_ascii(' '); 394 } 395 396 return OK; 397 } 398 399 /// Define a new sign or update an existing sign 400 static int sign_define_by_name(char *name, char *icon, char *text, char *linehl, char *texthl, 401 char *culhl, char *numhl, int prio) 402 { 403 cstr_t *key; 404 bool new_sign = false; 405 sign_T **sp = (sign_T **)pmap_put_ref(cstr_t)(&sign_map, name, &key, &new_sign); 406 407 if (new_sign) { 408 *key = xstrdup(name); 409 *sp = xcalloc(1, sizeof(sign_T)); 410 (*sp)->sn_name = (char *)(*key); 411 } 412 413 // Set values for a defined sign. 414 if (icon != NULL) { 415 /// Initialize the icon information for a new sign 416 xfree((*sp)->sn_icon); 417 (*sp)->sn_icon = xstrdup(icon); 418 backslash_halve((*sp)->sn_icon); 419 } 420 421 if (text != NULL && (init_sign_text(*sp, (*sp)->sn_text, text) == FAIL)) { 422 return FAIL; 423 } 424 425 (*sp)->sn_priority = prio; 426 427 char *arg[] = { linehl, texthl, culhl, numhl }; 428 int *hl[] = { &(*sp)->sn_line_hl, &(*sp)->sn_text_hl, &(*sp)->sn_cul_hl, &(*sp)->sn_num_hl }; 429 for (int i = 0; i < 4; i++) { 430 if (arg[i] != NULL) { 431 *hl[i] = *arg[i] ? syn_check_group(arg[i], strlen(arg[i])) : 0; 432 } 433 } 434 435 // Update already placed signs and redraw if necessary when modifying a sign. 436 if (!new_sign) { 437 bool did_redraw = false; 438 for (size_t i = 0; i < kv_size(decor_items); i++) { 439 DecorSignHighlight *sh = &kv_A(decor_items, i); 440 if (sh->sign_name && strcmp(sh->sign_name, name) == 0) { 441 memcpy(sh->text, (*sp)->sn_text, SIGN_WIDTH * sizeof(schar_T)); 442 sh->hl_id = (*sp)->sn_text_hl; 443 sh->line_hl_id = (*sp)->sn_line_hl; 444 sh->number_hl_id = (*sp)->sn_num_hl; 445 sh->cursorline_hl_id = (*sp)->sn_cul_hl; 446 if (!did_redraw) { 447 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 448 if (buf_has_signs(wp->w_buffer)) { 449 redraw_buf_later(wp->w_buffer, UPD_NOT_VALID); 450 } 451 } 452 did_redraw = true; 453 } 454 } 455 } 456 } 457 return OK; 458 } 459 460 /// Free the sign specified by 'name'. 461 static int sign_undefine_by_name(const char *name) 462 { 463 sign_T *sp = pmap_del(cstr_t)(&sign_map, name, NULL); 464 if (sp == NULL) { 465 semsg(_("E155: Unknown sign: %s"), name); 466 return FAIL; 467 } 468 469 xfree(sp->sn_name); 470 xfree(sp->sn_icon); 471 xfree(sp); 472 return OK; 473 } 474 475 /// List one sign. 476 static void sign_list_defined(sign_T *sp) 477 { 478 smsg(0, "sign %s", sp->sn_name); 479 if (sp->sn_icon != NULL) { 480 msg_puts(" icon="); 481 msg_outtrans(sp->sn_icon, 0, false); 482 msg_puts(_(" (not supported)")); 483 } 484 if (sp->sn_text[0]) { 485 msg_puts(" text="); 486 char buf[SIGN_WIDTH * MAX_SCHAR_SIZE]; 487 describe_sign_text(buf, sp->sn_text); 488 msg_outtrans(buf, 0, false); 489 } 490 if (sp->sn_priority > 0) { 491 char lbuf[MSG_BUF_LEN]; 492 vim_snprintf(lbuf, MSG_BUF_LEN, " priority=%d", sp->sn_priority); 493 msg_puts(lbuf); 494 } 495 static char *arg[] = { " linehl=", " texthl=", " culhl=", " numhl=" }; 496 int hl[] = { sp->sn_line_hl, sp->sn_text_hl, sp->sn_cul_hl, sp->sn_num_hl }; 497 for (int i = 0; i < 4; i++) { 498 if (hl[i] > 0) { 499 msg_puts(arg[i]); 500 const char *p = get_highlight_name_ext(NULL, hl[i] - 1, false); 501 msg_puts(p ? p : "NONE"); 502 } 503 } 504 } 505 506 /// List the signs matching 'name' 507 static void sign_list_by_name(char *name) 508 { 509 sign_T *sp = pmap_get(cstr_t)(&sign_map, name); 510 if (sp != NULL) { 511 sign_list_defined(sp); 512 } else { 513 semsg(_("E155: Unknown sign: %s"), name); 514 } 515 } 516 517 /// Place a sign at the specified file location or update a sign. 518 static int sign_place(uint32_t *id, char *group, char *name, buf_T *buf, linenr_T lnum, int prio) 519 { 520 // Check for reserved character '*' in group name 521 if (group != NULL && (*group == '*' || *group == NUL)) { 522 return FAIL; 523 } 524 525 sign_T *sp = pmap_get(cstr_t)(&sign_map, name); 526 if (sp == NULL) { 527 semsg(_("E155: Unknown sign: %s"), name); 528 return FAIL; 529 } 530 531 // Use the default priority value for this sign. 532 if (prio == -1) { 533 prio = (sp->sn_priority != -1) ? sp->sn_priority : SIGN_DEF_PRIO; 534 } 535 536 if (lnum > 0) { 537 // ":sign place {id} line={lnum} name={name} file={fname}": place a sign 538 buf_set_sign(buf, id, group, prio, lnum, sp); 539 } else { 540 // ":sign place {id} file={fname}": change sign type and/or priority 541 lnum = buf_mod_sign(buf, id, group, prio, sp); 542 } 543 if (lnum <= 0) { 544 semsg(_("E885: Not possible to change sign %s"), name); 545 return FAIL; 546 } 547 548 return OK; 549 } 550 551 static int sign_unplace_inner(buf_T *buf, int id, char *group, linenr_T atlnum) 552 { 553 if (!buf_has_signs(buf)) { // No signs in the buffer 554 return FAIL; 555 } 556 557 if (id == 0 || atlnum > 0 || (group != NULL && *group == '*')) { 558 // Delete multiple specified signs 559 if (!buf_delete_signs(buf, group, id, atlnum)) { 560 return FAIL; 561 } 562 } else { 563 // Delete only a single sign 564 int64_t ns = group_get_ns(group); 565 if (ns < 0 || !extmark_del_id(buf, (uint32_t)ns, (uint32_t)id)) { 566 return FAIL; 567 } 568 } 569 570 return OK; 571 } 572 573 /// Unplace the specified sign for a single or all buffers 574 static int sign_unplace(buf_T *buf, int id, char *group, linenr_T atlnum) 575 { 576 if (buf != NULL) { 577 return sign_unplace_inner(buf, id, group, atlnum); 578 } else { 579 int retval = OK; 580 FOR_ALL_BUFFERS(cbuf) { 581 if (!sign_unplace_inner(cbuf, id, group, atlnum)) { 582 retval = FAIL; 583 } 584 } 585 return retval; 586 } 587 } 588 589 /// Jump to a sign. 590 static linenr_T sign_jump(int id, char *group, buf_T *buf) 591 { 592 linenr_T lnum = buf_findsign(buf, id, group); 593 594 if (lnum <= 0) { 595 semsg(_("E157: Invalid sign ID: %d"), id); 596 return -1; 597 } 598 599 // goto a sign ... 600 if (buf_jump_open_win(buf) != NULL) { // ... in a current window 601 curwin->w_cursor.lnum = lnum; 602 check_cursor_lnum(curwin); 603 beginline(BL_WHITE); 604 } else { // ... not currently in a window 605 if (buf->b_fname == NULL) { 606 emsg(_("E934: Cannot jump to a buffer that does not have a name")); 607 return -1; 608 } 609 size_t cmdlen = strlen(buf->b_fname) + 24; 610 char *cmd = xmallocz(cmdlen); 611 snprintf(cmd, cmdlen, "e +%" PRId64 " %s", (int64_t)lnum, buf->b_fname); 612 do_cmdline_cmd(cmd); 613 xfree(cmd); 614 } 615 616 foldOpenCursor(); 617 618 return lnum; 619 } 620 621 /// ":sign define {name} ..." command 622 static void sign_define_cmd(char *name, char *cmdline) 623 { 624 char *icon = NULL; 625 char *text = NULL; 626 char *linehl = NULL; 627 char *texthl = NULL; 628 char *culhl = NULL; 629 char *numhl = NULL; 630 int prio = -1; 631 632 // set values for a defined sign. 633 while (true) { 634 char *arg = skipwhite(cmdline); 635 if (*arg == NUL) { 636 break; 637 } 638 cmdline = skiptowhite_esc(arg); 639 if (strncmp(arg, "icon=", 5) == 0) { 640 icon = arg + 5; 641 } else if (strncmp(arg, "text=", 5) == 0) { 642 text = arg + 5; 643 } else if (strncmp(arg, "linehl=", 7) == 0) { 644 linehl = arg + 7; 645 } else if (strncmp(arg, "texthl=", 7) == 0) { 646 texthl = arg + 7; 647 } else if (strncmp(arg, "culhl=", 6) == 0) { 648 culhl = arg + 6; 649 } else if (strncmp(arg, "numhl=", 6) == 0) { 650 numhl = arg + 6; 651 } else if (strncmp(arg, "priority=", 9) == 0) { 652 prio = atoi(arg + 9); 653 } else { 654 semsg(_(e_invarg2), arg); 655 return; 656 } 657 if (*cmdline == NUL) { 658 break; 659 } 660 *cmdline++ = NUL; 661 } 662 663 sign_define_by_name(name, icon, text, linehl, texthl, culhl, numhl, prio); 664 } 665 666 /// ":sign place" command 667 static void sign_place_cmd(buf_T *buf, linenr_T lnum, char *name, int id, char *group, int prio) 668 { 669 if (id <= 0) { 670 // List signs placed in a file/buffer 671 // :sign place file={fname} 672 // :sign place group={group} file={fname} 673 // :sign place group=* file={fname} 674 // :sign place buffer={nr} 675 // :sign place group={group} buffer={nr} 676 // :sign place group=* buffer={nr} 677 // :sign place 678 // :sign place group={group} 679 // :sign place group=* 680 if (lnum >= 0 || name != NULL || (group != NULL && *group == NUL)) { 681 emsg(_(e_invarg)); 682 } else { 683 sign_list_placed(buf, group); 684 } 685 } else { 686 // Place a new sign 687 if (name == NULL || buf == NULL || (group != NULL && *group == NUL)) { 688 emsg(_(e_invarg)); 689 return; 690 } 691 uint32_t uid = (uint32_t)id; 692 sign_place(&uid, group, name, buf, lnum, prio); 693 } 694 } 695 696 /// ":sign unplace" command 697 static void sign_unplace_cmd(buf_T *buf, linenr_T lnum, const char *name, int id, char *group) 698 { 699 if (lnum >= 0 || name != NULL || (group != NULL && *group == NUL)) { 700 emsg(_(e_invarg)); 701 return; 702 } 703 704 if (id == -1) { 705 lnum = curwin->w_cursor.lnum; 706 buf = curwin->w_buffer; 707 } 708 709 if (!sign_unplace(buf, MAX(0, id), group, lnum) && lnum > 0) { 710 emsg(_("E159: Missing sign number")); 711 } 712 } 713 714 /// Jump to a placed sign commands: 715 /// :sign jump {id} file={fname} 716 /// :sign jump {id} buffer={nr} 717 /// :sign jump {id} group={group} file={fname} 718 /// :sign jump {id} group={group} buffer={nr} 719 static void sign_jump_cmd(buf_T *buf, linenr_T lnum, const char *name, int id, char *group) 720 { 721 if (name == NULL && group == NULL && id == -1) { 722 emsg(_(e_argreq)); 723 return; 724 } 725 726 if (buf == NULL || (group != NULL && *group == NUL) || lnum >= 0 || name != NULL) { 727 // File or buffer is not specified or an empty group is used 728 // or a line number or a sign name is specified. 729 emsg(_(e_invarg)); 730 return; 731 } 732 733 sign_jump(id, group, buf); 734 } 735 736 /// Parse the command line arguments for the ":sign place", ":sign unplace" and 737 /// ":sign jump" commands. 738 /// The supported arguments are: line={lnum} name={name} group={group} 739 /// priority={prio} and file={fname} or buffer={nr}. 740 static int parse_sign_cmd_args(int cmd, char *arg, char **name, int *id, char **group, int *prio, 741 buf_T **buf, linenr_T *lnum) 742 { 743 char *arg1 = arg; 744 char *filename = NULL; 745 bool lnum_arg = false; 746 747 // first arg could be placed sign id 748 if (ascii_isdigit(*arg)) { 749 *id = getdigits_int(&arg, true, 0); 750 if (!ascii_iswhite(*arg) && *arg != NUL) { 751 *id = -1; 752 arg = arg1; 753 } else { 754 arg = skipwhite(arg); 755 } 756 } 757 758 while (*arg != NUL) { 759 if (strncmp(arg, "line=", 5) == 0) { 760 arg += 5; 761 *lnum = atoi(arg); 762 arg = skiptowhite(arg); 763 lnum_arg = true; 764 } else if (strncmp(arg, "*", 1) == 0 && cmd == SIGNCMD_UNPLACE) { 765 if (*id != -1) { 766 emsg(_(e_invarg)); 767 return FAIL; 768 } 769 *id = -2; 770 arg = skiptowhite(arg + 1); 771 } else if (strncmp(arg, "name=", 5) == 0) { 772 arg += 5; 773 char *namep = arg; 774 arg = skiptowhite(arg); 775 if (*arg != NUL) { 776 *arg++ = NUL; 777 } 778 while (namep[0] == '0' && namep[1] != NUL) { 779 namep++; 780 } 781 *name = namep; 782 } else if (strncmp(arg, "group=", 6) == 0) { 783 arg += 6; 784 *group = arg; 785 arg = skiptowhite(arg); 786 if (*arg != NUL) { 787 *arg++ = NUL; 788 } 789 } else if (strncmp(arg, "priority=", 9) == 0) { 790 arg += 9; 791 *prio = atoi(arg); 792 arg = skiptowhite(arg); 793 } else if (strncmp(arg, "file=", 5) == 0) { 794 arg += 5; 795 filename = arg; 796 *buf = buflist_findname_exp(arg); 797 break; 798 } else if (strncmp(arg, "buffer=", 7) == 0) { 799 arg += 7; 800 filename = arg; 801 *buf = buflist_findnr(getdigits_int(&arg, true, 0)); 802 if (*skipwhite(arg) != NUL) { 803 semsg(_(e_trailing_arg), arg); 804 } 805 break; 806 } else { 807 emsg(_(e_invarg)); 808 return FAIL; 809 } 810 arg = skipwhite(arg); 811 } 812 813 if (filename != NULL && *buf == NULL) { 814 semsg(_(e_invalid_buffer_name_str), filename); 815 return FAIL; 816 } 817 818 // If the filename is not supplied for the sign place or the sign jump 819 // command, then use the current buffer. 820 if (filename == NULL && ((cmd == SIGNCMD_PLACE && lnum_arg) || cmd == SIGNCMD_JUMP)) { 821 *buf = curwin->w_buffer; 822 } 823 return OK; 824 } 825 826 /// ":sign" command 827 void ex_sign(exarg_T *eap) 828 { 829 char *arg = eap->arg; 830 831 // Parse the subcommand. 832 char *p = skiptowhite(arg); 833 int idx = sign_cmd_idx(arg, p); 834 if (idx == SIGNCMD_LAST) { 835 semsg(_("E160: Unknown sign command: %s"), arg); 836 return; 837 } 838 arg = skipwhite(p); 839 840 if (idx <= SIGNCMD_LIST) { 841 // Define, undefine or list signs. 842 if (idx == SIGNCMD_LIST && *arg == NUL) { 843 // ":sign list": list all defined signs 844 sign_T *sp; 845 map_foreach_value(&sign_map, sp, { 846 sign_list_defined(sp); 847 }); 848 } else if (*arg == NUL) { 849 emsg(_("E156: Missing sign name")); 850 } else { 851 // Isolate the sign name. If it's a number skip leading zeroes, 852 // so that "099" and "99" are the same sign. But keep "0". 853 p = skiptowhite(arg); 854 if (*p != NUL) { 855 *p++ = NUL; 856 } 857 while (arg[0] == '0' && arg[1] != NUL) { 858 arg++; 859 } 860 861 if (idx == SIGNCMD_DEFINE) { 862 sign_define_cmd(arg, p); 863 } else if (idx == SIGNCMD_LIST) { 864 // ":sign list {name}" 865 sign_list_by_name(arg); 866 } else { 867 // ":sign undefine {name}" 868 sign_undefine_by_name(arg); 869 } 870 871 return; 872 } 873 } else { 874 int id = -1; 875 linenr_T lnum = -1; 876 char *name = NULL; 877 char *group = NULL; 878 int prio = -1; 879 buf_T *buf = NULL; 880 881 // Parse command line arguments 882 if (parse_sign_cmd_args(idx, arg, &name, &id, &group, &prio, &buf, &lnum) == FAIL) { 883 return; 884 } 885 886 if (idx == SIGNCMD_PLACE) { 887 sign_place_cmd(buf, lnum, name, id, group, prio); 888 } else if (idx == SIGNCMD_UNPLACE) { 889 sign_unplace_cmd(buf, lnum, name, id, group); 890 } else if (idx == SIGNCMD_JUMP) { 891 sign_jump_cmd(buf, lnum, name, id, group); 892 } 893 } 894 } 895 896 /// Get dictionary of information for a defined sign "sp" 897 static dict_T *sign_get_info_dict(sign_T *sp) 898 { 899 dict_T *d = tv_dict_alloc(); 900 901 tv_dict_add_str(d, S_LEN("name"), sp->sn_name); 902 903 if (sp->sn_icon != NULL) { 904 tv_dict_add_str(d, S_LEN("icon"), sp->sn_icon); 905 } 906 if (sp->sn_text[0]) { 907 char buf[SIGN_WIDTH * MAX_SCHAR_SIZE]; 908 describe_sign_text(buf, sp->sn_text); 909 tv_dict_add_str(d, S_LEN("text"), buf); 910 } 911 if (sp->sn_priority > 0) { 912 tv_dict_add_nr(d, S_LEN("priority"), sp->sn_priority); 913 } 914 static char *arg[] = { "linehl", "texthl", "culhl", "numhl" }; 915 int hl[] = { sp->sn_line_hl, sp->sn_text_hl, sp->sn_cul_hl, sp->sn_num_hl }; 916 for (int i = 0; i < 4; i++) { 917 if (hl[i] > 0) { 918 const char *p = get_highlight_name_ext(NULL, hl[i] - 1, false); 919 tv_dict_add_str(d, arg[i], strlen(arg[i]), p ? p : "NONE"); 920 } 921 } 922 return d; 923 } 924 925 /// Get dictionary of information for placed sign "mark" 926 static dict_T *sign_get_placed_info_dict(MTKey mark) 927 { 928 dict_T *d = tv_dict_alloc(); 929 930 DecorSignHighlight *sh = decor_find_sign(mt_decor(mark)); 931 932 tv_dict_add_str(d, S_LEN("name"), sign_get_name(sh)); 933 tv_dict_add_nr(d, S_LEN("id"), (int)mark.id); 934 tv_dict_add_str(d, S_LEN("group"), describe_ns((int)mark.ns, "")); 935 tv_dict_add_nr(d, S_LEN("lnum"), mark.pos.row + 1); 936 tv_dict_add_nr(d, S_LEN("priority"), sh->priority); 937 return d; 938 } 939 940 /// Returns information about signs placed in a buffer as list of dicts. 941 list_T *get_buffer_signs(buf_T *buf) 942 FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT 943 { 944 list_T *const l = tv_list_alloc(kListLenMayKnow); 945 MarkTreeIter itr[1]; 946 marktree_itr_get(buf->b_marktree, 0, 0, itr); 947 948 while (itr->x) { 949 MTKey mark = marktree_itr_current(itr); 950 if (!mt_end(mark) && mt_decor_sign(mark)) { 951 tv_list_append_dict(l, sign_get_placed_info_dict(mark)); 952 } 953 marktree_itr_next(buf->b_marktree, itr); 954 } 955 956 return l; 957 } 958 959 /// @return information about all the signs placed in a buffer 960 static void sign_get_placed_in_buf(buf_T *buf, linenr_T lnum, int sign_id, const char *group, 961 list_T *retlist) 962 { 963 dict_T *d = tv_dict_alloc(); 964 tv_list_append_dict(retlist, d); 965 966 tv_dict_add_nr(d, S_LEN("bufnr"), buf->b_fnum); 967 968 list_T *l = tv_list_alloc(kListLenMayKnow); 969 tv_dict_add_list(d, S_LEN("signs"), l); 970 971 int64_t ns = group_get_ns(group); 972 if (!buf_has_signs(buf) || ns < 0) { 973 return; 974 } 975 976 MarkTreeIter itr[1]; 977 kvec_t(MTKey) signs = KV_INITIAL_VALUE; 978 marktree_itr_get(buf->b_marktree, lnum ? lnum - 1 : 0, 0, itr); 979 980 while (itr->x) { 981 MTKey mark = marktree_itr_current(itr); 982 if (lnum && mark.pos.row >= lnum) { 983 break; 984 } 985 if (!mt_end(mark) 986 && (ns == UINT32_MAX || ns == mark.ns) 987 && ((lnum == 0 && sign_id == 0) 988 || (sign_id == 0 && lnum == mark.pos.row + 1) 989 || (lnum == 0 && sign_id == (int)mark.id) 990 || (lnum == mark.pos.row + 1 && sign_id == (int)mark.id))) { 991 if (mt_decor_sign(mark)) { 992 kv_push(signs, mark); 993 } 994 } 995 marktree_itr_next(buf->b_marktree, itr); 996 } 997 998 if (kv_size(signs)) { 999 qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_row_cmp); 1000 for (size_t i = 0; i < kv_size(signs); i++) { 1001 tv_list_append_dict(l, sign_get_placed_info_dict(kv_A(signs, i))); 1002 } 1003 kv_destroy(signs); 1004 } 1005 } 1006 1007 /// Get a list of signs placed in buffer 'buf'. If 'num' is non-zero, return the 1008 /// sign placed at the line number. If 'lnum' is zero, return all the signs 1009 /// placed in 'buf'. If 'buf' is NULL, return signs placed in all the buffers. 1010 static void sign_get_placed(buf_T *buf, linenr_T lnum, int id, const char *group, list_T *retlist) 1011 { 1012 if (buf != NULL) { 1013 sign_get_placed_in_buf(buf, lnum, id, group, retlist); 1014 } else { 1015 FOR_ALL_BUFFERS(cbuf) { 1016 if (buf_has_signs(cbuf)) { 1017 sign_get_placed_in_buf(cbuf, 0, id, group, retlist); 1018 } 1019 } 1020 } 1021 } 1022 1023 void free_signs(void) 1024 { 1025 cstr_t name; 1026 kvec_t(cstr_t) names = KV_INITIAL_VALUE; 1027 map_foreach_key(&sign_map, name, { 1028 kv_push(names, name); 1029 }); 1030 for (size_t i = 0; i < kv_size(names); i++) { 1031 sign_undefine_by_name(kv_A(names, i)); 1032 } 1033 kv_destroy(names); 1034 } 1035 1036 static enum { 1037 EXP_SUBCMD, // expand :sign sub-commands 1038 EXP_DEFINE, // expand :sign define {name} args 1039 EXP_PLACE, // expand :sign place {id} args 1040 EXP_LIST, // expand :sign place args 1041 EXP_UNPLACE, // expand :sign unplace" 1042 EXP_SIGN_NAMES, // expand with name of placed signs 1043 EXP_SIGN_GROUPS, // expand with name of placed sign groups 1044 } expand_what; 1045 1046 /// @return the n'th sign name (used for command line completion) 1047 static char *get_nth_sign_name(int idx) 1048 FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT 1049 { 1050 // Complete with name of signs already defined 1051 cstr_t name; 1052 int current_idx = 0; 1053 map_foreach_key(&sign_map, name, { 1054 if (current_idx++ == idx) { 1055 return (char *)name; 1056 } 1057 }); 1058 return NULL; 1059 } 1060 1061 /// @return the n'th sign group name (used for command line completion) 1062 static char *get_nth_sign_group_name(int idx) 1063 { 1064 // Complete with name of sign groups already defined 1065 if (idx < (int)kv_size(sign_ns)) { 1066 return (char *)describe_ns((NS)kv_A(sign_ns, idx), ""); 1067 } 1068 return NULL; 1069 } 1070 1071 /// Function given to ExpandGeneric() to obtain the sign command expansion. 1072 char *get_sign_name(expand_T *xp, int idx) 1073 { 1074 switch (expand_what) { 1075 case EXP_SUBCMD: 1076 return cmds[idx]; 1077 case EXP_DEFINE: { 1078 char *define_arg[] = { "culhl=", "icon=", "linehl=", "numhl=", "text=", "texthl=", 1079 "priority=", NULL }; 1080 return define_arg[idx]; 1081 } 1082 case EXP_PLACE: { 1083 char *place_arg[] = { "line=", "name=", "group=", "priority=", "file=", "buffer=", NULL }; 1084 return place_arg[idx]; 1085 } 1086 case EXP_LIST: { 1087 char *list_arg[] = { "group=", "file=", "buffer=", NULL }; 1088 return list_arg[idx]; 1089 } 1090 case EXP_UNPLACE: { 1091 char *unplace_arg[] = { "group=", "file=", "buffer=", NULL }; 1092 return unplace_arg[idx]; 1093 } 1094 case EXP_SIGN_NAMES: 1095 return get_nth_sign_name(idx); 1096 case EXP_SIGN_GROUPS: 1097 return get_nth_sign_group_name(idx); 1098 default: 1099 return NULL; 1100 } 1101 } 1102 1103 /// Handle command line completion for :sign command. 1104 void set_context_in_sign_cmd(expand_T *xp, char *arg) 1105 { 1106 // Default: expand subcommands. 1107 xp->xp_context = EXPAND_SIGN; 1108 expand_what = EXP_SUBCMD; 1109 xp->xp_pattern = arg; 1110 1111 char *end_subcmd = skiptowhite(arg); 1112 if (*end_subcmd == NUL) { 1113 // expand subcmd name 1114 // :sign {subcmd}<CTRL-D> 1115 return; 1116 } 1117 1118 int cmd_idx = sign_cmd_idx(arg, end_subcmd); 1119 1120 // :sign {subcmd} {subcmd_args} 1121 // | 1122 // begin_subcmd_args 1123 char *begin_subcmd_args = skipwhite(end_subcmd); 1124 1125 // Expand last argument of subcmd. 1126 // 1127 // :sign define {name} {args}... 1128 // | 1129 // p 1130 1131 // Loop until reaching last argument. 1132 char *last; 1133 char *p = begin_subcmd_args; 1134 do { 1135 p = skipwhite(p); 1136 last = p; 1137 p = skiptowhite(p); 1138 } while (*p != NUL); 1139 1140 p = vim_strchr(last, '='); 1141 1142 // :sign define {name} {args}... {last}= 1143 // | | 1144 // last p 1145 if (p == NULL) { 1146 // Expand last argument name (before equal sign). 1147 xp->xp_pattern = last; 1148 switch (cmd_idx) { 1149 case SIGNCMD_DEFINE: 1150 expand_what = EXP_DEFINE; 1151 break; 1152 case SIGNCMD_PLACE: 1153 // List placed signs 1154 if (ascii_isdigit(*begin_subcmd_args)) { 1155 // :sign place {id} {args}... 1156 expand_what = EXP_PLACE; 1157 } else { 1158 // :sign place {args}... 1159 expand_what = EXP_LIST; 1160 } 1161 break; 1162 case SIGNCMD_LIST: 1163 case SIGNCMD_UNDEFINE: 1164 // :sign list <CTRL-D> 1165 // :sign undefine <CTRL-D> 1166 expand_what = EXP_SIGN_NAMES; 1167 break; 1168 case SIGNCMD_JUMP: 1169 case SIGNCMD_UNPLACE: 1170 expand_what = EXP_UNPLACE; 1171 break; 1172 default: 1173 xp->xp_context = EXPAND_NOTHING; 1174 } 1175 } else { 1176 // Expand last argument value (after equal sign). 1177 xp->xp_pattern = p + 1; 1178 switch (cmd_idx) { 1179 case SIGNCMD_DEFINE: 1180 if (strncmp(last, "texthl", 6) == 0 1181 || strncmp(last, "linehl", 6) == 0 1182 || strncmp(last, "culhl", 5) == 0 1183 || strncmp(last, "numhl", 5) == 0) { 1184 xp->xp_context = EXPAND_HIGHLIGHT; 1185 } else if (strncmp(last, "icon", 4) == 0) { 1186 xp->xp_context = EXPAND_FILES; 1187 } else { 1188 xp->xp_context = EXPAND_NOTHING; 1189 } 1190 break; 1191 case SIGNCMD_PLACE: 1192 if (strncmp(last, "name", 4) == 0) { 1193 expand_what = EXP_SIGN_NAMES; 1194 } else if (strncmp(last, "group", 5) == 0) { 1195 expand_what = EXP_SIGN_GROUPS; 1196 } else if (strncmp(last, "file", 4) == 0) { 1197 xp->xp_context = EXPAND_BUFFERS; 1198 } else { 1199 xp->xp_context = EXPAND_NOTHING; 1200 } 1201 break; 1202 case SIGNCMD_UNPLACE: 1203 case SIGNCMD_JUMP: 1204 if (strncmp(last, "group", 5) == 0) { 1205 expand_what = EXP_SIGN_GROUPS; 1206 } else if (strncmp(last, "file", 4) == 0) { 1207 xp->xp_context = EXPAND_BUFFERS; 1208 } else { 1209 xp->xp_context = EXPAND_NOTHING; 1210 } 1211 break; 1212 default: 1213 xp->xp_context = EXPAND_NOTHING; 1214 } 1215 } 1216 } 1217 1218 /// Define a sign using the attributes in 'dict'. Returns 0 on success and -1 on 1219 /// failure. 1220 static int sign_define_from_dict(char *name, dict_T *dict) 1221 { 1222 if (name == NULL) { 1223 name = tv_dict_get_string(dict, "name", false); 1224 if (name == NULL || name[0] == NUL) { 1225 return -1; 1226 } 1227 } 1228 1229 char *icon = NULL; 1230 char *linehl = NULL; 1231 char *text = NULL; 1232 char *texthl = NULL; 1233 char *culhl = NULL; 1234 char *numhl = NULL; 1235 int prio = -1; 1236 1237 if (dict != NULL) { 1238 icon = tv_dict_get_string(dict, "icon", false); 1239 linehl = tv_dict_get_string(dict, "linehl", false); 1240 text = tv_dict_get_string(dict, "text", false); 1241 texthl = tv_dict_get_string(dict, "texthl", false); 1242 culhl = tv_dict_get_string(dict, "culhl", false); 1243 numhl = tv_dict_get_string(dict, "numhl", false); 1244 prio = (int)tv_dict_get_number_def(dict, "priority", -1); 1245 } 1246 1247 return sign_define_by_name(name, icon, text, linehl, texthl, culhl, numhl, prio) - 1; 1248 } 1249 1250 /// Define multiple signs using attributes from list 'l' and store the return 1251 /// values in 'retlist'. 1252 static void sign_define_multiple(list_T *l, list_T *retlist) 1253 { 1254 TV_LIST_ITER_CONST(l, li, { 1255 int retval = -1; 1256 if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) { 1257 retval = sign_define_from_dict(NULL, TV_LIST_ITEM_TV(li)->vval.v_dict); 1258 } else { 1259 emsg(_(e_dictreq)); 1260 } 1261 tv_list_append_number(retlist, retval); 1262 }); 1263 } 1264 1265 /// "sign_define()" function 1266 void f_sign_define(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1267 { 1268 if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) { 1269 // Define multiple signs 1270 tv_list_alloc_ret(rettv, kListLenMayKnow); 1271 1272 sign_define_multiple(argvars[0].vval.v_list, rettv->vval.v_list); 1273 return; 1274 } 1275 1276 // Define a single sign 1277 rettv->vval.v_number = -1; 1278 1279 char *name = (char *)tv_get_string_chk(&argvars[0]); 1280 if (name == NULL) { 1281 return; 1282 } 1283 1284 if (tv_check_for_opt_dict_arg(argvars, 1) == FAIL) { 1285 return; 1286 } 1287 1288 dict_T *d = argvars[1].v_type == VAR_DICT ? argvars[1].vval.v_dict : NULL; 1289 rettv->vval.v_number = sign_define_from_dict(name, d); 1290 } 1291 1292 /// "sign_getdefined()" function 1293 void f_sign_getdefined(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1294 { 1295 tv_list_alloc_ret(rettv, 0); 1296 1297 if (argvars[0].v_type == VAR_UNKNOWN) { 1298 sign_T *sp; 1299 map_foreach_value(&sign_map, sp, { 1300 tv_list_append_dict(rettv->vval.v_list, sign_get_info_dict(sp)); 1301 }); 1302 } else { 1303 sign_T *sp = pmap_get(cstr_t)(&sign_map, tv_get_string(&argvars[0])); 1304 if (sp != NULL) { 1305 tv_list_append_dict(rettv->vval.v_list, sign_get_info_dict(sp)); 1306 } 1307 } 1308 } 1309 1310 /// "sign_getplaced()" function 1311 void f_sign_getplaced(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1312 { 1313 buf_T *buf = NULL; 1314 linenr_T lnum = 0; 1315 int sign_id = 0; 1316 const char *group = NULL; 1317 bool notanum = false; 1318 1319 tv_list_alloc_ret(rettv, 0); 1320 1321 if (argvars[0].v_type != VAR_UNKNOWN) { 1322 // get signs placed in the specified buffer 1323 buf = get_buf_arg(&argvars[0]); 1324 if (buf == NULL) { 1325 return; 1326 } 1327 1328 if (argvars[1].v_type != VAR_UNKNOWN) { 1329 if (tv_check_for_nonnull_dict_arg(argvars, 1) == FAIL) { 1330 return; 1331 } 1332 dictitem_T *di; 1333 dict_T *dict = argvars[1].vval.v_dict; 1334 if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) { 1335 // get signs placed at this line 1336 lnum = tv_get_lnum(&di->di_tv); 1337 if (lnum <= 0) { 1338 return; 1339 } 1340 } 1341 if ((di = tv_dict_find(dict, "id", -1)) != NULL) { 1342 // get sign placed with this identifier 1343 sign_id = (int)tv_get_number_chk(&di->di_tv, ¬anum); 1344 if (notanum) { 1345 return; 1346 } 1347 } 1348 if ((di = tv_dict_find(dict, "group", -1)) != NULL) { 1349 group = tv_get_string_chk(&di->di_tv); 1350 if (group == NULL) { 1351 return; 1352 } 1353 if (*group == NUL) { // empty string means global group 1354 group = NULL; 1355 } 1356 } 1357 } 1358 } 1359 1360 sign_get_placed(buf, lnum, sign_id, group, rettv->vval.v_list); 1361 } 1362 1363 /// "sign_jump()" function 1364 void f_sign_jump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1365 { 1366 rettv->vval.v_number = -1; 1367 1368 // Sign identifier 1369 bool notanum = false; 1370 int id = (int)tv_get_number_chk(&argvars[0], ¬anum); 1371 if (notanum) { 1372 return; 1373 } 1374 if (id <= 0) { 1375 emsg(_(e_invarg)); 1376 return; 1377 } 1378 1379 // Sign group 1380 char *group = (char *)tv_get_string_chk(&argvars[1]); 1381 if (group == NULL) { 1382 return; 1383 } 1384 if (group[0] == NUL) { 1385 group = NULL; 1386 } 1387 1388 // Buffer to place the sign 1389 buf_T *buf = get_buf_arg(&argvars[2]); 1390 if (buf == NULL) { 1391 return; 1392 } 1393 1394 rettv->vval.v_number = sign_jump(id, group, buf); 1395 } 1396 1397 /// Place a new sign using the values specified in dict 'dict'. Returns the sign 1398 /// identifier if successfully placed, otherwise returns -1. 1399 static int sign_place_from_dict(typval_T *id_tv, typval_T *group_tv, typval_T *name_tv, 1400 typval_T *buf_tv, dict_T *dict) 1401 { 1402 dictitem_T *di; 1403 1404 int id = 0; 1405 bool notanum = false; 1406 if (id_tv == NULL) { 1407 di = tv_dict_find(dict, "id", -1); 1408 if (di != NULL) { 1409 id_tv = &di->di_tv; 1410 } 1411 } 1412 if (id_tv != NULL) { 1413 id = (int)tv_get_number_chk(id_tv, ¬anum); 1414 if (notanum) { 1415 return -1; 1416 } 1417 if (id < 0) { 1418 emsg(_(e_invarg)); 1419 return -1; 1420 } 1421 } 1422 1423 char *group = NULL; 1424 if (group_tv == NULL) { 1425 di = tv_dict_find(dict, "group", -1); 1426 if (di != NULL) { 1427 group_tv = &di->di_tv; 1428 } 1429 } 1430 if (group_tv != NULL) { 1431 group = (char *)tv_get_string_chk(group_tv); 1432 if (group == NULL) { 1433 return -1; 1434 } 1435 if (group[0] == NUL) { 1436 group = NULL; 1437 } 1438 } 1439 1440 char *name = NULL; 1441 if (name_tv == NULL) { 1442 di = tv_dict_find(dict, "name", -1); 1443 if (di != NULL) { 1444 name_tv = &di->di_tv; 1445 } 1446 } 1447 if (name_tv == NULL) { 1448 return -1; 1449 } 1450 name = (char *)tv_get_string_chk(name_tv); 1451 if (name == NULL) { 1452 return -1; 1453 } 1454 1455 if (buf_tv == NULL) { 1456 di = tv_dict_find(dict, "buffer", -1); 1457 if (di != NULL) { 1458 buf_tv = &di->di_tv; 1459 } 1460 } 1461 if (buf_tv == NULL) { 1462 return -1; 1463 } 1464 buf_T *buf = get_buf_arg(buf_tv); 1465 if (buf == NULL) { 1466 return -1; 1467 } 1468 1469 linenr_T lnum = 0; 1470 di = tv_dict_find(dict, "lnum", -1); 1471 if (di != NULL) { 1472 lnum = tv_get_lnum(&di->di_tv); 1473 if (lnum <= 0) { 1474 emsg(_(e_invarg)); 1475 return -1; 1476 } 1477 } 1478 1479 int prio = -1; 1480 di = tv_dict_find(dict, "priority", -1); 1481 if (di != NULL) { 1482 prio = (int)tv_get_number_chk(&di->di_tv, ¬anum); 1483 if (notanum) { 1484 return -1; 1485 } 1486 } 1487 1488 uint32_t uid = (uint32_t)id; 1489 if (sign_place(&uid, group, name, buf, lnum, prio) == OK) { 1490 return (int)uid; 1491 } 1492 1493 return -1; 1494 } 1495 1496 /// "sign_place()" function 1497 void f_sign_place(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1498 { 1499 dict_T *dict = NULL; 1500 1501 rettv->vval.v_number = -1; 1502 1503 if (argvars[4].v_type != VAR_UNKNOWN) { 1504 if (tv_check_for_nonnull_dict_arg(argvars, 4) == FAIL) { 1505 return; 1506 } 1507 dict = argvars[4].vval.v_dict; 1508 } 1509 1510 rettv->vval.v_number = sign_place_from_dict(&argvars[0], &argvars[1], 1511 &argvars[2], &argvars[3], dict); 1512 } 1513 1514 /// "sign_placelist()" function. Place multiple signs. 1515 void f_sign_placelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1516 { 1517 tv_list_alloc_ret(rettv, kListLenMayKnow); 1518 1519 if (argvars[0].v_type != VAR_LIST) { 1520 emsg(_(e_listreq)); 1521 return; 1522 } 1523 1524 // Process the List of sign attributes 1525 TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { 1526 int sign_id = -1; 1527 if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) { 1528 sign_id = sign_place_from_dict(NULL, NULL, NULL, NULL, TV_LIST_ITEM_TV(li)->vval.v_dict); 1529 } else { 1530 emsg(_(e_dictreq)); 1531 } 1532 tv_list_append_number(rettv->vval.v_list, sign_id); 1533 }); 1534 } 1535 1536 /// Undefine multiple signs 1537 static void sign_undefine_multiple(list_T *l, list_T *retlist) 1538 { 1539 TV_LIST_ITER_CONST(l, li, { 1540 int retval = -1; 1541 char *name = (char *)tv_get_string_chk(TV_LIST_ITEM_TV(li)); 1542 if (name != NULL && (sign_undefine_by_name(name) == OK)) { 1543 retval = 0; 1544 } 1545 tv_list_append_number(retlist, retval); 1546 }); 1547 } 1548 1549 /// "sign_undefine()" function 1550 void f_sign_undefine(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1551 { 1552 if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) { 1553 // Undefine multiple signs 1554 tv_list_alloc_ret(rettv, kListLenMayKnow); 1555 1556 sign_undefine_multiple(argvars[0].vval.v_list, rettv->vval.v_list); 1557 return; 1558 } 1559 1560 rettv->vval.v_number = -1; 1561 1562 if (argvars[0].v_type == VAR_UNKNOWN) { 1563 // Free all the signs 1564 free_signs(); 1565 rettv->vval.v_number = 0; 1566 } else { 1567 // Free only the specified sign 1568 const char *name = tv_get_string_chk(&argvars[0]); 1569 if (name == NULL) { 1570 return; 1571 } 1572 1573 if (sign_undefine_by_name(name) == OK) { 1574 rettv->vval.v_number = 0; 1575 } 1576 } 1577 } 1578 1579 /// Unplace the sign with attributes specified in 'dict'. Returns 0 on success 1580 /// and -1 on failure. 1581 static int sign_unplace_from_dict(typval_T *group_tv, dict_T *dict) 1582 { 1583 dictitem_T *di; 1584 int id = 0; 1585 buf_T *buf = NULL; 1586 char *group = (group_tv != NULL) ? (char *)tv_get_string(group_tv) 1587 : tv_dict_get_string(dict, "group", false); 1588 if (group != NULL && group[0] == NUL) { 1589 group = NULL; 1590 } 1591 1592 if (dict != NULL) { 1593 if ((di = tv_dict_find(dict, "buffer", -1)) != NULL) { 1594 buf = get_buf_arg(&di->di_tv); 1595 if (buf == NULL) { 1596 return -1; 1597 } 1598 } 1599 if (tv_dict_find(dict, "id", -1) != NULL) { 1600 id = (int)tv_dict_get_number(dict, "id"); 1601 if (id <= 0) { 1602 emsg(_(e_invarg)); 1603 return -1; 1604 } 1605 } 1606 } 1607 1608 return sign_unplace(buf, id, group, 0) - 1; 1609 } 1610 1611 /// "sign_unplace()" function 1612 void f_sign_unplace(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1613 { 1614 dict_T *dict = NULL; 1615 1616 rettv->vval.v_number = -1; 1617 1618 if (tv_check_for_string_arg(argvars, 0) == FAIL 1619 || tv_check_for_opt_dict_arg(argvars, 1) == FAIL) { 1620 return; 1621 } 1622 1623 if (argvars[1].v_type != VAR_UNKNOWN) { 1624 dict = argvars[1].vval.v_dict; 1625 } 1626 1627 rettv->vval.v_number = sign_unplace_from_dict(&argvars[0], dict); 1628 } 1629 1630 /// "sign_unplacelist()" function 1631 void f_sign_unplacelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1632 { 1633 tv_list_alloc_ret(rettv, kListLenMayKnow); 1634 1635 if (argvars[0].v_type != VAR_LIST) { 1636 emsg(_(e_listreq)); 1637 return; 1638 } 1639 1640 TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { 1641 int retval = -1; 1642 if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) { 1643 retval = sign_unplace_from_dict(NULL, TV_LIST_ITEM_TV(li)->vval.v_dict); 1644 } else { 1645 emsg(_(e_dictreq)); 1646 } 1647 tv_list_append_number(rettv->vval.v_list, retval); 1648 }); 1649 }