/* vi:set ts=8 sts=4 sw=4 noet:
 *
 * VIM - Vi IMproved	by Bram Moolenaar
 *
 * Do ":help uganda"  in Vim to read copying and usage conditions.
 * Do ":help credits" in Vim to see a list of people who contributed.
 * See README.txt for an overview of the Vim source code.
 */

/*
 * sign.c: functions for managing signs
 */

#include "vim.h"

#if defined(FEAT_SIGNS) || defined(PROTO)

/*
 * Struct to hold the sign properties.
 */
typedef struct sign sign_T;

struct sign
{
  sign_T *sn_next; // next sign in list
  int sn_typenr;   // type number of sign
  char_u *sn_name; // name of sign
  char_u *sn_icon; // name of pixmap
#ifdef FEAT_SIGN_ICONS
  void *sn_image; // icon image
#endif
  char_u *sn_text; // text used instead of pixmap
  int sn_line_hl;  // highlight ID for line
  int sn_text_hl;  // highlight ID for text
};

static sign_T *first_sign = NULL;
static int next_sign_typenr = 1;

static void sign_list_defined(sign_T *sp);
static void sign_undefine(sign_T *sp, sign_T *sp_prev);

static char *cmds[] = {
    "define",
#define SIGNCMD_DEFINE 0
    "undefine",
#define SIGNCMD_UNDEFINE 1
    "list",
#define SIGNCMD_LIST 2
    "place",
#define SIGNCMD_PLACE 3
    "unplace",
#define SIGNCMD_UNPLACE 4
    "jump",
#define SIGNCMD_JUMP 5
    NULL
#define SIGNCMD_LAST 6
};

static hashtab_T sg_table;   // sign group (signgroup_T) hashtable
static int next_sign_id = 1; // next sign id in the global group

/*
 * Initialize data needed for managing signs
 */
void init_signs(void)
{
  hash_init(&sg_table); // sign group hash table
}

/*
 * A new sign in group 'groupname' is added. If the group is not present,
 * create it. Otherwise reference the group.
 */
static signgroup_T *
sign_group_ref(char_u *groupname)
{
  hash_T hash;
  hashitem_T *hi;
  signgroup_T *group;

  hash = hash_hash(groupname);
  hi = hash_lookup(&sg_table, groupname, hash);
  if (HASHITEM_EMPTY(hi))
  {
    // new group
    group = alloc(sizeof(signgroup_T) + STRLEN(groupname));
    if (group == NULL)
      return NULL;
    STRCPY(group->sg_name, groupname);
    group->refcount = 1;
    group->next_sign_id = 1;
    hash_add_item(&sg_table, hi, group->sg_name, hash);
  }
  else
  {
    // existing group
    group = HI2SG(hi);
    group->refcount++;
  }

  return group;
}

/*
 * A sign in group 'groupname' is removed. If all the signs in this group are
 * removed, then remove the group.
 */
static void
sign_group_unref(char_u *groupname)
{
  hashitem_T *hi;
  signgroup_T *group;

  hi = hash_find(&sg_table, groupname);
  if (!HASHITEM_EMPTY(hi))
  {
    group = HI2SG(hi);
    group->refcount--;
    if (group->refcount == 0)
    {
      // All the signs in this group are removed
      hash_remove(&sg_table, hi);
      vim_free(group);
    }
  }
}

/*
 * Returns TRUE if 'sign' is in 'group'.
 * A sign can either be in the global group (sign->group == NULL)
 * or in a named group. If 'group' is '*', then the sign is part of the group.
 */
static int
sign_in_group(signlist_T *sign, char_u *group)
{
  return ((group != NULL && STRCMP(group, "*") == 0) || (group == NULL && sign->group == NULL) || (group != NULL && sign->group != NULL && STRCMP(group, sign->group->sg_name) == 0));
}

/*
 * Get the next free sign identifier in the specified group
 */
static int
sign_group_get_next_signid(buf_T *buf, char_u *groupname)
{
  int id = 1;
  signgroup_T *group = NULL;
  signlist_T *sign;
  hashitem_T *hi;
  int found = FALSE;

  if (groupname != NULL)
  {
    hi = hash_find(&sg_table, groupname);
    if (HASHITEM_EMPTY(hi))
      return id;
    group = HI2SG(hi);
  }

  // Search for the next usable sign identifier
  while (!found)
  {
    if (group == NULL)
      id = next_sign_id++; // global group
    else
      id = group->next_sign_id++;

    // Check whether this sign is already placed in the buffer
    found = TRUE;
    FOR_ALL_SIGNS_IN_BUF(buf, sign)
    {
      if (id == sign->id && sign_in_group(sign, groupname))
      {
        found = FALSE; // sign identifier is in use
        break;
      }
    }
  }

  return id;
}

/*
 * Insert a new sign into the signlist for buffer 'buf' between the 'prev' and
 * 'next' signs.
 */
static void
insert_sign(
    buf_T *buf,       // buffer to store sign in
    signlist_T *prev, // previous sign entry
    signlist_T *next, // next sign entry
    int id,           // sign ID
    char_u *group,    // sign group; NULL for global group
    int prio,         // sign priority
    linenr_T lnum,    // line number which gets the mark
    int typenr)       // typenr of sign we are adding
{
  signlist_T *newsign;

  newsign = lalloc_id(sizeof(signlist_T), FALSE, aid_insert_sign);
  if (newsign != NULL)
  {
    newsign->id = id;
    newsign->lnum = lnum;
    newsign->typenr = typenr;
    if (group != NULL)
    {
      newsign->group = sign_group_ref(group);
      if (newsign->group == NULL)
      {
        vim_free(newsign);
        return;
      }
    }
    else
      newsign->group = NULL;
    newsign->priority = prio;
    newsign->next = next;
    newsign->prev = prev;
    if (next != NULL)
      next->prev = newsign;

    if (prev == NULL)
    {
      // When adding first sign need to redraw the windows to create the
      // column for signs.
      if (buf->b_signlist == NULL)
      {
        redraw_buf_later(buf, NOT_VALID);
        changed_cline_bef_curs();
      }

      // first sign in signlist
      buf->b_signlist = newsign;
    }
    else
      prev->next = newsign;
  }
}

/*
 * Insert a new sign sorted by line number and sign priority.
 */
