neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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, &notanum);
   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], &notanum);
   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, &notanum);
   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, &notanum);
   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 }