diff options
Diffstat (limited to 'src/nvim/getchar.c')
| -rw-r--r-- | src/nvim/getchar.c | 847 | 
1 files changed, 484 insertions, 363 deletions
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 139bf7b802..ec14803a2e 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1287,9 +1287,9 @@ openscript (      oldcurscript = curscript;      do { -      update_topline_cursor();          /* update cursor position and topline */ -      normal_cmd(&oa, FALSE);           /* execute one command */ -      vpeekc();                         /* check for end of file */ +      update_topline_cursor();          // update cursor position and topline +      normal_cmd(&oa, false);           // execute one command +      vpeekc();                         // check for end of file      } while (scriptin[oldcurscript] != NULL);      State = save_State; @@ -1425,49 +1425,54 @@ int vgetc(void)        /* a keypad or special function key was not mapped, use it like         * its ASCII equivalent */        switch (c) { -      case K_KPLUS:               c = '+'; break; -      case K_KMINUS:              c = '-'; break; -      case K_KDIVIDE:             c = '/'; break; -      case K_KMULTIPLY:   c = '*'; break; -      case K_KENTER:              c = CAR; break; -      case K_KPOINT: -        c = '.'; break; -      case K_K0:          c = '0'; break; -      case K_K1:          c = '1'; break; -      case K_K2:          c = '2'; break; -      case K_K3:          c = '3'; break; -      case K_K4:          c = '4'; break; -      case K_K5:          c = '5'; break; -      case K_K6:          c = '6'; break; -      case K_K7:          c = '7'; break; -      case K_K8:          c = '8'; break; -      case K_K9:          c = '9'; break; - -      case K_XHOME: -      case K_ZHOME:       if (mod_mask == MOD_MASK_SHIFT) { -          c = K_S_HOME; -          mod_mask = 0; -      } else if (mod_mask == MOD_MASK_CTRL) { -          c = K_C_HOME; -          mod_mask = 0; -      } else -          c = K_HOME; -        break; -      case K_XEND: -      case K_ZEND:        if (mod_mask == MOD_MASK_SHIFT) { -          c = K_S_END; -          mod_mask = 0; -      } else if (mod_mask == MOD_MASK_CTRL) { -          c = K_C_END; -          mod_mask = 0; -      } else -          c = K_END; -        break; +        case K_KPLUS:       c = '+'; break; +        case K_KMINUS:      c = '-'; break; +        case K_KDIVIDE:     c = '/'; break; +        case K_KMULTIPLY:   c = '*'; break; +        case K_KENTER:      c = CAR; break; +        case K_KPOINT:      c = '.'; break; +        case K_KCOMMA:      c = ','; break; +        case K_KEQUAL:      c = '='; break; +        case K_K0:          c = '0'; break; +        case K_K1:          c = '1'; break; +        case K_K2:          c = '2'; break; +        case K_K3:          c = '3'; break; +        case K_K4:          c = '4'; break; +        case K_K5:          c = '5'; break; +        case K_K6:          c = '6'; break; +        case K_K7:          c = '7'; break; +        case K_K8:          c = '8'; break; +        case K_K9:          c = '9'; break; + +        case K_XHOME: +        case K_ZHOME: +          if (mod_mask == MOD_MASK_SHIFT) { +            c = K_S_HOME; +            mod_mask = 0; +          } else if (mod_mask == MOD_MASK_CTRL) { +            c = K_C_HOME; +            mod_mask = 0; +          } else { +            c = K_HOME; +          } +          break; +        case K_XEND: +        case K_ZEND: +          if (mod_mask == MOD_MASK_SHIFT) { +            c = K_S_END; +            mod_mask = 0; +          } else if (mod_mask == MOD_MASK_CTRL) { +            c = K_C_END; +            mod_mask = 0; +          } else { +            c = K_END; +          } +          break; -      case K_XUP:         c = K_UP; break; -      case K_XDOWN:       c = K_DOWN; break; -      case K_XLEFT:       c = K_LEFT; break; -      case K_XRIGHT:      c = K_RIGHT; break; +        case K_XUP:         c = K_UP; break; +        case K_XDOWN:       c = K_DOWN; break; +        case K_XLEFT:       c = K_LEFT; break; +        case K_XRIGHT:      c = K_RIGHT; break;        }        /* For a multi-byte character get all the bytes and return the @@ -2265,18 +2270,25 @@ static int vgetorpeek(int advance)            // that has a <Nop> RHS.            timedout = false;          } + +        long wait_time = 0; + +        if (advance) { +          if (typebuf.tb_len == 0 +              || !(p_timeout || (p_ttimeout && keylen == KEYLEN_PART_KEY))) { +            // blocking wait +            wait_time = -1L; +          } else if (keylen == KEYLEN_PART_KEY && p_ttm >= 0) { +            wait_time = p_ttm; +          } else { +            wait_time = p_tm; +          } +        } +          wait_tb_len = typebuf.tb_len;          c = inchar(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len, -            typebuf.tb_buflen - typebuf.tb_off - typebuf.tb_len - 1, -            !advance -            ? 0 -            : ((typebuf.tb_len == 0 -                || !(p_timeout || (p_ttimeout -                                   && keylen == KEYLEN_PART_KEY))) -               ? -1L -               : ((keylen == KEYLEN_PART_KEY && p_ttm >= 0) -                  ? p_ttm -                  : p_tm))); +                   typebuf.tb_buflen - typebuf.tb_off - typebuf.tb_len - 1, +                   wait_time);          if (i != 0)            pop_showcmd(); @@ -2490,286 +2502,334 @@ int fix_input_buffer(char_u *buf, int len)    return len;  } -/* - * map[!]		    : show all key mappings - * map[!] {lhs}		    : show key mapping for {lhs} - * map[!] {lhs} {rhs}	    : set key mapping for {lhs} to {rhs} - * noremap[!] {lhs} {rhs}   : same, but no remapping for {rhs} - * unmap[!] {lhs}	    : remove key mapping for {lhs} - * abbr			    : show all abbreviations - * abbr {lhs}		    : show abbreviations for {lhs} - * abbr {lhs} {rhs}	    : set abbreviation for {lhs} to {rhs} - * noreabbr {lhs} {rhs}	    : same, but no remapping for {rhs} - * unabbr {lhs}		    : remove abbreviation for {lhs} - * - * maptype: 0 for :map, 1 for :unmap, 2 for noremap. - * - * arg is pointer to any arguments. Note: arg cannot be a read-only string, - * it will be modified. - * - * for :map   mode is NORMAL + VISUAL + SELECTMODE + OP_PENDING - * for :map!  mode is INSERT + CMDLINE - * for :cmap  mode is CMDLINE - * for :imap  mode is INSERT - * for :lmap  mode is LANGMAP - * for :nmap  mode is NORMAL - * for :vmap  mode is VISUAL + SELECTMODE - * for :xmap  mode is VISUAL - * for :smap  mode is SELECTMODE - * for :omap  mode is OP_PENDING - * for :tmap  mode is TERM_FOCUS - * - * for :abbr  mode is INSERT + CMDLINE - * for :iabbr mode is INSERT - * for :cabbr mode is CMDLINE - * - * Return 0 for success - *	  1 for invalid arguments - *	  2 for no match - *	  4 for out of mem (deprecated, WON'T HAPPEN) - *	  5 for entry not unique - */ -int  -do_map ( -    int maptype, -    char_u *arg, -    int mode, -    int abbrev                     /* not a mapping but an abbreviation */ -) +/// Replace termcodes in the given LHS and RHS and store the results into the +/// `lhs` and `rhs` of the given @ref MapArguments struct. +/// +/// `rhs` and `orig_rhs` will both point to new allocated buffers. `orig_rhs` +/// will hold a copy of the given `orig_rhs`. +/// +/// The `*_len` variables will be set appropriately. If the length of +/// the final `lhs` exceeds `MAXMAPLEN`, `lhs_len` will be set equal to the +/// original larger length and `lhs` will be truncated. +/// +/// If RHS is equal to "<Nop>", `rhs` will be the empty string, `rhs_len` +/// will be zero, and `rhs_is_noop` will be set to true. +/// +/// Any memory allocated by @ref replace_termcodes is freed before this function +/// returns. +/// +/// @param[in] orig_lhs   Original mapping LHS, with characters to replace. +/// @param[in] orig_lhs_len   `strlen` of orig_lhs. +/// @param[in] orig_rhs   Original mapping RHS, with characters to replace. +/// @param[in] orig_rhs_len   `strlen` of orig_rhs. +/// @param[in] cpo_flags  See param docs for @ref replace_termcodes. +/// @param[out] mapargs   MapArguments struct holding the replaced strings. +void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len, +                        const char_u *orig_rhs, const size_t orig_rhs_len, +                        int cpo_flags, MapArguments *mapargs)  { -  char_u      *keys; -  mapblock_T  *mp, **mpp; -  char_u      *rhs; -  char_u      *p; -  int n; -  int len = 0;                  /* init for GCC */ -  int hasarg; -  int haskey; -  int did_it = FALSE; -  int did_local = FALSE; -  int round; -  char_u      *keys_buf = NULL; -  char_u      *arg_buf = NULL; -  int retval = 0; -  int do_backslash; -  int hash; -  int new_hash; -  mapblock_T  **abbr_table; -  mapblock_T  **map_table; -  bool unique = false; -  bool nowait = false; -  bool silent = false; -  bool expr = false; -  int noremap; -  char_u      *orig_rhs; +  char_u *lhs_buf = NULL; +  char_u *rhs_buf = NULL; -  keys = arg; -  map_table = maphash; -  abbr_table = &first_abbr; +  // If mapping has been given as ^V<C_UP> say, then replace the term codes +  // with the appropriate two bytes. If it is a shifted special key, unshift +  // it too, giving another two bytes. +  // +  // replace_termcodes() may move the result to allocated memory, which +  // needs to be freed later (*lhs_buf and *rhs_buf). +  // replace_termcodes() also removes CTRL-Vs and sometimes backslashes. +  char_u *replaced = replace_termcodes(orig_lhs, orig_lhs_len, &lhs_buf, +                                       true, true, true, cpo_flags); +  mapargs->lhs_len = STRLEN(replaced); +  xstrlcpy((char *)mapargs->lhs, (char *)replaced, sizeof(mapargs->lhs)); + +  mapargs->orig_rhs_len = orig_rhs_len; +  mapargs->orig_rhs = xcalloc(mapargs->orig_rhs_len + 1, sizeof(char_u)); +  xstrlcpy((char *)mapargs->orig_rhs, (char *)orig_rhs, +           mapargs->orig_rhs_len + 1); + +  if (STRICMP(orig_rhs, "<nop>") == 0) {  // "<Nop>" means nothing +    mapargs->rhs = xcalloc(1, sizeof(char_u));  // single null-char +    mapargs->rhs_len = 0; +    mapargs->rhs_is_noop = true; +  } else { +    replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, +                                 false, true, true, cpo_flags); +    mapargs->rhs_len = STRLEN(replaced); +    mapargs->rhs_is_noop = false; +    mapargs->rhs = xcalloc(mapargs->rhs_len + 1, sizeof(char_u)); +    xstrlcpy((char *)mapargs->rhs, (char *)replaced, mapargs->rhs_len + 1); +  } -  /* For ":noremap" don't remap, otherwise do remap. */ -  if (maptype == 2) -    noremap = REMAP_NONE; -  else -    noremap = REMAP_YES; +  xfree(lhs_buf); +  xfree(rhs_buf); +} -  /* Accept <buffer>, <nowait>, <silent>, <expr> <script> and <unique> in -   * any order. */ -  for (;; ) { -    /* -     * Check for "<buffer>": mapping local to buffer. -     */ -    if (STRNCMP(keys, "<buffer>", 8) == 0) { -      keys = skipwhite(keys + 8); -      map_table = curbuf->b_maphash; -      abbr_table = &curbuf->b_first_abbr; +/// Parse a string of |:map-arguments| into a @ref MapArguments struct. +/// +/// Termcodes, backslashes, CTRL-V's, etc. inside the extracted {lhs} and +/// {rhs} are replaced by @ref set_maparg_lhs_rhs. +/// +/// rhs and orig_rhs in the returned mapargs will be set to null or a pointer +/// to allocated memory and should be freed even on error. +/// +/// @param[in]  strargs   String of map args, e.g. "<buffer> <expr><silent>". +///                       May contain leading or trailing whitespace. +/// @param[in]  is_unmap  True, if strargs should be parsed like an |:unmap| +///                       command. |:unmap| commands interpret *all* text to the +///                       right of the last map argument as the {lhs} of the +///                       mapping, i.e. a literal ' ' character is treated like +///                       a "<space>", rather than separating the {lhs} from the +///                       {rhs}. +/// @param[out] mapargs   MapArguments struct holding all extracted argument +///                       values. +/// @return 0 on success, 1 if invalid arguments are detected. +int str_to_mapargs(const char_u *strargs, bool is_unmap, MapArguments *mapargs) +{ +  const char_u *to_parse = strargs; +  to_parse = skipwhite(to_parse); +  MapArguments parsed_args;  // copy these into mapargs "all at once" when done +  memset(&parsed_args, 0, sizeof(parsed_args)); + +  // Accept <buffer>, <nowait>, <silent>, <expr>, <script>, and <unique> in +  // any order. +  while (true) { +    if (STRNCMP(to_parse, "<buffer>", 8) == 0) { +      to_parse = skipwhite(to_parse + 8); +      parsed_args.buffer = true;        continue;      } -    /* -     * Check for "<nowait>": don't wait for more characters. -     */ -    if (STRNCMP(keys, "<nowait>", 8) == 0) { -      keys = skipwhite(keys + 8); -      nowait = true; +    if (STRNCMP(to_parse, "<nowait>", 8) == 0) { +      to_parse = skipwhite(to_parse + 8); +      parsed_args.nowait = true;        continue;      } -    /* -     * Check for "<silent>": don't echo commands. -     */ -    if (STRNCMP(keys, "<silent>", 8) == 0) { -      keys = skipwhite(keys + 8); -      silent = true; +    if (STRNCMP(to_parse, "<silent>", 8) == 0) { +      to_parse = skipwhite(to_parse + 8); +      parsed_args.silent = true;        continue;      }      // Ignore obsolete "<special>" modifier. -    if (STRNCMP(keys, "<special>", 9) == 0) { -      keys = skipwhite(keys + 9); +    if (STRNCMP(to_parse, "<special>", 9) == 0) { +      to_parse = skipwhite(to_parse + 9);        continue;      } -    /* -     * Check for "<script>": remap script-local mappings only -     */ -    if (STRNCMP(keys, "<script>", 8) == 0) { -      keys = skipwhite(keys + 8); -      noremap = REMAP_SCRIPT; +    if (STRNCMP(to_parse, "<script>", 8) == 0) { +      to_parse = skipwhite(to_parse + 8); +      parsed_args.script = true;        continue;      } -    /* -     * Check for "<expr>": {rhs} is an expression. -     */ -    if (STRNCMP(keys, "<expr>", 6) == 0) { -      keys = skipwhite(keys + 6); -      expr = true; +    if (STRNCMP(to_parse, "<expr>", 6) == 0) { +      to_parse = skipwhite(to_parse + 6); +      parsed_args.expr = true;        continue;      } -    /* -     * Check for "<unique>": don't overwrite an existing mapping. -     */ -    if (STRNCMP(keys, "<unique>", 8) == 0) { -      keys = skipwhite(keys + 8); -      unique = true; + +    if (STRNCMP(to_parse, "<unique>", 8) == 0) { +      to_parse = skipwhite(to_parse + 8); +      parsed_args.unique = true;        continue;      }      break;    } -  validate_maphash(); - -  /* -   * Find end of keys and skip CTRL-Vs (and backslashes) in it. -   * Accept backslash like CTRL-V when 'cpoptions' does not contain 'B'. -   * with :unmap white space is included in the keys, no argument possible. -   */ -  p = keys; -  do_backslash = (vim_strchr(p_cpo, CPO_BSLASH) == NULL); -  while (*p && (maptype == 1 || !ascii_iswhite(*p))) { -    if ((p[0] == Ctrl_V || (do_backslash && p[0] == '\\')) && p[1] != NUL) { -      p++;  // skip CTRL-V or backslash +  // Find the next whitespace character, call that the end of {lhs}. +  // +  // If a character (e.g. whitespace) is immediately preceded by a CTRL-V, +  // "scan past" that character, i.e. don't "terminate" LHS with that character +  // if it's whitespace. +  // +  // Treat backslash like CTRL-V when 'cpoptions' does not contain 'B'. +  // +  // With :unmap, literal white space is included in the {lhs}; there is no +  // separate {rhs}. +  const char_u *lhs_end = to_parse; +  bool do_backslash = (vim_strchr(p_cpo, CPO_BSLASH) == NULL); +  while (*lhs_end && (is_unmap || !ascii_iswhite(*lhs_end))) { +    if ((lhs_end[0] == Ctrl_V || (do_backslash && lhs_end[0] == '\\')) +        && lhs_end[1] != NUL) { +      lhs_end++;  // skip CTRL-V or backslash      } -    p++; +    lhs_end++; +  } + +  // {lhs_end} is a pointer to the "terminating whitespace" after {lhs}. +  // Use that to initialize {rhs_start}. +  const char_u *rhs_start = skipwhite(lhs_end); + +  // Given {lhs} might be larger than MAXMAPLEN before replace_termcodes +  // (e.g. "<Space>" is longer than ' '), so first copy into a buffer. +  size_t orig_lhs_len = (size_t)(lhs_end - to_parse); +  char_u *lhs_to_replace = xcalloc(orig_lhs_len + 1, sizeof(char_u)); +  xstrlcpy((char *)lhs_to_replace, (char *)to_parse, orig_lhs_len + 1); + +  size_t orig_rhs_len = STRLEN(rhs_start); +  set_maparg_lhs_rhs(lhs_to_replace, orig_lhs_len, +                     rhs_start, orig_rhs_len, +                     CPO_TO_CPO_FLAGS, &parsed_args); + +  xfree(lhs_to_replace); + +  *mapargs = parsed_args; + +  if (parsed_args.lhs_len > MAXMAPLEN) { +    return 1; +  } +  return 0; +} + +/// Sets or removes a mapping or abbreviation in buffer `buf`. +/// +/// @param maptype    @see do_map +/// @param args  Fully parsed and "preprocessed" arguments for the +///              (un)map/abbrev command. Termcodes should have already been +///              replaced; whitespace, `<` and `>` signs, etc. in {lhs} and +///              {rhs} are assumed to be literal components of the mapping. +/// @param mode       @see do_map +/// @param is_abbrev  @see do_map +/// @param buf        Target Buffer +int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, +               buf_T *buf) +{ +  mapblock_T  *mp, **mpp; +  char_u      *p; +  int n; +  int len = 0;  // init for GCC +  int did_it = false; +  int did_local = false; +  int round; +  int retval = 0; +  int hash; +  int new_hash; +  mapblock_T  **abbr_table; +  mapblock_T  **map_table; +  int noremap; + +  map_table = maphash; +  abbr_table = &first_abbr; + +  // For ":noremap" don't remap, otherwise do remap. +  if (maptype == 2) { +    noremap = REMAP_NONE; +  } else { +    noremap = REMAP_YES;    } -  if (*p != NUL) { -    *p++ = NUL; + +  if (args->buffer) { +    // If <buffer> was given, we'll be searching through the buffer's +    // mappings/abbreviations, not the globals. +    map_table = buf->b_maphash; +    abbr_table = &buf->b_first_abbr;    } +  if (args->script) { +    noremap = REMAP_SCRIPT; +  } + +  validate_maphash(); -  p = skipwhite(p); -  rhs = p; -  hasarg = (*rhs != NUL); -  haskey = (*keys != NUL); +  bool has_lhs = (args->lhs[0] != NUL); +  bool has_rhs = (args->rhs[0] != NUL) || args->rhs_is_noop; -  /* check for :unmap without argument */ -  if (maptype == 1 && !haskey) { +  // check for :unmap without argument +  if (maptype == 1 && !has_lhs) {      retval = 1;      goto theend;    } -  // If mapping has been given as ^V<C_UP> say, then replace the term codes -  // with the appropriate two bytes. If it is a shifted special key, unshift -  // it too, giving another two bytes. -  // replace_termcodes() may move the result to allocated memory, which -  // needs to be freed later (*keys_buf and *arg_buf). -  // replace_termcodes() also removes CTRL-Vs and sometimes backslashes. -  if (haskey) { -    keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, true, true, true, -                             CPO_TO_CPO_FLAGS); -  } -  orig_rhs = rhs; -  if (hasarg) { -    if (STRICMP(rhs, "<nop>") == 0) {  // "<Nop>" means nothing -      rhs = (char_u *)""; -    } else { -      rhs = replace_termcodes(rhs, STRLEN(rhs), &arg_buf, false, true, true, -                              CPO_TO_CPO_FLAGS); -    } -  } +  char_u *lhs = (char_u *)&args->lhs; +  char_u *rhs = (char_u *)args->rhs; +  char_u *orig_rhs = args->orig_rhs; -  //    // check arguments and translate function keys -  // -  if (haskey) { -    len = (int)STRLEN(keys); -    if (len > MAXMAPLEN) {              /* maximum length of MAXMAPLEN chars */ +  if (has_lhs) { +    len = (int)args->lhs_len; +    if (len > MAXMAPLEN) {        retval = 1;        goto theend;      } -    if (abbrev && maptype != 1) { -      /* -       * If an abbreviation ends in a keyword character, the -       * rest must be all keyword-char or all non-keyword-char. -       * Otherwise we won't be able to find the start of it in a -       * vi-compatible way. -       */ +    if (is_abbrev && maptype != 1) { +      // +      // If an abbreviation ends in a keyword character, the +      // rest must be all keyword-char or all non-keyword-char. +      // Otherwise we won't be able to find the start of it in a +      // vi-compatible way. +      //        if (has_mbyte) {          int first, last;          int same = -1; -        first = vim_iswordp(keys); +        first = vim_iswordp(lhs);          last = first; -        p = keys + (*mb_ptr2len)(keys); +        p = lhs + (*mb_ptr2len)(lhs);          n = 1; -        while (p < keys + len) { -          ++n;                                  /* nr of (multi-byte) chars */ -          last = vim_iswordp(p);                /* type of last char */ -          if (same == -1 && last != first) -            same = n - 1;                       /* count of same char type */ +        while (p < lhs + len) { +          n++;                                  // nr of (multi-byte) chars +          last = vim_iswordp(p);                // type of last char +          if (same == -1 && last != first) { +            same = n - 1;                       // count of same char type +          }            p += (*mb_ptr2len)(p);          }          if (last && n > 2 && same >= 0 && same < n - 1) {            retval = 1;            goto theend;          } -      } else if (vim_iswordc(keys[len - 1]))    /* ends in keyword char */ -        for (n = 0; n < len - 2; ++n) -          if (vim_iswordc(keys[n]) != vim_iswordc(keys[len - 2])) { +      } else if (vim_iswordc(lhs[len - 1])) {  // ends in keyword char +        for (n = 0; n < len - 2; n++) { +          if (vim_iswordc(lhs[n]) != vim_iswordc(lhs[len - 2])) {              retval = 1;              goto theend;            } -      /* An abbreviation cannot contain white space. */ -      for (n = 0; n < len; ++n) -        if (ascii_iswhite(keys[n])) { +        }  // for +      } +      // An abbreviation cannot contain white space. +      for (n = 0; n < len; n++) { +        if (ascii_iswhite(lhs[n])) {            retval = 1;            goto theend;          } +      }  // for      }    } -  if (haskey && hasarg && abbrev)       /* if we will add an abbreviation */ -    no_abbr = FALSE;                    /* reset flag that indicates there are -                                                            no abbreviations */ +  if (has_lhs && has_rhs && is_abbrev) {  // if we will add an abbreviation, +    no_abbr = false;  // reset flag that indicates there are no abbreviations +  } -  if (!haskey || (maptype != 1 && !hasarg)) +  if (!has_lhs || (maptype != 1 && !has_rhs)) {      msg_start(); +  } -  /* -   * Check if a new local mapping wasn't already defined globally. -   */ -  if (map_table == curbuf->b_maphash && haskey && hasarg && maptype != 1) { -    /* need to loop over all global hash lists */ -    for (hash = 0; hash < 256 && !got_int; ++hash) { -      if (abbrev) { -        if (hash != 0)          /* there is only one abbreviation list */ +  // Check if a new local mapping wasn't already defined globally. +  if (map_table == buf->b_maphash && has_lhs && has_rhs && maptype != 1) { +    // need to loop over all global hash lists +    for (hash = 0; hash < 256 && !got_int; hash++) { +      if (is_abbrev) { +        if (hash != 0) {  // there is only one abbreviation list            break; +        }          mp = first_abbr; -      } else +      } else {          mp = maphash[hash]; +      }        for (; mp != NULL && !got_int; mp = mp->m_next) { -        /* check entries with the same mode */ +        // check entries with the same mode          if ((mp->m_mode & mode) != 0              && mp->m_keylen == len -            && unique -            && STRNCMP(mp->m_keys, keys, (size_t)len) == 0) { -          if (abbrev) +            && args->unique +            && STRNCMP(mp->m_keys, lhs, (size_t)len) == 0) { +          if (is_abbrev) {              EMSG2(_("E224: global abbreviation already exists for %s"), -                mp->m_keys); -          else -            EMSG2(_("E225: global mapping already exists for %s"), -                mp->m_keys); +                  mp->m_keys); +          } else { +            EMSG2(_("E225: global mapping already exists for %s"), mp->m_keys); +          }            retval = 5;            goto theend;          } @@ -2777,30 +2837,29 @@ do_map (      }    } -  /* -   * When listing global mappings, also list buffer-local ones here. -   */ -  if (map_table != curbuf->b_maphash && !hasarg && maptype != 1) { -    /* need to loop over all global hash lists */ -    for (hash = 0; hash < 256 && !got_int; ++hash) { -      if (abbrev) { -        if (hash != 0)          /* there is only one abbreviation list */ +  // When listing global mappings, also list buffer-local ones here. +  if (map_table != buf->b_maphash && !has_rhs && maptype != 1) { +    // need to loop over all global hash lists +    for (hash = 0; hash < 256 && !got_int; hash++) { +      if (is_abbrev) { +        if (hash != 0) {  // there is only one abbreviation list            break; -        mp = curbuf->b_first_abbr; -      } else -        mp = curbuf->b_maphash[hash]; +        } +        mp = buf->b_first_abbr; +      } else { +        mp = buf->b_maphash[hash]; +      }        for (; mp != NULL && !got_int; mp = mp->m_next) { -        /* check entries with the same mode */ +        // check entries with the same mode          if ((mp->m_mode & mode) != 0) { -          if (!haskey) {                            /* show all entries */ -            showmap(mp, TRUE); -            did_local = TRUE; +          if (!has_lhs) {  // show all entries +            showmap(mp, true); +            did_local = true;            } else {              n = mp->m_keylen; -            if (STRNCMP(mp->m_keys, keys, -                    (size_t)(n < len ? n : len)) == 0) { -              showmap(mp, TRUE); -              did_local = TRUE; +            if (STRNCMP(mp->m_keys, lhs, (size_t)(n < len ? n : len)) == 0) { +              showmap(mp, true); +              did_local = true;              }            }          } @@ -2808,103 +2867,98 @@ do_map (      }    } -  /* -   * Find an entry in the maphash[] list that matches. -   * For :unmap we may loop two times: once to try to unmap an entry with a -   * matching 'from' part, a second time, if the first fails, to unmap an -   * entry with a matching 'to' part. This was done to allow ":ab foo bar" -   * to be unmapped by typing ":unab foo", where "foo" will be replaced by -   * "bar" because of the abbreviation. -   */ +  // Find an entry in the maphash[] list that matches. +  // For :unmap we may loop two times: once to try to unmap an entry with a +  // matching 'from' part, a second time, if the first fails, to unmap an +  // entry with a matching 'to' part. This was done to allow ":ab foo bar" +  // to be unmapped by typing ":unab foo", where "foo" will be replaced by +  // "bar" because of the abbreviation.    for (round = 0; (round == 0 || maptype == 1) && round <= 1 -       && !did_it && !got_int; ++round) { -    /* need to loop over all hash lists */ -    for (hash = 0; hash < 256 && !got_int; ++hash) { -      if (abbrev) { -        if (hash > 0)           /* there is only one abbreviation list */ +       && !did_it && !got_int; round++) { +    // need to loop over all hash lists +    for (hash = 0; hash < 256 && !got_int; hash++) { +      if (is_abbrev) { +        if (hash > 0) {  // there is only one abbreviation list            break; +        }          mpp = abbr_table; -      } else +      } else {          mpp = &(map_table[hash]); +      }        for (mp = *mpp; mp != NULL && !got_int; mp = *mpp) { - -        if (!(mp->m_mode & mode)) {         /* skip entries with wrong mode */ +        if (!(mp->m_mode & mode)) {         // skip entries with wrong mode            mpp = &(mp->m_next);            continue;          } -        if (!haskey) {                      /* show all entries */ +        if (!has_lhs) {                      // show all entries            showmap(mp, map_table != maphash); -          did_it = TRUE; -        } else {                          /* do we have a match? */ -          if (round) {              /* second round: Try unmap "rhs" string */ +          did_it = true; +        } else {                          // do we have a match? +          if (round) {              // second round: Try unmap "rhs" string              n = (int)STRLEN(mp->m_str);              p = mp->m_str;            } else {              n = mp->m_keylen;              p = mp->m_keys;            } -          if (STRNCMP(p, keys, (size_t)(n < len ? n : len)) == 0) { -            if (maptype == 1) {                 /* delete entry */ -              /* Only accept a full match.  For abbreviations we -               * ignore trailing space when matching with the -               * "lhs", since an abbreviation can't have -               * trailing space. */ -              if (n != len && (!abbrev || round || n > len -                               || *skipwhite(keys + n) != NUL)) { +          if (STRNCMP(p, lhs, (size_t)(n < len ? n : len)) == 0) { +            if (maptype == 1) {  // delete entry +              // Only accept a full match.  For abbreviations we +              // ignore trailing space when matching with the +              // "lhs", since an abbreviation can't have +              // trailing space. +              if (n != len && (!is_abbrev || round || n > len +                               || *skipwhite(lhs + n) != NUL)) {                  mpp = &(mp->m_next);                  continue;                } -              /* -               * We reset the indicated mode bits. If nothing is -               * left the entry is deleted below. -               */ +              // We reset the indicated mode bits. If nothing is +              // left the entry is deleted below.                mp->m_mode &= ~mode; -              did_it = TRUE;                    /* remember we did something */ -            } else if (!hasarg) {             /* show matching entry */ +              did_it = true;  // remember we did something +            } else if (!has_rhs) {  // show matching entry                showmap(mp, map_table != maphash); -              did_it = TRUE; -            } else if (n != len) {            /* new entry is ambiguous */ +              did_it = true; +            } else if (n != len) {  // new entry is ambiguous                mpp = &(mp->m_next);                continue; -            } else if (unique) { -              if (abbrev) -                EMSG2(_("E226: abbreviation already exists for %s"), -                    p); -              else +            } else if (args->unique) { +              if (is_abbrev) { +                EMSG2(_("E226: abbreviation already exists for %s"), p); +              } else {                  EMSG2(_("E227: mapping already exists for %s"), p); +              }                retval = 5;                goto theend; -            } else {                          /* new rhs for existing entry */ -              mp->m_mode &= ~mode;                      /* remove mode bits */ -              if (mp->m_mode == 0 && !did_it) {             /* reuse entry */ +            } else {  // new rhs for existing entry +              mp->m_mode &= ~mode;  // remove mode bits +              if (mp->m_mode == 0 && !did_it) {  // reuse entry                  xfree(mp->m_str);                  mp->m_str = vim_strsave(rhs);                  xfree(mp->m_orig_str);                  mp->m_orig_str = vim_strsave(orig_rhs);                  mp->m_noremap = noremap; -                mp->m_nowait = nowait; -                mp->m_silent = silent; +                mp->m_nowait = args->nowait; +                mp->m_silent = args->silent;                  mp->m_mode = mode; -                mp->m_expr = expr; +                mp->m_expr = args->expr;                  mp->m_script_ID = current_SID; -                did_it = TRUE; +                did_it = true;                }              } -            if (mp->m_mode == 0) {              // entry can be deleted +            if (mp->m_mode == 0) {  // entry can be deleted                mapblock_free(mpp); -              continue;                         // continue with *mpp +              continue;  // continue with *mpp              } -            /* -             * May need to put this entry into another hash list. -             */ +            // May need to put this entry into another hash list.              new_hash = MAP_HASH(mp->m_mode, mp->m_keys[0]); -            if (!abbrev && new_hash != hash) { +            if (!is_abbrev && new_hash != hash) {                *mpp = mp->m_next;                mp->m_next = map_table[new_hash];                map_table[new_hash] = mp; -              continue;                         /* continue with *mpp */ +              continue;  // continue with *mpp              }            }          } @@ -2913,13 +2967,13 @@ do_map (      }    } -  if (maptype == 1) {                       /* delete entry */ +  if (maptype == 1) {  // delete entry      if (!did_it) { -      retval = 2;                           /* no match */ -    } else if (*keys == Ctrl_C) { +      retval = 2;  // no match +    } else if (*lhs == Ctrl_C) {        // If CTRL-C has been unmapped, reuse it for Interrupting. -      if (map_table == curbuf->b_maphash) { -        curbuf->b_mapped_ctrl_c &= ~mode; +      if (map_table == buf->b_maphash) { +        buf->b_mapped_ctrl_c &= ~mode;        } else {          mapped_ctrl_c &= ~mode;        } @@ -2927,48 +2981,46 @@ do_map (      goto theend;    } -  if (!haskey || !hasarg) {                 /* print entries */ -    if (!did_it -        && !did_local -        ) { -      if (abbrev) +  if (!has_lhs || !has_rhs) {  // print entries +    if (!did_it && !did_local) { +      if (is_abbrev) {          MSG(_("No abbreviation found")); -      else +      } else {          MSG(_("No mapping found")); +      }      } -    goto theend;                            /* listing finished */ +    goto theend;  // listing finished    } -  if (did_it)                   /* have added the new entry already */ +  if (did_it) {  // have added the new entry already      goto theend; +  } -  /* -   * Get here when adding a new entry to the maphash[] list or abbrlist. -   */ +  // Get here when adding a new entry to the maphash[] list or abbrlist.    mp = xmalloc(sizeof(mapblock_T));    // If CTRL-C has been mapped, don't always use it for Interrupting. -  if (*keys == Ctrl_C) { -    if (map_table == curbuf->b_maphash) { -      curbuf->b_mapped_ctrl_c |= mode; +  if (*lhs == Ctrl_C) { +    if (map_table == buf->b_maphash) { +      buf->b_mapped_ctrl_c |= mode;      } else {        mapped_ctrl_c |= mode;      }    } -  mp->m_keys = vim_strsave(keys); +  mp->m_keys = vim_strsave(lhs);    mp->m_str = vim_strsave(rhs);    mp->m_orig_str = vim_strsave(orig_rhs);    mp->m_keylen = (int)STRLEN(mp->m_keys);    mp->m_noremap = noremap; -  mp->m_nowait = nowait; -  mp->m_silent = silent; +  mp->m_nowait = args->nowait; +  mp->m_silent = args->silent;    mp->m_mode = mode; -  mp->m_expr = expr; +  mp->m_expr = args->expr;    mp->m_script_ID = current_SID; -  /* add the new entry in front of the abbrlist or maphash[] list */ -  if (abbrev) { +  // add the new entry in front of the abbrlist or maphash[] list +  if (is_abbrev) {      mp->m_next = *abbr_table;      *abbr_table = mp;    } else { @@ -2978,11 +3030,80 @@ do_map (    }  theend: -  xfree(keys_buf); -  xfree(arg_buf);    return retval;  } + +/// Set or remove a mapping or an abbreviation in the current buffer, OR +/// display (matching) mappings/abbreviations. +/// +/// ```vim +/// map[!]                          " show all key mappings +/// map[!] {lhs}                    " show key mapping for {lhs} +/// map[!] {lhs} {rhs}              " set key mapping for {lhs} to {rhs} +/// noremap[!] {lhs} {rhs}          " same, but no remapping for {rhs} +/// unmap[!] {lhs}                  " remove key mapping for {lhs} +/// abbr                            " show all abbreviations +/// abbr {lhs}                      " show abbreviations for {lhs} +/// abbr {lhs} {rhs}                " set abbreviation for {lhs} to {rhs} +/// noreabbr {lhs} {rhs}            " same, but no remapping for {rhs} +/// unabbr {lhs}                    " remove abbreviation for {lhs} +/// +/// for :map   mode is NORMAL + VISUAL + SELECTMODE + OP_PENDING +/// for :map!  mode is INSERT + CMDLINE +/// for :cmap  mode is CMDLINE +/// for :imap  mode is INSERT +/// for :lmap  mode is LANGMAP +/// for :nmap  mode is NORMAL +/// for :vmap  mode is VISUAL + SELECTMODE +/// for :xmap  mode is VISUAL +/// for :smap  mode is SELECTMODE +/// for :omap  mode is OP_PENDING +/// for :tmap  mode is TERM_FOCUS +/// +/// for :abbr  mode is INSERT + CMDLINE +/// for :iabbr mode is INSERT +/// for :cabbr mode is CMDLINE +/// ``` +/// +/// @param maptype  0 for |:map|, 1 for |:unmap|, 2 for |noremap|. +/// @param arg      C-string containing the arguments of the map/abbrev +///                 command, i.e. everything except the initial `:[X][nore]map`. +///                 - Cannot be a read-only string; it will be modified. +/// @param mode   Bitflags representing the mode in which to set the mapping. +///               See @ref get_map_mode. +/// @param is_abbrev  True if setting an abbreviation, false otherwise. +/// +/// @return 0 on success. On failure, will return one of the following: +///         - 1 for invalid arguments +///         - 2 for no match +///         - 4 for out of mem (deprecated, WON'T HAPPEN) +///         - 5 for entry not unique +/// +int do_map(int maptype, char_u *arg, int mode, bool is_abbrev) +{ +  MapArguments parsed_args; +  int result = str_to_mapargs(arg, maptype == 1, &parsed_args); +  switch (result) { +    case 0: +      break; +    case 1: +      result = 1;  // invalid arguments +      goto free_and_return; +    default: +      assert(false && "Unknown return code from str_to_mapargs!"); +      result = -1; +      goto free_and_return; +  }  // switch + +  result = buf_do_map(maptype, &parsed_args, mode, is_abbrev, curbuf); + +free_and_return: +  xfree(parsed_args.rhs); +  xfree(parsed_args.orig_rhs); +  return result; +} +  /*   * Delete one entry from the abbrlist or maphash[].   * "mpp" is a pointer to the m_next field of the PREVIOUS entry!  | 