static void
insert_sign_by_lnum_prio(
    buf_T *buf,       // buffer to store sign in
    signlist_T *prev, // previous sign entry
    int id,           // sign ID
    char_u *group,    // sign group; NULL for global group
    int prio,         // sign priority
    linenr_T lnum,    // line number which gets the mark
    int typenr)       // typenr of sign we are adding
{
  signlist_T *sign;

  // keep signs sorted by lnum and by priority: insert new sign at
  // the proper position in the list for this lnum.
  while (prev != NULL && prev->lnum == lnum && prev->priority <= prio)
    prev = prev->prev;
  if (prev == NULL)
    sign = buf->b_signlist;
  else
    sign = prev->next;

  insert_sign(buf, prev, sign, id, group, prio, lnum, typenr);
}

/*
 * Get the name of a sign by its typenr.
 */
static char_u *
sign_typenr2name(int typenr)
{
  sign_T *sp;

  for (sp = first_sign; sp != NULL; sp = sp->sn_next)
    if (sp->sn_typenr == typenr)
      return sp->sn_name;
  return (char_u *)_("[Deleted]");
}

/*
 * Return information about a sign in a Dict
 */
static dict_T *
sign_get_info(signlist_T *sign)
{
  dict_T *d;

  if ((d = dict_alloc_id(aid_sign_getinfo)) == NULL)
    return NULL;
  dict_add_number(d, "id", sign->id);
  dict_add_string(d, "group", (sign->group == NULL) ? (char_u *)"" : sign->group->sg_name);
  dict_add_number(d, "lnum", sign->lnum);
  dict_add_string(d, "name", sign_typenr2name(sign->typenr));
  dict_add_number(d, "priority", sign->priority);

  return d;
}

/*
 * Add the sign into the signlist. Find the right spot to do it though.
 */
static void
buf_addsign(
    buf_T *buf,        // buffer to store sign in
    int id,            // sign ID
    char_u *groupname, // sign group
    int prio,          // sign priority
    linenr_T lnum,     // line number which gets the mark
    int typenr)        // typenr of sign we are adding
{
  signlist_T *sign; // a sign in the signlist
  signlist_T *prev; // the previous sign

  prev = NULL;
  FOR_ALL_SIGNS_IN_BUF(buf, sign)
  {
    if (lnum == sign->lnum && id == sign->id && sign_in_group(sign, groupname))
    {
      // Update an existing sign
      sign->typenr = typenr;
      return;
    }
    else if (lnum < sign->lnum)
    {
      insert_sign_by_lnum_prio(buf, prev, id, groupname, prio,
                               lnum, typenr);
      return;
    }
    prev = sign;
  }

  insert_sign_by_lnum_prio(buf, prev, id, groupname, prio, lnum, typenr);
  return;
}

/*
 * For an existing, placed sign "markId" change the type to "typenr".
 * Returns the line number of the sign, or zero if the sign is not found.
 */
static linenr_T
buf_change_sign_type(
    buf_T *buf,    // buffer to store sign in
    int markId,    // sign ID
    char_u *group, // sign group
    int typenr)    // typenr of sign we are adding
{
  signlist_T *sign; // a sign in the signlist

  FOR_ALL_SIGNS_IN_BUF(buf, sign)
  {
    if (sign->id == markId && sign_in_group(sign, group))
    {
      sign->typenr = typenr;
      return sign->lnum;
    }
  }

  return (linenr_T)0;
}

/*
 * Return the type number of the sign at line number 'lnum' in buffer 'buf'
 * which has the attribute specified by 'type'. Returns 0 if a sign is not
 * found at the line number or it doesn't have the specified attribute.
 */
int buf_getsigntype(
    buf_T *buf,
    linenr_T lnum,
    int type) // SIGN_ICON, SIGN_TEXT, SIGN_ANY, SIGN_LINEHL
{
  signlist_T *sign; // a sign in a b_signlist

  FOR_ALL_SIGNS_IN_BUF(buf, sign)
  if (sign->lnum == lnum && (type == SIGN_ANY
#ifdef FEAT_SIGN_ICONS
                             || (type == SIGN_ICON && sign_get_image(sign->typenr) != NULL)
#endif
                             || (type == SIGN_TEXT && sign_get_text(sign->typenr) != NULL) || (type == SIGN_LINEHL && sign_get_attr(sign->typenr, TRUE) != 0)))
    return sign->typenr;
  return 0;
}

/*
 * Delete sign 'id' in group 'group' from buffer 'buf'.
 * If 'id' is zero, then delete all the signs in group 'group'. Otherwise
 * delete only the specified sign.
 * If 'group' is '*', then delete the sign in all the groups. If 'group' is
 * NULL, then delete the sign in the global group. Otherwise delete the sign in
 * the specified group.
 * Returns the line number of the deleted sign. If multiple signs are deleted,
 * then returns the line number of the last sign deleted.
 */
linenr_T
buf_delsign(
    buf_T *buf,      // buffer sign is stored in
    linenr_T atlnum, // sign at this line, 0 - at any line
    int id,          // sign id
    char_u *group)   // sign group
{
  signlist_T **lastp; // pointer to pointer to current sign
  signlist_T *sign;   // a sign in a b_signlist
  signlist_T *next;   // the next sign in a b_signlist
  linenr_T lnum;      // line number whose sign was deleted

  lastp = &buf->b_signlist;
  lnum = 0;
  for (sign = buf->b_signlist; sign != NULL; sign = next)
  {
    next = sign->next;
    if ((id == 0 || sign->id == id) && (atlnum == 0 || sign->lnum == atlnum) && sign_in_group(sign, group))

    {
      *lastp = next;
      if (next != NULL)
        next->prev = sign->prev;
      lnum = sign->lnum;
      if (sign->group != NULL)
        sign_group_unref(sign->group->sg_name);
      vim_free(sign);
      redraw_buf_line_later(buf, lnum);

      // Check whether only one sign needs to be deleted
      // If deleting a sign with a specific identifier in a particular
      // group or deleting any sign at a particular line number, delete
      // only one sign.
      if (group == NULL || (*group != '*' && id != 0) || (*group == '*' && atlnum != 0))
        break;
    }
    else
      lastp = &sign->next;
  }

  // When deleting the last sign the cursor position may change, because the
  // sign columns no longer shows.  And the 'signcolumn' may be hidden.
  if (buf->b_signlist == NULL)
  {
    redraw_buf_later(buf, NOT_VALID);
    changed_cline_bef_curs();
  }

  return lnum;
}

/*
 * Find the line number of the sign with the requested id in group 'group'. If
 * the sign does not exist, return 0 as the line number. This will still let
 * the correct file get loaded.
 */
int buf_findsign(
    buf_T *buf,    // buffer to store sign in
    int id,        // sign ID
    char_u *group) // sign group
{
  signlist_T *sign; // a sign in the signlist

  FOR_ALL_SIGNS_IN_BUF(buf, sign)
  if (sign->id == id && sign_in_group(sign, group))
    return sign->lnum;

  return 0;
}

/*
 * Return the sign at line 'lnum' in buffer 'buf'. Returns NULL if a sign is
 * not found at the line. If 'groupname' is NULL, searches in the global group.
 */
static signlist_T *
buf_getsign_at_line(
    buf_T *buf,        // buffer whose sign we are searching for
    linenr_T lnum,     // line number of sign
    char_u *groupname) // sign group name
{
  signlist_T *sign; // a sign in the signlist

  FOR_ALL_SIGNS_IN_BUF(buf, sign)
  if (sign->lnum == lnum && sign_in_group(sign, groupname))
    return sign;

  return NULL;
}

/*
 * Return the identifier of the sign at line number 'lnum' in buffer 'buf'.
 */
int buf_findsign_id(
    buf_T *buf,        // buffer whose sign we are searching for
    linenr_T lnum,     // line number of sign
    char_u *groupname) // sign group name
{
  signlist_T *sign; // a sign in the signlist

  sign = buf_getsign_at_line(buf, lnum, groupname);
  if (sign != NULL)
    return sign->id;

  return 0;
}

/*
 * Delete signs in group 'group' in buffer "buf". If 'group' is '*', then
 * delete all the signs.
 */
void buf_delete_signs(buf_T *buf, char_u *group)
{
  signlist_T *sign;
  signlist_T **lastp; // pointer to pointer to current sign
  signlist_T *next;

  // When deleting the last sign need to redraw the windows to remove the
  // sign column. Not when curwin is NULL (this means we're exiting).
  if (buf->b_signlist != NULL && curwin != NULL)
  {
    redraw_buf_later(buf, NOT_VALID);
    changed_cline_bef_curs();
  }

  lastp = &buf->b_signlist;
  for (sign = buf->b_signlist; sign != NULL; sign = next)
  {
    next = sign->next;
    if (sign_in_group(sign, group))
    {
      *lastp = next;
      if (next != NULL)
        next->prev = sign->prev;
      if (sign->group != NULL)
        sign_group_unref(sign->group->sg_name);
      vim_free(sign);
    }
    else
      lastp = &sign->next;
  }
}

/*
 * List placed signs for "rbuf".  If "rbuf" is NULL do it for all buffers.
 */
static void
sign_list_placed(buf_T *rbuf, char_u *sign_group)
{
  buf_T *buf;
  signlist_T *sign;
  char lbuf[MSG_BUF_LEN];
  char group[MSG_BUF_LEN];

  msg_puts_title(_("\n--- Signs ---"));
  msg_putchar('\n');
  if (rbuf == NULL)
    buf = firstbuf;
  else
    buf = rbuf;
  while (buf != NULL && !got_int)
  {
    if (buf->b_signlist != NULL)
    {
      vim_snprintf(lbuf, MSG_BUF_LEN, _("Signs for %s:"), buf->b_fname);
      msg_puts_attr(lbuf, HL_ATTR(HLF_D));
      msg_putchar('\n');
    }
    FOR_ALL_SIGNS_IN_BUF(buf, sign)
    {
      if (got_int)
        break;
      if (!sign_in_group(sign, sign_group))
        continue;
      if (sign->group != NULL)
        vim_snprintf(group, MSG_BUF_LEN, _("  group=%s"),
                     sign->group->sg_name);
      else
        group[0] = '\0';
      vim_snprintf(lbuf, MSG_BUF_LEN,
                   _("    line=%ld  id=%d%s  name=%s  priority=%d"),
                   (long)sign->lnum, sign->id, group,
                   sign_typenr2name(sign->typenr), sign->priority);
      msg_puts(lbuf);
      msg_putchar('\n');
    }
    if (rbuf != NULL)
      break;
    buf = buf->b_next;
  }
}

/*
 * Adjust a placed sign for inserted/deleted lines.
 */
void sign_mark_adjust(
    linenr_T line1,
    linenr_T line2,
    long amount,
    long amount_after)
{
  signlist_T *sign; // a sign in a b_signlist
  linenr_T new_lnum;

  FOR_ALL_SIGNS_IN_BUF(curbuf, sign)
  {
    // Ignore changes to lines after the sign
    if (sign->lnum < line1)
      continue;
    new_lnum = sign->lnum;
    if (sign->lnum >= line1 && sign->lnum <= line2)
    {
      if (amount != MAXLNUM)
        new_lnum += amount;
    }
    else if (sign->lnum > line2)
      // Lines inserted or deleted before the sign
      new_lnum += amount_after;

    // If the new sign line number is past the last line in the buffer,
    // then don't adjust the line number. Otherwise, it will always be past
    // the last line and will not be visible.
    if (new_lnum <= curbuf->b_ml.ml_line_count)
      sign->lnum = new_lnum;
  }
}

/*
 * Find index of a ":sign" subcmd from its name.
 * "*end_cmd" must be writable.
 */
static int
sign_cmd_idx(
    char_u *begin_cmd, // begin of sign subcmd
    char_u *end_cmd)   // just after sign subcmd
{
  int idx;
  char save = *end_cmd;

  *end_cmd = NUL;
  for (idx = 0;; ++idx)
    if (cmds[idx] == NULL || STRCMP(begin_cmd, cmds[idx]) == 0)
      break;
  *end_cmd = save;
  return idx;
}

/*
 * Find a sign by name. Also returns pointer to the previous sign.
 */
static sign_T *
sign_find(char_u *name, sign_T **sp_prev)
{
  sign_T *sp;

  if (sp_prev != NULL)
    *sp_prev = NULL;
  for (sp = first_sign; sp != NULL; sp = sp->sn_next)
  {
    if (STRCMP(sp->sn_name, name) == 0)
      break;
    if (sp_prev != NULL)
      *sp_prev = sp;
  }

  return sp;
}

/*
 * Allocate a new sign
 */
static sign_T *
alloc_new_sign(char_u *name)
{
  sign_T *sp;
  sign_T *lp;
  int start = next_sign_typenr;

  // Allocate a new sign.
  sp = alloc_clear_id(sizeof(sign_T), aid_sign_define_by_name);
  if (sp == NULL)
    return NULL;

  // Check that next_sign_typenr is not already being used.
  // This only happens after wrapping around.  Hopefully
  // another one got deleted and we can use its number.
  for (lp = first_sign; lp != NULL;)
  {
    if (lp->sn_typenr == next_sign_typenr)
    {
      ++next_sign_typenr;
      if (next_sign_typenr == MAX_TYPENR)
        next_sign_typenr = 1;
      if (next_sign_typenr == start)
      {
        vim_free(sp);
        emsg(_("E612: Too many signs defined"));
        return NULL;
      }
      lp = first_sign; // start all over
      continue;
    }
    lp = lp->sn_next;
  }

  sp->sn_typenr = next_sign_typenr;
  if (++next_sign_typenr == MAX_TYPENR)
    next_sign_typenr = 1; // wrap around

  sp->sn_name = vim_strsave(name);
  if (sp->sn_name == NULL) // out of memory
  {
    vim_free(sp);
    return NULL;
  }

  return sp;
}

/*
 * Initialize the icon information for a new sign
 */
static void
sign_define_init_icon(sign_T *sp, char_u *icon)
{
  vim_free(sp->sn_icon);
  sp->sn_icon = vim_strsave(icon);
  backslash_halve(sp->sn_icon);
#ifdef FEAT_SIGN_ICONS
  if (gui.in_use)
  {
    if (sp->sn_image != NULL)
      gui_mch_destroy_sign(sp->sn_image);
    sp->sn_image = gui_mch_register_sign(sp->sn_icon);
  }
#endif
}

/*
 * Initialize the text for a new sign
 */
static int
sign_define_init_text(sign_T *sp, char_u *text)
{
  char_u *s;
  char_u *endp;
  int cells;
  int len;

  endp = text + (int)STRLEN(text);

  // Remove backslashes so that it is possible to use a space.
  for (s = text; s + 1 < endp; ++s)
    if (*s == '\\')
    {
      STRMOVE(s, s + 1);
      --endp;
    }

  // Count cells and check for non-printable chars
  if (has_mbyte)
  {
    cells = 0;
    for (s = text; s < endp; s += (*mb_ptr2len)(s))
    {
      if (!vim_isprintc((*mb_ptr2char)(s)))
        break;
      cells += (*mb_ptr2cells)(s);
    }
  }
  else
  {
    for (s = text; s < endp; ++s)
      if (!vim_isprintc(*s))
        break;
    cells = (int)(s - text);
  }

  // Currently sign text must be one or two display cells
  if (s != endp || cells < 1 || cells > 2)
  {
    semsg(_("E239: Invalid sign text: %s"), text);
    return FAIL;
  }

  vim_free(sp->sn_text);
  // Allocate one byte more if we need to pad up
  // with a space.
  len = (int)(endp - text + ((cells == 1) ? 1 : 0));
  sp->sn_text = vim_strnsave(text, len);

  // For single character sign text, pad with a space.
  if (sp->sn_text != NULL && cells == 1)
    STRCPY(sp->sn_text + len - 1, " ");

  return OK;
}

/*
 * Define a new sign or update an existing sign
 */
int sign_define_by_name(
    char_u *name,
    char_u *icon,
    char_u *linehl,
    char_u *text,
    char_u *texthl)
{
  sign_T *sp_prev;
  sign_T *sp;

  sp = sign_find(name, &sp_prev);
  if (sp == NULL)
  {
    sp = alloc_new_sign(name);
    if (sp == NULL)
      return FAIL;

    // add the new sign to the list of signs
    if (sp_prev == NULL)
      first_sign = sp;
    else
      sp_prev->sn_next = sp;
  }

  // set values for a defined sign.
  if (icon != NULL)
    sign_define_init_icon(sp, icon);

  if (text != NULL && (sign_define_init_text(sp, text) == FAIL))
    return FAIL;

  if (linehl != NULL)
    sp->sn_line_hl = syn_check_group(linehl, (int)STRLEN(linehl));

  if (texthl != NULL)
    sp->sn_text_hl = syn_check_group(texthl, (int)STRLEN(texthl));

  return OK;
}

/*
 * Free the sign specified by 'name'.
 */
int sign_undefine_by_name(char_u *name)
{
  sign_T *sp_prev;
  sign_T *sp;

  sp = sign_find(name, &sp_prev);
  if (sp == NULL)
  {
    semsg(_("E155: Unknown sign: %s"), name);
    return FAIL;
  }
  sign_undefine(sp, sp_prev);

  return OK;
}

/*
 * List the signs matching 'name'
 */
static void
sign_list_by_name(char_u *name)
{
  sign_T *sp;

  sp = sign_find(name, NULL);
  if (sp != NULL)
    sign_list_defined(sp);
  else
    semsg(_("E155: Unknown sign: %s"), name);
}

/*
 * Place a sign at the specified file location or update a sign.
 */
int sign_place(
    int *sign_id,
    char_u *sign_group,
    char_u *sign_name,
    buf_T *buf,
    linenr_T lnum,
    int prio)
{
  sign_T *sp;

  // Check for reserved character '*' in group name
  if (sign_group != NULL && (*sign_group == '*' || *sign_group == '\0'))
    return FAIL;

  for (sp = first_sign; sp != NULL; sp = sp->sn_next)
    if (STRCMP(sp->sn_name, sign_name) == 0)
      break;
  if (sp == NULL)
  {
    semsg(_("E155: Unknown sign: %s"), sign_name);
    return FAIL;
  }
  if (*sign_id == 0)
    *sign_id = sign_group_get_next_signid(buf, sign_group);

  if (lnum > 0)
    // ":sign place {id} line={lnum} name={name} file={fname}":
    // place a sign
    buf_addsign(buf, *sign_id, sign_group, prio, lnum, sp->sn_typenr);
  else
    // ":sign place {id} file={fname}": change sign type
    lnum = buf_change_sign_type(buf, *sign_id, sign_group, sp->sn_typenr);
  if (lnum > 0)
    redraw_buf_line_later(buf, lnum);
  else
  {
    semsg(_("E885: Not possible to change sign %s"), sign_name);
    return FAIL;
  }

  return OK;
}

/*
 * Unplace the specified sign
 */
int sign_unplace(int sign_id, char_u *sign_group, buf_T *buf, linenr_T atlnum)
{
  if (buf->b_signlist == NULL) // No signs in the buffer
    return OK;

  if (sign_id == 0)
  {
    // Delete all the signs in the specified buffer
    redraw_buf_later(buf, NOT_VALID);
    buf_delete_signs(buf, sign_group);
  }
  else
  {
    linenr_T lnum;

    // Delete only the specified signs
    lnum = buf_delsign(buf, atlnum, sign_id, sign_group);
    if (lnum == 0)
      return FAIL;
  }

  return OK;
}

/*
 * Unplace the sign at the current cursor line.
 */
static void
sign_unplace_at_cursor(char_u *groupname)
{
  int id = -1;

  id = buf_findsign_id(curwin->w_buffer, curwin->w_cursor.lnum, groupname);
  if (id > 0)
    sign_unplace(id, groupname, curwin->w_buffer, curwin->w_cursor.lnum);
  else
    emsg(_("E159: Missing sign number"));
}

/*
 * Jump to a sign.
 */
linenr_T
sign_jump(int sign_id, char_u *sign_group, buf_T *buf)
{
  linenr_T lnum;

  if ((lnum = buf_findsign(buf, sign_id, sign_group)) <= 0)
  {
    semsg(_("E157: Invalid sign ID: %d"), sign_id);
    return -1;
  }

  // goto a sign ...
  if (buf_jump_open_win(buf) != NULL)
  { // ... in a current window
    curwin->w_cursor.lnum = lnum;
    check_cursor_lnum();
    beginline(BL_WHITE);
  }
  else
  { // ... not currently in a window
    char_u *cmd;

    if (buf->b_fname == NULL)
    {
      emsg(_("E934: Cannot jump to a buffer that does not have a name"));
      return -1;
    }
    cmd = alloc(STRLEN(buf->b_fname) + 25);
    if (cmd == NULL)
      return -1;
    sprintf((char *)cmd, "e +%ld %s", (long)lnum, buf->b_fname);
    do_cmdline_cmd(cmd);
    vim_free(cmd);
  }
#ifdef FEAT_FOLDING
  foldOpenCursor();
#endif

  return lnum;
}

/*
 * ":sign define {name} ..." command
 */
static void
sign_define_cmd(char_u *sign_name, char_u *cmdline)
{
  char_u *arg;
  char_u *p = cmdline;
  char_u *icon = NULL;
  char_u *text = NULL;
  char_u *linehl = NULL;
  char_u *texthl = NULL;
  int failed = FALSE;

  // set values for a defined sign.
  for (;;)
  {
    arg = skipwhite(p);
    if (*arg == NUL)
      break;
    p = skiptowhite_esc(arg);
    if (STRNCMP(arg, "icon=", 5) == 0)
    {
      arg += 5;
      icon = vim_strnsave(arg, (int)(p - arg));
    }
    else if (STRNCMP(arg, "text=", 5) == 0)
    {
      arg += 5;
      text = vim_strnsave(arg, (int)(p - arg));
    }
    else if (STRNCMP(arg, "linehl=", 7) == 0)
    {
      arg += 7;
      linehl = vim_strnsave(arg, (int)(p - arg));
    }
    else if (STRNCMP(arg, "texthl=", 7) == 0)
    {
      arg += 7;
      texthl = vim_strnsave(arg, (int)(p - arg));
    }
    else
    {
      semsg(_(e_invarg2), arg);
      failed = TRUE;
      break;
    }
  }

  if (!failed)
    sign_define_by_name(sign_name, icon, linehl, text, texthl);

  vim_free(icon);
  vim_free(text);
  vim_free(linehl);
  vim_free(texthl);
}

/*
 * ":sign place" command
 */
static void
sign_place_cmd(
    buf_T *buf,
    linenr_T lnum,
    char_u *sign_name,
    int id,
    char_u *group,
    int prio)
{
  if (id <= 0)
  {
    // List signs placed in a file/buffer
    //   :sign place file={fname}
    //   :sign place group={group} file={fname}
    //   :sign place group=* file={fname}
    //   :sign place buffer={nr}
    //   :sign place group={group} buffer={nr}
    //   :sign place group=* buffer={nr}
    //   :sign place
    //   :sign place group={group}
    //   :sign place group=*
    if (lnum >= 0 || sign_name != NULL || (group != NULL && *group == '\0'))
      emsg(_(e_invarg));
    else
      sign_list_placed(buf, group);
  }
  else
  {
    // Place a new sign
    if (sign_name == NULL || buf == NULL || (group != NULL && *group == '\0'))
    {
      emsg(_(e_invarg));
      return;
    }

    sign_place(&id, group, sign_name, buf, lnum, prio);
  }
}

/*
 * ":sign unplace" command
 */
static void
sign_unplace_cmd(
    buf_T *buf,
    linenr_T lnum,
    char_u *sign_name,
    int id,
    char_u *group)
{
  if (lnum >= 0 || sign_name != NULL || (group != NULL && *group == '\0'))
  {
    emsg(_(e_invarg));
    return;
  }

  if (id == -2)
  {
    if (buf != NULL)
      // :sign unplace * file={fname}
      // :sign unplace * group={group} file={fname}
      // :sign unplace * group=* file={fname}
      // :sign unplace * buffer={nr}
      // :sign unplace * group={group} buffer={nr}
      // :sign unplace * group=* buffer={nr}
      sign_unplace(0, group, buf, 0);
    else
      // :sign unplace *
      // :sign unplace * group={group}
      // :sign unplace * group=*
      FOR_ALL_BUFFERS(buf)
    if (buf->b_signlist != NULL)
      buf_delete_signs(buf, group);
  }
  else
  {
    if (buf != NULL)
      // :sign unplace {id} file={fname}
      // :sign unplace {id} group={group} file={fname}
      // :sign unplace {id} group=* file={fname}
      // :sign unplace {id} buffer={nr}
      // :sign unplace {id} group={group} buffer={nr}
      // :sign unplace {id} group=* buffer={nr}
      sign_unplace(id, group, buf, 0);
    else
    {
      if (id == -1)
      {
        // :sign unplace group={group}
        // :sign unplace group=*
        sign_unplace_at_cursor(group);
      }
      else
      {
        // :sign unplace {id}
        // :sign unplace {id} group={group}
        // :sign unplace {id} group=*
        FOR_ALL_BUFFERS(buf)
        sign_unplace(id, group, buf, 0);
      }
    }
  }
}

/*
 * Jump to a placed sign commands:
 *   :sign jump {id} file={fname}
 *   :sign jump {id} buffer={nr}
 *   :sign jump {id} group={group} file={fname}
 *   :sign jump {id} group={group} buffer={nr}
 */
static void
sign_jump_cmd(
    buf_T *buf,
    linenr_T lnum,
    char_u *sign_name,
    int id,
    char_u *group)
{
  if (sign_name == NULL && group == NULL && id == -1)
  {
    emsg(_(e_argreq));
    return;
  }

  if (buf == NULL || (group != NULL && *group == '\0') || lnum >= 0 || sign_name != NULL)
  {
    // File or buffer is not specified or an empty group is used
    // or a line number or a sign name is specified.
    emsg(_(e_invarg));
    return;
  }
  (void)sign_jump(id, group, buf);
}

/*
 * Parse the command line arguments for the ":sign place", ":sign unplace" and
 * ":sign jump" commands.
 * The supported arguments are: line={lnum} name={name} group={group}
 * priority={prio} and file={fname} or buffer={nr}.
 */
static int
parse_sign_cmd_args(
    int cmd,
    char_u *arg,
    char_u **sign_name,
    int *signid,
    char_u **group,
    int *prio,
    buf_T **buf,
    linenr_T *lnum)
{
  char_u *arg1;
  char_u *name;
  char_u *filename = NULL;
  int lnum_arg = FALSE;

  // first arg could be placed sign id
  arg1 = arg;
  if (VIM_ISDIGIT(*arg))
  {
    *signid = getdigits(&arg);
    if (!VIM_ISWHITE(*arg) && *arg != NUL)
    {
      *signid = -1;
      arg = arg1;
    }
    else
      arg = skipwhite(arg);
  }

  while (*arg != NUL)
  {
    if (STRNCMP(arg, "line=", 5) == 0)
    {
      arg += 5;
      *lnum = atoi((char *)arg);
      arg = skiptowhite(arg);
      lnum_arg = TRUE;
    }
    else if (STRNCMP(arg, "*", 1) == 0 && cmd == SIGNCMD_UNPLACE)
    {
      if (*signid != -1)
      {
        emsg(_(e_invarg));
        return FAIL;
      }
      *signid = -2;
      arg = skiptowhite(arg + 1);
    }
    else if (STRNCMP(arg, "name=", 5) == 0)
    {
      arg += 5;
      name = arg;
      arg = skiptowhite(arg);
      if (*arg != NUL)
        *arg++ = NUL;
      while (name[0] == '0' && name[1] != NUL)
        ++name;
      *sign_name = name;
    }
    else if (STRNCMP(arg, "group=", 6) == 0)
    {
      arg += 6;
      *group = arg;
      arg = skiptowhite(arg);
      if (*arg != NUL)
        *arg++ = NUL;
    }
    else if (STRNCMP(arg, "priority=", 9) == 0)
    {
      arg += 9;
      *prio = atoi((char *)arg);
      arg = skiptowhite(arg);
    }
    else if (STRNCMP(arg, "file=", 5) == 0)
    {
      arg += 5;
      filename = arg;
      *buf = buflist_findname_exp(arg);
      break;
    }
    else if (STRNCMP(arg, "buffer=", 7) == 0)
    {
      arg += 7;
      filename = arg;
      *buf = buflist_findnr((int)getdigits(&arg));
      if (*skipwhite(arg) != NUL)
        emsg(_(e_trailing));
      break;
    }
    else
    {
      emsg(_(e_invarg));
      return FAIL;
    }
    arg = skipwhite(arg);
  }

  if (filename != NULL && *buf == NULL)
  {
    semsg(_("E158: Invalid buffer name: %s"), filename);
    return FAIL;
  }

  // If the filename is not supplied for the sign place or the sign jump
  // command, then use the current buffer.
  if (filename == NULL && ((cmd == SIGNCMD_PLACE && lnum_arg) || cmd == SIGNCMD_JUMP))
    *buf = curwin->w_buffer;

  return OK;
}

/*
 * ":sign" command
 */
void ex_sign(exarg_T *eap)
{
  char_u *arg = eap->arg;
  char_u *p;
  int idx;
  sign_T *sp;
  buf_T *buf = NULL;

  // Parse the subcommand.
  p = skiptowhite(arg);
  idx = sign_cmd_idx(arg, p);
  if (idx == SIGNCMD_LAST)
  {
    semsg(_("E160: Unknown sign command: %s"), arg);
    return;
  }
  arg = skipwhite(p);

  if (idx <= SIGNCMD_LIST)
  {
    // Define, undefine or list signs.
    if (idx == SIGNCMD_LIST && *arg == NUL)
    {
      // ":sign list": list all defined signs
      for (sp = first_sign; sp != NULL && !got_int; sp = sp->sn_next)
        sign_list_defined(sp);
    }
    else if (*arg == NUL)
      emsg(_("E156: Missing sign name"));
    else
    {
      char_u *name;

      // Isolate the sign name.  If it's a number skip leading zeroes,
      // so that "099" and "99" are the same sign.  But keep "0".
      p = skiptowhite(arg);
      if (*p != NUL)
        *p++ = NUL;
      while (arg[0] == '0' && arg[1] != NUL)
        ++arg;
      name = vim_strsave(arg);

      if (idx == SIGNCMD_DEFINE)
        sign_define_cmd(name, p);
      else if (idx == SIGNCMD_LIST)
        // ":sign list {name}"
        sign_list_by_name(name);
      else
        // ":sign undefine {name}"
        sign_undefine_by_name(name);

      vim_free(name);
      return;
    }
  }
  else
  {
    int id = -1;
    linenr_T lnum = -1;
    char_u *sign_name = NULL;
    char_u *group = NULL;
    int prio = SIGN_DEF_PRIO;

    // Parse command line arguments
    if (parse_sign_cmd_args(idx, arg, &sign_name, &id, &group, &prio,
                            &buf, &lnum) == FAIL)
      return;

    if (idx == SIGNCMD_PLACE)
      sign_place_cmd(buf, lnum, sign_name, id, group, prio);
    else if (idx == SIGNCMD_UNPLACE)
      sign_unplace_cmd(buf, lnum, sign_name, id, group);
    else if (idx == SIGNCMD_JUMP)
      sign_jump_cmd(buf, lnum, sign_name, id, group);
  }
}

/*
 * Return information about a specified sign
 */
static void
sign_getinfo(sign_T *sp, dict_T *retdict)
{
  char_u *p;

  dict_add_string(retdict, "name", (char_u *)sp->sn_name);
  if (sp->sn_icon != NULL)
    dict_add_string(retdict, "icon", (char_u *)sp->sn_icon);
  if (sp->sn_text != NULL)
    dict_add_string(retdict, "text", (char_u *)sp->sn_text);
  if (sp->sn_line_hl > 0)
  {
    p = get_highlight_name_ext(NULL, sp->sn_line_hl - 1, FALSE);
    if (p == NULL)
      p = (char_u *)"NONE";
    dict_add_string(retdict, "linehl", (char_u *)p);
  }
  if (sp->sn_text_hl > 0)
  {
    p = get_highlight_name_ext(NULL, sp->sn_text_hl - 1, FALSE);
    if (p == NULL)
      p = (char_u *)"NONE";
    dict_add_string(retdict, "texthl", (char_u *)p);
  }
}

/*
 * If 'name' is NULL, return a list of all the defined signs.
 * Otherwise, return information about the specified sign.
 */
void sign_getlist(char_u *name, list_T *retlist)
{
  sign_T *sp = first_sign;
  dict_T *dict;

  if (name != NULL)
  {
    sp = sign_find(name, NULL);
    if (sp == NULL)
      return;
  }

  for (; sp != NULL && !got_int; sp = sp->sn_next)
  {
    if ((dict = dict_alloc_id(aid_sign_getlist)) == NULL)
      return;
    if (list_append_dict(retlist, dict) == FAIL)
      return;
    sign_getinfo(sp, dict);

    if (name != NULL) // handle only the specified sign
      break;
  }
}

/*
 * Returns information about signs placed in a buffer as list of dicts.
 */
void get_buffer_signs(buf_T *buf, list_T *l)
{
  signlist_T *sign;
  dict_T *d;

  FOR_ALL_SIGNS_IN_BUF(buf, sign)
  {
    if ((d = sign_get_info(sign)) != NULL)
      list_append_dict(l, d);
  }
}

/*
 * Return information about all the signs placed in a buffer
 */
static void
sign_get_placed_in_buf(
    buf_T *buf,
    linenr_T lnum,
    int sign_id,
    char_u *sign_group,
    list_T *retlist)
{
  dict_T *d;
  list_T *l;
  signlist_T *sign;
  dict_T *sdict;

  if ((d = dict_alloc_id(aid_sign_getplaced_dict)) == NULL)
    return;
  list_append_dict(retlist, d);

  dict_add_number(d, "bufnr", (long)buf->b_fnum);

  if ((l = list_alloc_id(aid_sign_getplaced_list)) == NULL)
    return;
  dict_add_list(d, "signs", l);

  FOR_ALL_SIGNS_IN_BUF(buf, sign)
  {
    if (!sign_in_group(sign, sign_group))
      continue;
    if ((lnum == 0 && sign_id == 0) || (sign_id == 0 && lnum == sign->lnum) || (lnum == 0 && sign_id == sign->id) || (lnum == sign->lnum && sign_id == sign->id))
    {
      if ((sdict = sign_get_info(sign)) != NULL)
        list_append_dict(l, sdict);
    }
  }
}

/*
 * Get a list of signs placed in buffer 'buf'. If 'num' is non-zero, return the
 * sign placed at the line number. If 'lnum' is zero, return all the signs
 * placed in 'buf'. If 'buf' is NULL, return signs placed in all the buffers.
 */
void sign_get_placed(
    buf_T *buf,
    linenr_T lnum,
    int sign_id,
    char_u *sign_group,
    list_T *retlist)
{
  if (buf != NULL)
    sign_get_placed_in_buf(buf, lnum, sign_id, sign_group, retlist);
  else
  {
    FOR_ALL_BUFFERS(buf)
    {
      if (buf->b_signlist != NULL)
        sign_get_placed_in_buf(buf, 0, sign_id, sign_group, retlist);
    }
  }
}

#if defined(FEAT_SIGN_ICONS) || defined(PROTO)
/*
 * Allocate the icons.  Called when the GUI has started.  Allows defining
 * signs before it starts.
 */
void sign_gui_started(void)
{
  sign_T *sp;

  for (sp = first_sign; sp != NULL; sp = sp->sn_next)
    if (sp->sn_icon != NULL)
      sp->sn_image = gui_mch_register_sign(sp->sn_icon);
}
#endif

/*
 * List one sign.
 */
static void
sign_list_defined(sign_T *sp)
{
  char_u *p;

  smsg("sign %s", sp->sn_name);
  if (sp->sn_icon != NULL)
  {
    msg_puts(" icon=");
    msg_outtrans(sp->sn_icon);
#ifdef FEAT_SIGN_ICONS
    if (sp->sn_image == NULL)
      msg_puts(_(" (NOT FOUND)"));
#else
    msg_puts(_(" (not supported)"));
#endif
  }
  if (sp->sn_text != NULL)
  {
    msg_puts(" text=");
    msg_outtrans(sp->sn_text);
  }
  if (sp->sn_line_hl > 0)
  {
    msg_puts(" linehl=");
    p = get_highlight_name_ext(NULL, sp->sn_line_hl - 1, FALSE);
    if (p == NULL)
      msg_puts("NONE");
    else
      msg_puts((char *)p);
  }
  if (sp->sn_text_hl > 0)
  {
    msg_puts(" texthl=");
    p = get_highlight_name_ext(NULL, sp->sn_text_hl - 1, FALSE);
    if (p == NULL)
      msg_puts("NONE");
    else
      msg_puts((char *)p);
  }
}

/*
 * Undefine a sign and free its memory.
 */
static void
sign_undefine(sign_T *sp, sign_T *sp_prev)
{
  vim_free(sp->sn_name);
  vim_free(sp->sn_icon);
#ifdef FEAT_SIGN_ICONS
  if (sp->sn_image != NULL)
  {
    gui_mch_destroy_sign(sp->sn_image);
  }
#endif
  vim_free(sp->sn_text);
  if (sp_prev == NULL)
    first_sign = sp->sn_next;
  else
    sp_prev->sn_next = sp->sn_next;
  vim_free(sp);
}

/*
 * Get highlighting attribute for sign "typenr".
 * If "line" is TRUE: line highl, if FALSE: text highl.
 */
int sign_get_attr(int typenr, int line)
{
  sign_T *sp;

  for (sp = first_sign; sp != NULL; sp = sp->sn_next)
    if (sp->sn_typenr == typenr)
    {
      if (line)
      {
        if (sp->sn_line_hl > 0)
          return syn_id2attr(sp->sn_line_hl);
      }
      else
      {
        if (sp->sn_text_hl > 0)
          return syn_id2attr(sp->sn_text_hl);
      }
      break;
    }
  return 0;
}

/*
 * Get text mark for sign "typenr".
 * Returns NULL if there isn't one.
 */
char_u *
sign_get_text(int typenr)
{
  sign_T *sp;

  for (sp = first_sign; sp != NULL; sp = sp->sn_next)
    if (sp->sn_typenr == typenr)
      return sp->sn_text;
  return NULL;
}

#if defined(FEAT_SIGN_ICONS) || defined(PROTO)
void *
sign_get_image(
    int typenr) // the attribute which may have a sign
{
  sign_T *sp;

  for (sp = first_sign; sp != NULL; sp = sp->sn_next)
    if (sp->sn_typenr == typenr)
      return sp->sn_image;
  return NULL;
}
#endif

/*
 * Undefine/free all signs.
 */
void free_signs(void)
{
  while (first_sign != NULL)
    sign_undefine(first_sign, NULL);
}

#if defined(FEAT_CMDL_COMPL) || defined(PROTO)
static enum {
  EXP_SUBCMD,     // expand :sign sub-commands
  EXP_DEFINE,     // expand :sign define {name} args
  EXP_PLACE,      // expand :sign place {id} args
  EXP_LIST,       // expand :sign place args
  EXP_UNPLACE,    // expand :sign unplace"
  EXP_SIGN_NAMES, // expand with name of placed signs
  EXP_SIGN_GROUPS // expand with name of placed sign groups
} expand_what;

/*
 * Return the n'th sign name (used for command line completion)
 */
static char_u *
get_nth_sign_name(int idx)
{
  int current_idx;
  sign_T *sp;

  // Complete with name of signs already defined
  current_idx = 0;
  for (sp = first_sign; sp != NULL; sp = sp->sn_next)
    if (current_idx++ == idx)
      return sp->sn_name;
  return NULL;
}

/*
 * Return the n'th sign group name (used for command line completion)
 */
static char_u *
get_nth_sign_group_name(int idx)
{
  int current_idx;
  int todo;
  hashitem_T *hi;
  signgroup_T *group;

  // Complete with name of sign groups already defined
  current_idx = 0;
  todo = (int)sg_table.ht_used;
  for (hi = sg_table.ht_array; todo > 0; ++hi)
  {
    if (!HASHITEM_EMPTY(hi))
    {
      --todo;
      if (current_idx++ == idx)
      {
        group = HI2SG(hi);
        return group->sg_name;
      }
    }
  }
  return NULL;
}

/*
 * Function given to ExpandGeneric() to obtain the sign command
 * expansion.
 */
char_u *
get_sign_name(expand_T *xp UNUSED, int idx)
{
  switch (expand_what)
  {
  case EXP_SUBCMD:
    return (char_u *)cmds[idx];
  case EXP_DEFINE:
  {
    char *define_arg[] =
        {
            "icon=", "linehl=", "text=", "texthl=", NULL};
    return (char_u *)define_arg[idx];
  }
  case EXP_PLACE:
  {
    char *place_arg[] =
        {
            "line=", "name=", "group=", "priority=", "file=",
            "buffer=", NULL};
    return (char_u *)place_arg[idx];
  }
  case EXP_LIST:
  {
    char *list_arg[] =
        {
            "group=", "file=", "buffer=", NULL};
    return (char_u *)list_arg[idx];
  }
  case EXP_UNPLACE:
  {
    char *unplace_arg[] = {"group=", "file=", "buffer=", NULL};
    return (char_u *)unplace_arg[idx];
  }
  case EXP_SIGN_NAMES:
    return get_nth_sign_name(idx);
  case EXP_SIGN_GROUPS:
    return get_nth_sign_group_name(idx);
  default:
    return NULL;
  }
}

/*
 * Handle command line completion for :sign command.
 */
void set_context_in_sign_cmd(expand_T *xp, char_u *arg)
{
  char_u *p;
  char_u *end_subcmd;
  char_u *last;
  int cmd_idx;
  char_u *begin_subcmd_args;

  // Default: expand subcommands.
  xp->xp_context = EXPAND_SIGN;
  expand_what = EXP_SUBCMD;
  xp->xp_pattern = arg;

  end_subcmd = skiptowhite(arg);
  if (*end_subcmd == NUL)
    // expand subcmd name
    // :sign {subcmd}<CTRL-D>
    return;

  cmd_idx = sign_cmd_idx(arg, end_subcmd);

  // :sign {subcmd} {subcmd_args}
  //		      |
  //		      begin_subcmd_args
  begin_subcmd_args = skipwhite(end_subcmd);

  // expand last argument of subcmd

  // :sign define {name} {args}...
  //		    |
  //		    p

  // Loop until reaching last argument.
  p = begin_subcmd_args;
  do
  {
    p = skipwhite(p);
    last = p;
    p = skiptowhite(p);
  } while (*p != NUL);

  p = vim_strchr(last, '=');

  // :sign define {name} {args}... {last}=
  //				     |	   |
  //				  last	   p
  if (p == NULL)
  {
    // Expand last argument name (before equal sign).
    xp->xp_pattern = last;
    switch (cmd_idx)
    {
    case SIGNCMD_DEFINE:
      expand_what = EXP_DEFINE;
      break;
    case SIGNCMD_PLACE:
      // List placed signs
      if (VIM_ISDIGIT(*begin_subcmd_args))
        //   :sign place {id} {args}...
        expand_what = EXP_PLACE;
      else
        //   :sign place {args}...
        expand_what = EXP_LIST;
      break;
    case SIGNCMD_LIST:
    case SIGNCMD_UNDEFINE:
      // :sign list <CTRL-D>
      // :sign undefine <CTRL-D>
      expand_what = EXP_SIGN_NAMES;
      break;
    case SIGNCMD_JUMP:
    case SIGNCMD_UNPLACE:
      expand_what = EXP_UNPLACE;
      break;
    default:
      xp->xp_context = EXPAND_NOTHING;
    }
  }
  else
  {
    // Expand last argument value (after equal sign).
    xp->xp_pattern = p + 1;
    switch (cmd_idx)
    {
    case SIGNCMD_DEFINE:
      if (STRNCMP(last, "texthl", 6) == 0 || STRNCMP(last, "linehl", 6) == 0)
        xp->xp_context = EXPAND_HIGHLIGHT;
      else if (STRNCMP(last, "icon", 4) == 0)
        xp->xp_context = EXPAND_FILES;
      else
        xp->xp_context = EXPAND_NOTHING;
      break;
    case SIGNCMD_PLACE:
      if (STRNCMP(last, "name", 4) == 0)
        expand_what = EXP_SIGN_NAMES;
      else if (STRNCMP(last, "group", 5) == 0)
        expand_what = EXP_SIGN_GROUPS;
      else if (STRNCMP(last, "file", 4) == 0)
        xp->xp_context = EXPAND_BUFFERS;
      else
        xp->xp_context = EXPAND_NOTHING;
      break;
    case SIGNCMD_UNPLACE:
    case SIGNCMD_JUMP:
      if (STRNCMP(last, "group", 5) == 0)
        expand_what = EXP_SIGN_GROUPS;
      else if (STRNCMP(last, "file", 4) == 0)
        xp->xp_context = EXPAND_BUFFERS;
      else
        xp->xp_context = EXPAND_NOTHING;
      break;
    default:
      xp->xp_context = EXPAND_NOTHING;
    }
  }
}
#endif

#endif /* FEAT_SIGNS */
