// This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com // getchar.c: Code related to getting a character from the user or a script // file, manipulations with redo buffer and stuff buffer. #include #include #include #include #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/assert.h" #include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" #include "nvim/event/loop.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/input.h" #include "nvim/insexpand.h" #include "nvim/keycodes.h" #include "nvim/lua/executor.h" #include "nvim/main.h" #include "nvim/mapping.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/os/fileio.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/plines.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" /// Index in scriptin static int curscript = 0; FileDescriptor *scriptin[NSCRIPT] = { NULL }; // These buffers are used for storing: // - stuffed characters: A command that is translated into another command. // - redo characters: will redo the last change. // - recorded characters: for the "q" command. // // The bytes are stored like in the typeahead buffer: // - K_SPECIAL introduces a special key (two more bytes follow). A literal // K_SPECIAL is stored as K_SPECIAL KS_SPECIAL KE_FILLER. // These translations are also done on multi-byte characters! // // Escaping K_SPECIAL is done by inchar(). // Un-escaping is done by vgetc(). #define MINIMAL_SIZE 20 // minimal size for b_str static buffheader_T redobuff = { { NULL, { NUL } }, NULL, 0, 0 }; static buffheader_T old_redobuff = { { NULL, { NUL } }, NULL, 0, 0 }; static buffheader_T recordbuff = { { NULL, { NUL } }, NULL, 0, 0 }; // First read ahead buffer. Used for translated commands. static buffheader_T readbuf1 = { { NULL, { NUL } }, NULL, 0, 0 }; // Second read ahead buffer. Used for redo. static buffheader_T readbuf2 = { { NULL, { NUL } }, NULL, 0, 0 }; static int typeahead_char = 0; // typeahead char that's not flushed /* * when block_redo is TRUE redo buffer will not be changed * used by edit() to repeat insertions and 'V' command for redoing */ static int block_redo = FALSE; static int KeyNoremap = 0; // remapping flags /* * Variables used by vgetorpeek() and flush_buffers() * * typebuf.tb_buf[] contains all characters that are not consumed yet. * typebuf.tb_buf[typebuf.tb_off] is the first valid character. * typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len - 1] is the last valid char. * typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len] must be NUL. * The head of the buffer may contain the result of mappings, abbreviations * and @a commands. The length of this part is typebuf.tb_maplen. * typebuf.tb_silent is the part where applies. * After the head are characters that come from the terminal. * typebuf.tb_no_abbr_cnt is the number of characters in typebuf.tb_buf that * should not be considered for abbreviations. * Some parts of typebuf.tb_buf may not be mapped. These parts are remembered * in typebuf.tb_noremap[], which is the same length as typebuf.tb_buf and * contains RM_NONE for the characters that are not to be remapped. * typebuf.tb_noremap[typebuf.tb_off] is the first valid flag. * (typebuf has been put in globals.h, because check_termcode() needs it). */ #define RM_YES 0 // tb_noremap: remap #define RM_NONE 1 // tb_noremap: don't remap #define RM_SCRIPT 2 // tb_noremap: remap local script mappings #define RM_ABBR 4 // tb_noremap: don't remap, do abbrev. // typebuf.tb_buf has three parts: room in front (for result of mappings), the // middle for typeahead and room for new characters (which needs to be 3 * // MAXMAPLEN for the Amiga). #define TYPELEN_INIT (5 * (MAXMAPLEN + 3)) static char_u typebuf_init[TYPELEN_INIT]; // initial typebuf.tb_buf static char_u noremapbuf_init[TYPELEN_INIT]; // initial typebuf.tb_noremap static size_t last_recorded_len = 0; // number of last recorded chars #ifdef INCLUDE_GENERATED_DECLARATIONS # include "getchar.c.generated.h" #endif /* * Free and clear a buffer. */ void free_buff(buffheader_T *buf) { buffblock_T *p, *np; for (p = buf->bh_first.b_next; p != NULL; p = np) { np = p->b_next; xfree(p); } buf->bh_first.b_next = NULL; buf->bh_curr = NULL; } /// Return the contents of a buffer as a single string. /// K_SPECIAL in the returned string is escaped. /// /// @param dozero count == zero is not an error static char_u *get_buffcont(buffheader_T *buffer, int dozero) { size_t count = 0; char_u *p = NULL; char_u *p2; // compute the total length of the string for (const buffblock_T *bp = buffer->bh_first.b_next; bp != NULL; bp = bp->b_next) { count += STRLEN(bp->b_str); } if (count || dozero) { p = xmalloc(count + 1); p2 = p; for (const buffblock_T *bp = buffer->bh_first.b_next; bp != NULL; bp = bp->b_next) { for (const char_u *str = bp->b_str; *str;) { *p2++ = *str++; } } *p2 = NUL; } return p; } /// Return the contents of the record buffer as a single string /// and clear the record buffer. /// K_SPECIAL in the returned string is escaped. char_u *get_recorded(void) { char_u *p; size_t len; p = get_buffcont(&recordbuff, TRUE); free_buff(&recordbuff); /* * Remove the characters that were added the last time, these must be the * (possibly mapped) characters that stopped the recording. */ len = STRLEN(p); if (len >= last_recorded_len) { len -= last_recorded_len; p[len] = NUL; } /* * When stopping recording from Insert mode with CTRL-O q, also remove the * CTRL-O. */ if (len > 0 && restart_edit != 0 && p[len - 1] == Ctrl_O) { p[len - 1] = NUL; } return p; } /// Return the contents of the redo buffer as a single string. /// K_SPECIAL in the returned string is escaped. char_u *get_inserted(void) { return get_buffcont(&redobuff, FALSE); } /// Add string after the current block of the given buffer /// /// K_SPECIAL should have been escaped already. /// /// @param[out] buf Buffer to add to. /// @param[in] s String to add. /// @param[in] slen String length or -1 for NUL-terminated string. static void add_buff(buffheader_T *const buf, const char *const s, ptrdiff_t slen) { if (slen < 0) { slen = (ptrdiff_t)strlen(s); } if (slen == 0) { // don't add empty strings return; } if (buf->bh_first.b_next == NULL) { // first add to list buf->bh_space = 0; buf->bh_curr = &(buf->bh_first); } else if (buf->bh_curr == NULL) { // buffer has already been read iemsg(_("E222: Add to read buffer")); return; } else if (buf->bh_index != 0) { memmove(buf->bh_first.b_next->b_str, buf->bh_first.b_next->b_str + buf->bh_index, STRLEN(buf->bh_first.b_next->b_str + buf->bh_index) + 1); } buf->bh_index = 0; size_t len; if (buf->bh_space >= (size_t)slen) { len = STRLEN(buf->bh_curr->b_str); STRLCPY(buf->bh_curr->b_str + len, s, slen + 1); buf->bh_space -= (size_t)slen; } else { if (slen < MINIMAL_SIZE) { len = MINIMAL_SIZE; } else { len = (size_t)slen; } buffblock_T *p = xmalloc(sizeof(buffblock_T) + len); buf->bh_space = len - (size_t)slen; STRLCPY(p->b_str, s, slen + 1); p->b_next = buf->bh_curr->b_next; buf->bh_curr->b_next = p; buf->bh_curr = p; } } /// Delete "slen" bytes from the end of "buf". /// Only works when it was just added. static void delete_buff_tail(buffheader_T *buf, int slen) { int len; if (buf->bh_curr == NULL) { return; // nothing to delete } len = (int)STRLEN(buf->bh_curr->b_str); if (len >= slen) { buf->bh_curr->b_str[len - slen] = NUL; buf->bh_space += (size_t)slen; } } /// Add number "n" to buffer "buf". static void add_num_buff(buffheader_T *buf, long n) { char number[32]; snprintf(number, sizeof(number), "%ld", n); add_buff(buf, number, -1L); } /// Add character 'c' to buffer "buf". /// Translates special keys, NUL, K_SPECIAL and multibyte characters. static void add_char_buff(buffheader_T *buf, int c) { uint8_t bytes[MB_MAXBYTES + 1]; int len; if (IS_SPECIAL(c)) { len = 1; } else { len = utf_char2bytes(c, (char *)bytes); } for (int i = 0; i < len; i++) { if (!IS_SPECIAL(c)) { c = bytes[i]; } char temp[4]; if (IS_SPECIAL(c) || c == K_SPECIAL || c == NUL) { // Translate special key code into three byte sequence. temp[0] = (char)K_SPECIAL; temp[1] = (char)K_SECOND(c); temp[2] = (char)K_THIRD(c); temp[3] = NUL; } else { temp[0] = (char)c; temp[1] = NUL; } add_buff(buf, temp, -1L); } } /// Get one byte from the read buffers. Use readbuf1 one first, use readbuf2 /// if that one is empty. /// If advance == TRUE go to the next char. /// No translation is done K_SPECIAL is escaped. static int read_readbuffers(int advance) { int c; c = read_readbuf(&readbuf1, advance); if (c == NUL) { c = read_readbuf(&readbuf2, advance); } return c; } static int read_readbuf(buffheader_T *buf, int advance) { char_u c; if (buf->bh_first.b_next == NULL) { // buffer is empty return NUL; } buffblock_T *const curr = buf->bh_first.b_next; c = curr->b_str[buf->bh_index]; if (advance) { if (curr->b_str[++buf->bh_index] == NUL) { buf->bh_first.b_next = curr->b_next; xfree(curr); buf->bh_index = 0; } } return c; } /* * Prepare the read buffers for reading (if they contain something). */ static void start_stuff(void) { if (readbuf1.bh_first.b_next != NULL) { readbuf1.bh_curr = &(readbuf1.bh_first); readbuf1.bh_space = 0; } if (readbuf2.bh_first.b_next != NULL) { readbuf2.bh_curr = &(readbuf2.bh_first); readbuf2.bh_space = 0; } } /* * Return TRUE if the stuff buffer is empty. */ int stuff_empty(void) FUNC_ATTR_PURE { return (readbuf1.bh_first.b_next == NULL && readbuf2.bh_first.b_next == NULL); } /* * Return TRUE if readbuf1 is empty. There may still be redo characters in * redbuf2. */ int readbuf1_empty(void) FUNC_ATTR_PURE { return (readbuf1.bh_first.b_next == NULL); } /* * Set a typeahead character that won't be flushed. */ void typeahead_noflush(int c) { typeahead_char = c; } /* * Remove the contents of the stuff buffer and the mapped characters in the * typeahead buffer (used in case of an error). If "flush_typeahead" is true, * flush all typeahead characters (used when interrupted by a CTRL-C). */ void flush_buffers(flush_buffers_T flush_typeahead) { init_typebuf(); start_stuff(); while (read_readbuffers(true) != NUL) {} if (flush_typeahead == FLUSH_MINIMAL) { // remove mapped characters at the start only typebuf.tb_off += typebuf.tb_maplen; typebuf.tb_len -= typebuf.tb_maplen; } else { // remove typeahead if (flush_typeahead == FLUSH_INPUT) { // We have to get all characters, because we may delete the first // part of an escape sequence. In an xterm we get one char at a // time and we have to get them all. while (inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 10L) != 0) {} } typebuf.tb_off = MAXMAPLEN; typebuf.tb_len = 0; // Reset the flag that text received from a client or from feedkeys() // was inserted in the typeahead buffer. typebuf_was_filled = false; } typebuf.tb_maplen = 0; typebuf.tb_silent = 0; cmd_silent = false; typebuf.tb_no_abbr_cnt = 0; if (++typebuf.tb_change_cnt == 0) { typebuf.tb_change_cnt = 1; } } /// flush map and typeahead buffers and give a warning for an error void beep_flush(void) { if (emsg_silent == 0) { flush_buffers(FLUSH_MINIMAL); vim_beep(BO_ERROR); } } /* * The previous contents of the redo buffer is kept in old_redobuffer. * This is used for the CTRL-O <.> command in insert mode. */ void ResetRedobuff(void) { if (!block_redo) { free_buff(&old_redobuff); old_redobuff = redobuff; redobuff.bh_first.b_next = NULL; } } /* * Discard the contents of the redo buffer and restore the previous redo * buffer. */ void CancelRedo(void) { if (!block_redo) { free_buff(&redobuff); redobuff = old_redobuff; old_redobuff.bh_first.b_next = NULL; start_stuff(); while (read_readbuffers(true) != NUL) {} } } /// Save redobuff and old_redobuff to save_redobuff and save_old_redobuff. /// Used before executing autocommands and user functions. void saveRedobuff(save_redo_T *save_redo) { save_redo->sr_redobuff = redobuff; redobuff.bh_first.b_next = NULL; save_redo->sr_old_redobuff = old_redobuff; old_redobuff.bh_first.b_next = NULL; // Make a copy, so that ":normal ." in a function works. char *const s = (char *)get_buffcont(&save_redo->sr_redobuff, false); if (s != NULL) { add_buff(&redobuff, s, -1L); xfree(s); } } /// Restore redobuff and old_redobuff from save_redobuff and save_old_redobuff. /// Used after executing autocommands and user functions. void restoreRedobuff(save_redo_T *save_redo) { free_buff(&redobuff); redobuff = save_redo->sr_redobuff; free_buff(&old_redobuff); old_redobuff = save_redo->sr_old_redobuff; } /// Append "s" to the redo buffer. /// K_SPECIAL should already have been escaped. void AppendToRedobuff(const char *s) { if (!block_redo) { add_buff(&redobuff, s, -1L); } } /// Append to Redo buffer literally, escaping special characters with CTRL-V. /// K_SPECIAL is escaped as well. /// /// @param str String to append /// @param len Length of `str` or -1 for up to the NUL. void AppendToRedobuffLit(const char *str, int len) { if (block_redo) { return; } const char *s = str; while (len < 0 ? *s != NUL : s - str < len) { // Put a string of normal characters in the redo buffer (that's // faster). const char *start = s; while (*s >= ' ' && *s < DEL && (len < 0 || s - str < len)) { s++; } // Don't put '0' or '^' as last character, just in case a CTRL-D is // typed next. if (*s == NUL && (s[-1] == '0' || s[-1] == '^')) { s--; } if (s > start) { add_buff(&redobuff, start, (long)(s - start)); } if (*s == NUL || (len >= 0 && s - str >= len)) { break; } // Handle a special or multibyte character. // Composing chars separately are handled separately. const int c = mb_cptr2char_adv((const char_u **)&s); if (c < ' ' || c == DEL || (*s == NUL && (c == '0' || c == '^'))) { add_char_buff(&redobuff, Ctrl_V); } // CTRL-V '0' must be inserted as CTRL-V 048. if (*s == NUL && c == '0') { add_buff(&redobuff, "048", 3L); } else { add_char_buff(&redobuff, c); } } } /// Append a character to the redo buffer. /// Translates special keys, NUL, K_SPECIAL and multibyte characters. void AppendCharToRedobuff(int c) { if (!block_redo) { add_char_buff(&redobuff, c); } } /* * Append a number to the redo buffer. */ void AppendNumberToRedobuff(long n) { if (!block_redo) { add_num_buff(&redobuff, n); } } /// Append string "s" to the stuff buffer. /// K_SPECIAL must already have been escaped. void stuffReadbuff(const char *s) { add_buff(&readbuf1, s, -1L); } /// Append string "s" to the redo stuff buffer. /// @remark K_SPECIAL must already have been escaped. void stuffRedoReadbuff(const char *s) { add_buff(&readbuf2, s, -1L); } void stuffReadbuffLen(const char *s, long len) { add_buff(&readbuf1, s, len); } /// Stuff "s" into the stuff buffer, leaving special key codes unmodified and /// escaping other K_SPECIAL bytes. /// Change CR, LF and ESC into a space. void stuffReadbuffSpec(const char *s) { while (*s != NUL) { if ((uint8_t)(*s) == K_SPECIAL && s[1] != NUL && s[2] != NUL) { // Insert special key literally. stuffReadbuffLen(s, 3); s += 3; } else { int c = mb_cptr2char_adv((const char_u **)&s); if (c == CAR || c == NL || c == ESC) { c = ' '; } stuffcharReadbuff(c); } } } /// Append a character to the stuff buffer. /// Translates special keys, NUL, K_SPECIAL and multibyte characters. void stuffcharReadbuff(int c) { add_char_buff(&readbuf1, c); } /* * Append a number to the stuff buffer. */ void stuffnumReadbuff(long n) { add_num_buff(&readbuf1, n); } /// Read a character from the redo buffer. Translates K_SPECIAL and /// multibyte characters. /// The redo buffer is left as it is. /// If init is true, prepare for redo, return FAIL if nothing to redo, OK /// otherwise. /// If old_redo is true, use old_redobuff instead of redobuff. static int read_redo(bool init, bool old_redo) { static buffblock_T *bp; static char_u *p; int c; int n; char_u buf[MB_MAXBYTES + 1]; int i; if (init) { bp = old_redo ? old_redobuff.bh_first.b_next : redobuff.bh_first.b_next; if (bp == NULL) { return FAIL; } p = bp->b_str; return OK; } if ((c = *p) == NUL) { return c; } // Reverse the conversion done by add_char_buff() // For a multi-byte character get all the bytes and return the // converted character. if (c != K_SPECIAL || p[1] == KS_SPECIAL) { n = MB_BYTE2LEN_CHECK(c); } else { n = 1; } for (i = 0;; i++) { if (c == K_SPECIAL) { // special key or escaped K_SPECIAL c = TO_SPECIAL(p[1], p[2]); p += 2; } if (*++p == NUL && bp->b_next != NULL) { bp = bp->b_next; p = bp->b_str; } buf[i] = (char_u)c; if (i == n - 1) { // last byte of a character if (n != 1) { c = utf_ptr2char((char *)buf); } break; } c = *p; if (c == NUL) { // cannot happen? break; } } return c; } /// Copy the rest of the redo buffer into the stuff buffer (in a slow way). /// If old_redo is true, use old_redobuff instead of redobuff. /// The escaped K_SPECIAL is copied without translation. static void copy_redo(bool old_redo) { int c; while ((c = read_redo(false, old_redo)) != NUL) { add_char_buff(&readbuf2, c); } } // Stuff the redo buffer into readbuf2. // Insert the redo count into the command. // If "old_redo" is true, the last but one command is repeated // instead of the last command (inserting text). This is used for // CTRL-O <.> in insert mode // // return FAIL for failure, OK otherwise int start_redo(long count, bool old_redo) { int c; // init the pointers; return if nothing to redo if (read_redo(true, old_redo) == FAIL) { return FAIL; } c = read_redo(false, old_redo); // copy the buffer name, if present if (c == '"') { add_buff(&readbuf2, "\"", 1L); c = read_redo(false, old_redo); // if a numbered buffer is used, increment the number if (c >= '1' && c < '9') { c++; } add_char_buff(&readbuf2, c); // the expression register should be re-evaluated if (c == '=') { add_char_buff(&readbuf2, CAR); cmd_silent = true; } c = read_redo(false, old_redo); } if (c == 'v') { // redo Visual VIsual = curwin->w_cursor; VIsual_active = true; VIsual_select = false; VIsual_reselect = true; redo_VIsual_busy = true; c = read_redo(false, old_redo); } // try to enter the count (in place of a previous count) if (count) { while (ascii_isdigit(c)) { // skip "old" count c = read_redo(false, old_redo); } add_num_buff(&readbuf2, count); } // copy from the redo buffer into the stuff buffer add_char_buff(&readbuf2, c); copy_redo(old_redo); return OK; } /* * Repeat the last insert (R, o, O, a, A, i or I command) by stuffing * the redo buffer into readbuf2. * return FAIL for failure, OK otherwise */ int start_redo_ins(void) { int c; if (read_redo(true, false) == FAIL) { return FAIL; } start_stuff(); // skip the count and the command character while ((c = read_redo(false, false)) != NUL) { if (vim_strchr("AaIiRrOo", c) != NULL) { if (c == 'O' || c == 'o') { add_buff(&readbuf2, NL_STR, -1L); } break; } } // copy the typed text from the redo buffer into the stuff buffer copy_redo(false); block_redo = true; return OK; } void stop_redo_ins(void) { block_redo = FALSE; } /* * Initialize typebuf.tb_buf to point to typebuf_init. * alloc() cannot be used here: In out-of-memory situations it would * be impossible to type anything. */ static void init_typebuf(void) { if (typebuf.tb_buf == NULL) { typebuf.tb_buf = typebuf_init; typebuf.tb_noremap = noremapbuf_init; typebuf.tb_buflen = TYPELEN_INIT; typebuf.tb_len = 0; typebuf.tb_off = MAXMAPLEN + 4; typebuf.tb_change_cnt = 1; } } /// @return true when keys cannot be remapped. bool noremap_keys(void) { return KeyNoremap & (RM_NONE|RM_SCRIPT); } // Insert a string in position 'offset' in the typeahead buffer (for "@r" // and ":normal" command, vgetorpeek() and check_termcode()) // // If noremap is REMAP_YES, new string can be mapped again. // If noremap is REMAP_NONE, new string cannot be mapped again. // If noremap is REMAP_SKIP, first char of new string cannot be mapped again, // but abbreviations are allowed. // If noremap is REMAP_SCRIPT, new string cannot be mapped again, except for // script-local mappings. // If noremap is > 0, that many characters of the new string cannot be mapped. // // If nottyped is true, the string does not return KeyTyped (don't use when // offset is non-zero!). // // If silent is true, cmd_silent is set when the characters are obtained. // // return FAIL for failure, OK otherwise int ins_typebuf(char *str, int noremap, int offset, bool nottyped, bool silent) { char_u *s1, *s2; int addlen; int i; int val; int nrm; init_typebuf(); if (++typebuf.tb_change_cnt == 0) { typebuf.tb_change_cnt = 1; } addlen = (int)STRLEN(str); if (offset == 0 && addlen <= typebuf.tb_off) { // Easy case: there is room in front of typebuf.tb_buf[typebuf.tb_off] typebuf.tb_off -= addlen; memmove(typebuf.tb_buf + typebuf.tb_off, str, (size_t)addlen); } else if (typebuf.tb_len == 0 && typebuf.tb_buflen >= addlen + 3 * (MAXMAPLEN + 4)) { // Buffer is empty and string fits in the existing buffer. // Leave some space before and after, if possible. typebuf.tb_off = (typebuf.tb_buflen - addlen - 3 * (MAXMAPLEN + 4)) / 2; memmove(typebuf.tb_buf + typebuf.tb_off, str, (size_t)addlen); } else { // Need to allocate a new buffer. // In typebuf.tb_buf there must always be room for 3 * (MAXMAPLEN + 4) // characters. We add some extra room to avoid having to allocate too // often. int newoff = MAXMAPLEN + 4; int extra = addlen + newoff + 4 * (MAXMAPLEN + 4); if (typebuf.tb_len > 2147483674 - extra) { // string is getting too long for 32 bit int emsg(_(e_toocompl)); // also calls flush_buffers setcursor(); return FAIL; } int newlen = typebuf.tb_len + extra; s1 = xmalloc((size_t)newlen); s2 = xmalloc((size_t)newlen); typebuf.tb_buflen = newlen; // copy the old chars, before the insertion point memmove(s1 + newoff, typebuf.tb_buf + typebuf.tb_off, (size_t)offset); // copy the new chars memmove(s1 + newoff + offset, str, (size_t)addlen); // copy the old chars, after the insertion point, including the NUL at // the end int bytes = typebuf.tb_len - offset + 1; assert(bytes > 0); memmove(s1 + newoff + offset + addlen, typebuf.tb_buf + typebuf.tb_off + offset, (size_t)bytes); if (typebuf.tb_buf != typebuf_init) { xfree(typebuf.tb_buf); } typebuf.tb_buf = s1; memmove(s2 + newoff, typebuf.tb_noremap + typebuf.tb_off, (size_t)offset); memmove(s2 + newoff + offset + addlen, typebuf.tb_noremap + typebuf.tb_off + offset, (size_t)(typebuf.tb_len - offset)); if (typebuf.tb_noremap != noremapbuf_init) { xfree(typebuf.tb_noremap); } typebuf.tb_noremap = s2; typebuf.tb_off = newoff; } typebuf.tb_len += addlen; // If noremap == REMAP_SCRIPT: do remap script-local mappings. if (noremap == REMAP_SCRIPT) { val = RM_SCRIPT; } else if (noremap == REMAP_SKIP) { val = RM_ABBR; } else { val = RM_NONE; } /* * Adjust typebuf.tb_noremap[] for the new characters: * If noremap == REMAP_NONE or REMAP_SCRIPT: new characters are * (sometimes) not remappable * If noremap == REMAP_YES: all the new characters are mappable * If noremap > 0: "noremap" characters are not remappable, the rest * mappable */ if (noremap == REMAP_SKIP) { nrm = 1; } else if (noremap < 0) { nrm = addlen; } else { nrm = noremap; } for (i = 0; i < addlen; ++i) { typebuf.tb_noremap[typebuf.tb_off + i + offset] = (char_u)((--nrm >= 0) ? val : RM_YES); } // tb_maplen and tb_silent only remember the length of mapped and/or // silent mappings at the start of the buffer, assuming that a mapped // sequence doesn't result in typed characters. if (nottyped || typebuf.tb_maplen > offset) { typebuf.tb_maplen += addlen; } if (silent || typebuf.tb_silent > offset) { typebuf.tb_silent += addlen; cmd_silent = true; } if (typebuf.tb_no_abbr_cnt && offset == 0) { // and not used for abbrev.s typebuf.tb_no_abbr_cnt += addlen; } return OK; } /// Put character "c" back into the typeahead buffer. /// Can be used for a character obtained by vgetc() that needs to be put back. /// Uses cmd_silent, KeyTyped and KeyNoremap to restore the flags belonging to /// the char. /// /// @return the length of what was inserted int ins_char_typebuf(int c, int modifiers) { char_u buf[MB_MAXBYTES * 3 + 4]; unsigned int len = special_to_buf(c, modifiers, true, buf); assert(len < sizeof(buf)); buf[len] = NUL; (void)ins_typebuf((char *)buf, KeyNoremap, 0, !KeyTyped, cmd_silent); return (int)len; } /// Return TRUE if the typeahead buffer was changed (while waiting for a /// character to arrive). Happens when a message was received from a client or /// from feedkeys(). /// But check in a more generic way to avoid trouble: When "typebuf.tb_buf" /// changed it was reallocated and the old pointer can no longer be used. /// Or "typebuf.tb_off" may have been changed and we would overwrite characters /// that was just added. /// /// @param tb_change_cnt old value of typebuf.tb_change_cnt bool typebuf_changed(int tb_change_cnt) FUNC_ATTR_PURE { return tb_change_cnt != 0 && (typebuf.tb_change_cnt != tb_change_cnt || typebuf_was_filled); } /* * Return TRUE if there are no characters in the typeahead buffer that have * not been typed (result from a mapping or come from ":normal"). */ int typebuf_typed(void) FUNC_ATTR_PURE { return typebuf.tb_maplen == 0; } /* * Return the number of characters that are mapped (or not typed). */ int typebuf_maplen(void) FUNC_ATTR_PURE { return typebuf.tb_maplen; } /* * remove "len" characters from typebuf.tb_buf[typebuf.tb_off + offset] */ void del_typebuf(int len, int offset) { int i; if (len == 0) { return; // nothing to do } typebuf.tb_len -= len; /* * Easy case: Just increase typebuf.tb_off. */ if (offset == 0 && typebuf.tb_buflen - (typebuf.tb_off + len) >= 3 * MAXMAPLEN + 3) { typebuf.tb_off += len; } /* * Have to move the characters in typebuf.tb_buf[] and typebuf.tb_noremap[] */ else { i = typebuf.tb_off + offset; /* * Leave some extra room at the end to avoid reallocation. */ if (typebuf.tb_off > MAXMAPLEN) { memmove(typebuf.tb_buf + MAXMAPLEN, typebuf.tb_buf + typebuf.tb_off, (size_t)offset); memmove(typebuf.tb_noremap + MAXMAPLEN, typebuf.tb_noremap + typebuf.tb_off, (size_t)offset); typebuf.tb_off = MAXMAPLEN; } // adjust typebuf.tb_buf (include the NUL at the end) int bytes = typebuf.tb_len - offset + 1; assert(bytes > 0); memmove(typebuf.tb_buf + typebuf.tb_off + offset, typebuf.tb_buf + i + len, (size_t)bytes); // adjust typebuf.tb_noremap[] memmove(typebuf.tb_noremap + typebuf.tb_off + offset, typebuf.tb_noremap + i + len, (size_t)(typebuf.tb_len - offset)); } if (typebuf.tb_maplen > offset) { // adjust tb_maplen if (typebuf.tb_maplen < offset + len) { typebuf.tb_maplen = offset; } else { typebuf.tb_maplen -= len; } } if (typebuf.tb_silent > offset) { // adjust tb_silent if (typebuf.tb_silent < offset + len) { typebuf.tb_silent = offset; } else { typebuf.tb_silent -= len; } } if (typebuf.tb_no_abbr_cnt > offset) { // adjust tb_no_abbr_cnt if (typebuf.tb_no_abbr_cnt < offset + len) { typebuf.tb_no_abbr_cnt = offset; } else { typebuf.tb_no_abbr_cnt -= len; } } // Reset the flag that text received from a client or from feedkeys() // was inserted in the typeahead buffer. typebuf_was_filled = false; if (++typebuf.tb_change_cnt == 0) { typebuf.tb_change_cnt = 1; } } /* * Write typed characters to script file. * If recording is on put the character in the recordbuffer. */ static void gotchars(const char_u *chars, size_t len) FUNC_ATTR_NONNULL_ALL { const char_u *s = chars; static char_u buf[4] = { 0 }; static size_t buflen = 0; size_t todo = len; while (todo--) { buf[buflen++] = *s++; // When receiving a special key sequence, store it until we have all // the bytes and we can decide what to do with it. if (buflen == 1 && buf[0] == K_SPECIAL) { continue; } if (buflen == 2) { continue; } // Handle one byte at a time; no translation to be done. for (size_t i = 0; i < buflen; i++) { updatescript(buf[i]); } if (reg_recording != 0) { buf[buflen] = NUL; add_buff(&recordbuff, (char *)buf, (ptrdiff_t)buflen); // remember how many chars were last recorded last_recorded_len += buflen; } buflen = 0; } may_sync_undo(); // output "debug mode" message next time in debug mode debug_did_msg = false; // Since characters have been typed, consider the following to be in // another mapping. Search string will be kept in history. maptick++; } /// Undo the last gotchars() for "len" bytes. To be used when putting a typed /// character back into the typeahead buffer, thus gotchars() will be called /// again. /// Only affects recorded characters. void ungetchars(int len) { if (reg_recording != 0) { delete_buff_tail(&recordbuff, len); last_recorded_len -= (size_t)len; } } /* * Sync undo. Called when typed characters are obtained from the typeahead * buffer, or when a menu is used. * Do not sync: * - In Insert mode, unless cursor key has been used. * - While reading a script file. * - When no_u_sync is non-zero. */ void may_sync_undo(void) { if ((!(State & (MODE_INSERT | MODE_CMDLINE)) || arrow_used) && scriptin[curscript] == NULL) { u_sync(false); } } /* * Make "typebuf" empty and allocate new buffers. */ void alloc_typebuf(void) { typebuf.tb_buf = xmalloc(TYPELEN_INIT); typebuf.tb_noremap = xmalloc(TYPELEN_INIT); typebuf.tb_buflen = TYPELEN_INIT; typebuf.tb_off = MAXMAPLEN + 4; // can insert without realloc typebuf.tb_len = 0; typebuf.tb_maplen = 0; typebuf.tb_silent = 0; typebuf.tb_no_abbr_cnt = 0; if (++typebuf.tb_change_cnt == 0) { typebuf.tb_change_cnt = 1; } } /* * Free the buffers of "typebuf". */ void free_typebuf(void) { if (typebuf.tb_buf == typebuf_init) { internal_error("Free typebuf 1"); } else { XFREE_CLEAR(typebuf.tb_buf); } if (typebuf.tb_noremap == noremapbuf_init) { internal_error("Free typebuf 2"); } else { XFREE_CLEAR(typebuf.tb_noremap); } } /* * When doing ":so! file", the current typeahead needs to be saved, and * restored when "file" has been read completely. */ static typebuf_T saved_typebuf[NSCRIPT]; void save_typebuf(void) { init_typebuf(); saved_typebuf[curscript] = typebuf; alloc_typebuf(); } static int old_char = -1; // character put back by vungetc() static int old_mod_mask; // mod_mask for ungotten character static int old_mouse_grid; // mouse_grid related to old_char static int old_mouse_row; // mouse_row related to old_char static int old_mouse_col; // mouse_col related to old_char static int old_KeyStuffed; // whether old_char was stuffed static bool can_get_old_char(void) { // If the old character was not stuffed and characters have been added to // the stuff buffer, need to first get the stuffed characters instead. return old_char != -1 && (old_KeyStuffed || stuff_empty()); } /* * Save all three kinds of typeahead, so that the user must type at a prompt. */ void save_typeahead(tasave_T *tp) { tp->save_typebuf = typebuf; alloc_typebuf(); tp->typebuf_valid = true; tp->old_char = old_char; tp->old_mod_mask = old_mod_mask; old_char = -1; tp->save_readbuf1 = readbuf1; readbuf1.bh_first.b_next = NULL; tp->save_readbuf2 = readbuf2; readbuf2.bh_first.b_next = NULL; } /* * Restore the typeahead to what it was before calling save_typeahead(). * The allocated memory is freed, can only be called once! */ void restore_typeahead(tasave_T *tp) { if (tp->typebuf_valid) { free_typebuf(); typebuf = tp->save_typebuf; } old_char = tp->old_char; old_mod_mask = tp->old_mod_mask; free_buff(&readbuf1); readbuf1 = tp->save_readbuf1; free_buff(&readbuf2); readbuf2 = tp->save_readbuf2; } /// Open a new script file for the ":source!" command. /// /// @param directly when true execute directly void openscript(char_u *name, bool directly) { if (curscript + 1 == NSCRIPT) { emsg(_(e_nesting)); return; } // Disallow sourcing a file in the sandbox, the commands would be executed // later, possibly outside of the sandbox. if (check_secure()) { return; } if (ignore_script) { // Not reading from script, also don't open one. Warning message? return; } if (scriptin[curscript] != NULL) { // already reading script curscript++; } // use NameBuff for expanded name expand_env(name, NameBuff, MAXPATHL); int error; if ((scriptin[curscript] = file_open_new(&error, (char *)NameBuff, kFileReadOnly, 0)) == NULL) { semsg(_(e_notopen_2), name, os_strerror(error)); if (curscript) { curscript--; } return; } save_typebuf(); /* * Execute the commands from the file right now when using ":source!" * after ":global" or ":argdo" or in a loop. Also when another command * follows. This means the display won't be updated. Don't do this * always, "make test" would fail. */ if (directly) { oparg_T oa; int oldcurscript; int save_State = State; int save_restart_edit = restart_edit; int save_finish_op = finish_op; int save_msg_scroll = msg_scroll; State = MODE_NORMAL; msg_scroll = false; // no msg scrolling in Normal mode restart_edit = 0; // don't go to Insert mode clear_oparg(&oa); finish_op = false; oldcurscript = curscript; do { update_topline_cursor(); // update cursor position and topline normal_cmd(&oa, false); // execute one command (void)vpeekc(); // check for end of file } while (scriptin[oldcurscript] != NULL); State = save_State; msg_scroll = save_msg_scroll; restart_edit = save_restart_edit; finish_op = save_finish_op; } } /* * Close the currently active input script. */ static void closescript(void) { free_typebuf(); typebuf = saved_typebuf[curscript]; file_free(scriptin[curscript], false); scriptin[curscript] = NULL; if (curscript > 0) { curscript--; } } #if defined(EXITFREE) void close_all_scripts(void) { while (scriptin[0] != NULL) { closescript(); } } #endif /* * Return TRUE when reading keys from a script file. */ int using_script(void) FUNC_ATTR_PURE { return scriptin[curscript] != NULL; } /// This function is called just before doing a blocking wait. Thus after /// waiting 'updatetime' for a character to arrive. void before_blocking(void) { updatescript(0); if (may_garbage_collect) { garbage_collect(false); } } /// updatescript() is called when a character can be written to the script file /// or when we have waited some time for a character (c == 0). /// /// All the changed memfiles are synced if c == 0 or when the number of typed /// characters reaches 'updatecount' and 'updatecount' is non-zero. static void updatescript(int c) { static int count = 0; if (c && scriptout) { putc(c, scriptout); } bool idle = (c == 0); if (idle || (p_uc > 0 && ++count >= p_uc)) { ml_sync_all(idle, true, (!!p_fs || idle)); // Always fsync at idle (CursorHold). count = 0; } } /// Merge "modifiers" into "c_arg". int merge_modifiers(int c_arg, int *modifiers) { int c = c_arg; if (*modifiers & MOD_MASK_CTRL) { if ((c >= '`' && c <= 0x7f) || (c >= '@' && c <= '_')) { c &= 0x1f; if (c == NUL) { c = K_ZERO; } } else if (c == '6') { // CTRL-6 is equivalent to CTRL-^ c = 0x1e; } if (c != c_arg) { *modifiers &= ~MOD_MASK_CTRL; } } return c; } /// Get the next input character. /// Can return a special key or a multi-byte character. /// Can return NUL when called recursively, use safe_vgetc() if that's not /// wanted. /// This translates escaped K_SPECIAL bytes to a K_SPECIAL byte. /// Collects the bytes of a multibyte character into the whole character. /// Returns the modifiers in the global "mod_mask". int vgetc(void) { int c, c2; int n; char_u buf[MB_MAXBYTES + 1]; int i; // Do garbage collection when garbagecollect() was called previously and // we are now at the toplevel. if (may_garbage_collect && want_garbage_collect) { garbage_collect(false); } /* * If a character was put back with vungetc, it was already processed. * Return it directly. */ if (can_get_old_char()) { c = old_char; old_char = -1; mod_mask = old_mod_mask; mouse_grid = old_mouse_grid; mouse_row = old_mouse_row; mouse_col = old_mouse_col; } else { // number of characters recorded from the last vgetc() call static size_t last_vgetc_recorded_len = 0; mod_mask = 0; vgetc_mod_mask = 0; vgetc_char = 0; // last_recorded_len can be larger than last_vgetc_recorded_len // if peeking records more last_recorded_len -= last_vgetc_recorded_len; for (;;) { // this is done twice if there are modifiers bool did_inc = false; if (mod_mask) { // no mapping after modifier has been read no_mapping++; allow_keys++; did_inc = true; // mod_mask may change value } c = vgetorpeek(true); if (did_inc) { no_mapping--; allow_keys--; } // Get two extra bytes for special keys if (c == K_SPECIAL) { int save_allow_keys = allow_keys; no_mapping++; allow_keys = 0; // make sure BS is not found c2 = vgetorpeek(true); // no mapping for these chars c = vgetorpeek(true); no_mapping--; allow_keys = save_allow_keys; if (c2 == KS_MODIFIER) { mod_mask = c; continue; } c = TO_SPECIAL(c2, c); } // 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_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_KUP: case K_XUP: c = K_UP; break; case K_KDOWN: case K_XDOWN: c = K_DOWN; break; case K_KLEFT: case K_XLEFT: c = K_LEFT; break; case K_KRIGHT: case K_XRIGHT: c = K_RIGHT; break; } // For a multi-byte character get all the bytes and return the // converted character. // Note: This will loop until enough bytes are received! if ((n = MB_BYTE2LEN_CHECK(c)) > 1) { no_mapping++; buf[0] = (char_u)c; for (i = 1; i < n; i++) { buf[i] = (char_u)vgetorpeek(true); if (buf[i] == K_SPECIAL) { // Must be a K_SPECIAL - KS_SPECIAL - KE_FILLER sequence, // which represents a K_SPECIAL (0x80). (void)vgetorpeek(true); // skip KS_SPECIAL (void)vgetorpeek(true); // skip KE_FILLER } } no_mapping--; c = utf_ptr2char((char *)buf); } if (vgetc_char == 0) { vgetc_mod_mask = mod_mask; vgetc_char = c; } // If mappings are enabled (i.e., not i_CTRL-V) and the user directly typed something with // MOD_MASK_ALT ( as x rather // than as an unbound keypress. #8213 // In Terminal mode, however, this is not desirable. #16202 #16220 // Also do not do this for mouse keys, as terminals encode mouse events as CSI sequences, and // MOD_MASK_ALT has a meaning even for unmapped mouse keys. if (!no_mapping && KeyTyped && mod_mask == MOD_MASK_ALT && !(State & MODE_TERMINAL) && !is_mouse_key(c)) { mod_mask = 0; int len = ins_char_typebuf(c, 0); (void)ins_char_typebuf(ESC, 0); ungetchars(len + 3); // K_SPECIAL KS_MODIFIER MOD_MASK_ALT takes 3 more bytes continue; } break; } last_vgetc_recorded_len = last_recorded_len; } /* * In the main loop "may_garbage_collect" can be set to do garbage * collection in the first next vgetc(). It's disabled after that to * avoid internally used Lists and Dicts to be freed. */ may_garbage_collect = false; // Execute Lua on_key callbacks. nlua_execute_on_key(c); return c; } /* * Like vgetc(), but never return a NUL when called recursively, get a key * directly from the user (ignoring typeahead). */ int safe_vgetc(void) { int c; c = vgetc(); if (c == NUL) { c = get_keystroke(NULL); } return c; } /* * Like safe_vgetc(), but loop to handle K_IGNORE. * Also ignore scrollbar events. */ int plain_vgetc(void) { int c; do { c = safe_vgetc(); } while (c == K_IGNORE || c == K_VER_SCROLLBAR || c == K_HOR_SCROLLBAR || c == K_MOUSEMOVE); return c; } /* * Check if a character is available, such that vgetc() will not block. * If the next character is a special character or multi-byte, the returned * character is not valid!. * Returns NUL if no character is available. */ int vpeekc(void) { if (can_get_old_char()) { return old_char; } return vgetorpeek(false); } /* * Check if any character is available, also half an escape sequence. * Trick: when no typeahead found, but there is something in the typeahead * buffer, it must be an ESC that is recognized as the start of a key code. */ int vpeekc_any(void) { int c; c = vpeekc(); if (c == NUL && typebuf.tb_len > 0) { c = ESC; } return c; } /* * Call vpeekc() without causing anything to be mapped. * Return TRUE if a character is available, FALSE otherwise. */ int char_avail(void) { int retval; no_mapping++; retval = vpeekc(); no_mapping--; return retval != NUL; } /// "getchar()" and "getcharstr()" functions static void getchar_common(typval_T *argvars, typval_T *rettv) FUNC_ATTR_NONNULL_ALL { varnumber_T n; bool error = false; no_mapping++; allow_keys++; for (;;) { // Position the cursor. Needed after a message that ends in a space, // or if event processing caused a redraw. ui_cursor_goto(msg_row, msg_col); if (argvars[0].v_type == VAR_UNKNOWN) { // getchar(): blocking wait. // TODO(bfredl): deduplicate shared logic with state_enter ? if (!char_avail()) { // flush output before waiting ui_flush(); (void)os_inchar(NULL, 0, -1, 0, main_loop.events); if (!multiqueue_empty(main_loop.events)) { state_handle_k_event(); continue; } } n = safe_vgetc(); } else if (tv_get_number_chk(&argvars[0], &error) == 1) { // getchar(1): only check if char avail n = vpeekc_any(); } else if (error || vpeekc_any() == NUL) { // illegal argument or getchar(0) and no char avail: return zero n = 0; } else { // getchar(0) and char avail() != NUL: get a character. // Note that vpeekc_any() returns K_SPECIAL for K_IGNORE. n = safe_vgetc(); } if (n == K_IGNORE || n == K_MOUSEMOVE || n == K_VER_SCROLLBAR || n == K_HOR_SCROLLBAR) { continue; } break; } no_mapping--; allow_keys--; if (!ui_has_messages()) { // redraw the screen after getchar() update_screen(CLEAR); } set_vim_var_nr(VV_MOUSE_WIN, 0); set_vim_var_nr(VV_MOUSE_WINID, 0); set_vim_var_nr(VV_MOUSE_LNUM, 0); set_vim_var_nr(VV_MOUSE_COL, 0); rettv->vval.v_number = n; if (n != 0 && (IS_SPECIAL(n) || mod_mask != 0)) { char_u temp[10]; // modifier: 3, mbyte-char: 6, NUL: 1 int i = 0; // Turn a special key into three bytes, plus modifier. if (mod_mask != 0) { temp[i++] = K_SPECIAL; temp[i++] = KS_MODIFIER; temp[i++] = (char_u)mod_mask; } if (IS_SPECIAL(n)) { temp[i++] = K_SPECIAL; temp[i++] = (char_u)K_SECOND(n); temp[i++] = K_THIRD(n); } else { i += utf_char2bytes((int)n, (char *)temp + i); } assert(i < 10); temp[i++] = NUL; rettv->v_type = VAR_STRING; rettv->vval.v_string = (char *)vim_strsave(temp); if (is_mouse_key((int)n)) { int row = mouse_row; int col = mouse_col; int grid = mouse_grid; linenr_T lnum; win_T *wp; int winnr = 1; if (row >= 0 && col >= 0) { // Find the window at the mouse coordinates and compute the // text position. win_T *const win = mouse_find_win(&grid, &row, &col); if (win == NULL) { return; } (void)mouse_comp_pos(win, &row, &col, &lnum); for (wp = firstwin; wp != win; wp = wp->w_next) { winnr++; } set_vim_var_nr(VV_MOUSE_WIN, winnr); set_vim_var_nr(VV_MOUSE_WINID, wp->handle); set_vim_var_nr(VV_MOUSE_LNUM, lnum); set_vim_var_nr(VV_MOUSE_COL, col + 1); } } } } /// "getchar()" function void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { getchar_common(argvars, rettv); } /// "getcharstr()" function void f_getcharstr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { getchar_common(argvars, rettv); if (rettv->v_type == VAR_NUMBER) { char temp[7]; // mbyte-char: 6, NUL: 1 const varnumber_T n = rettv->vval.v_number; int i = 0; if (n != 0) { i += utf_char2bytes((int)n, (char *)temp); } assert(i < 7); temp[i++] = NUL; rettv->v_type = VAR_STRING; rettv->vval.v_string = xstrdup(temp); } } /// "getcharmod()" function void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = mod_mask; } typedef enum { map_result_fail, // failed, break loop map_result_get, // get a character from typeahead map_result_retry, // try to map again map_result_nomatch, // no matching mapping, get char } map_result_T; /// Put "string[new_slen]" in typebuf. /// Remove "slen" bytes. /// @return FAIL for error, OK otherwise. static int put_string_in_typebuf(int offset, int slen, char_u *string, int new_slen) { int extra = new_slen - slen; string[new_slen] = NUL; if (extra < 0) { // remove matched chars, taking care of noremap del_typebuf(-extra, offset); } else if (extra > 0) { // insert the extra space we need if (ins_typebuf((char *)string + slen, REMAP_YES, offset, false, false) == FAIL) { return FAIL; } } // Careful: del_typebuf() and ins_typebuf() may have reallocated // typebuf.tb_buf[]! memmove(typebuf.tb_buf + typebuf.tb_off + offset, string, (size_t)new_slen); return OK; } /// Check if the bytes at the start of the typeahead buffer are a character used /// in Insert mode completion. This includes the form with a CTRL modifier. static bool at_ins_compl_key(void) { char_u *p = typebuf.tb_buf + typebuf.tb_off; int c = *p; if (typebuf.tb_len > 3 && c == K_SPECIAL && p[1] == KS_MODIFIER && (p[2] & MOD_MASK_CTRL)) { c = p[3] & 0x1f; } return (ctrl_x_mode_not_default() && vim_is_ctrl_x_key(c)) || ((compl_cont_status & CONT_LOCAL) && (c == Ctrl_N || c == Ctrl_P)); } /// Check if typebuf.tb_buf[] contains a modifier plus key that can be changed /// into just a key, apply that. /// Check from typebuf.tb_buf[typebuf.tb_off] to typebuf.tb_buf[typebuf.tb_off + "max_offset"]. /// @return the length of the replaced bytes, 0 if nothing changed, -1 for error. static int check_simplify_modifier(int max_offset) { for (int offset = 0; offset < max_offset; offset++) { if (offset + 3 >= typebuf.tb_len) { break; } char_u *tp = typebuf.tb_buf + typebuf.tb_off + offset; if (tp[0] == K_SPECIAL && tp[1] == KS_MODIFIER) { // A modifier was not used for a mapping, apply it to ASCII // keys. Shift would already have been applied. int modifier = tp[2]; int c = tp[3]; int new_c = merge_modifiers(c, &modifier); if (new_c != c) { if (offset == 0) { // At the start: remember the character and mod_mask before // merging, in some cases, e.g. at the hit-return prompt, // they are put back in the typeahead buffer. vgetc_char = c; vgetc_mod_mask = tp[2]; } char_u new_string[MB_MAXBYTES]; int len; if (IS_SPECIAL(new_c)) { new_string[0] = K_SPECIAL; new_string[1] = (char_u)K_SECOND(new_c); new_string[2] = (char_u)K_THIRD(new_c); len = 3; } else { len = utf_char2bytes(new_c, (char *)new_string); } if (modifier == 0) { if (put_string_in_typebuf(offset, 4, new_string, len) == FAIL) { return -1; } } else { tp[2] = (char_u)modifier; if (put_string_in_typebuf(offset + 3, 1, new_string, len) == FAIL) { return -1; } } return len; } } } return 0; } /// Handle mappings in the typeahead buffer. /// - When something was mapped, return map_result_retry for recursive mappings. /// - When nothing mapped and typeahead has a character: return map_result_get. /// - When there is no match yet, return map_result_nomatch, need to get more /// typeahead. /// - On failure (out of memory) return map_result_fail. static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) { mapblock_T *mp = NULL; mapblock_T *mp2; mapblock_T *mp_match; int mp_match_len = 0; int max_mlen = 0; int tb_c1; int mlen; int nolmaplen; int keylen = *keylenp; int i; int local_State = get_real_state(); bool is_plug_map = false; // If typeahead starts with then remap, even for a "noremap" mapping. if (typebuf.tb_len >= 3 && typebuf.tb_buf[typebuf.tb_off] == K_SPECIAL && typebuf.tb_buf[typebuf.tb_off + 1] == KS_EXTRA && typebuf.tb_buf[typebuf.tb_off + 2] == KE_PLUG) { is_plug_map = true; } // Check for a mappable key sequence. // Walk through one maphash[] list until we find an entry that matches. // // Don't look for mappings if: // - no_mapping set: mapping disabled (e.g. for CTRL-V) // - maphash_valid not set: no mappings present. // - typebuf.tb_buf[typebuf.tb_off] should not be remapped // - in insert or cmdline mode and 'paste' option set // - waiting for "hit return to continue" and CR or SPACE typed // - waiting for a char with --more-- // - in Ctrl-X mode, and we get a valid char for that mode tb_c1 = typebuf.tb_buf[typebuf.tb_off]; if (no_mapping == 0 && (no_zero_mapping == 0 || tb_c1 != '0') && (typebuf.tb_maplen == 0 || is_plug_map || (!(typebuf.tb_noremap[typebuf.tb_off] & (RM_NONE|RM_ABBR)))) && !(p_paste && (State & (MODE_INSERT | MODE_CMDLINE))) && !(State == MODE_HITRETURN && (tb_c1 == CAR || tb_c1 == ' ')) && State != MODE_ASKMORE && State != MODE_CONFIRM && !at_ins_compl_key()) { if (tb_c1 == K_SPECIAL) { nolmaplen = 2; } else { LANGMAP_ADJUST(tb_c1, ((State & (MODE_CMDLINE | MODE_INSERT)) == 0 && get_real_state() != MODE_SELECT)); nolmaplen = 0; } // First try buffer-local mappings. mp = get_buf_maphash_list(local_State, tb_c1); mp2 = get_maphash_list(local_State, tb_c1); if (mp == NULL) { // There are no buffer-local mappings. mp = mp2; mp2 = NULL; } // Loop until a partly matching mapping is found or all (local) // mappings have been checked. // The longest full match is remembered in "mp_match". // A full match is only accepted if there is no partly match, so "aa" // and "aaa" can both be mapped. mp_match = NULL; mp_match_len = 0; for (; mp != NULL; mp->m_next == NULL ? (mp = mp2, mp2 = NULL) : (mp = mp->m_next)) { // Only consider an entry if the first character matches and it is // for the current state. // Skip ":lmap" mappings if keys were mapped. if (mp->m_keys[0] == tb_c1 && (mp->m_mode & local_State) && ((mp->m_mode & MODE_LANGMAP) == 0 || typebuf.tb_maplen == 0)) { int nomap = nolmaplen; int c2; // find the match length of this mapping for (mlen = 1; mlen < typebuf.tb_len; mlen++) { c2 = typebuf.tb_buf[typebuf.tb_off + mlen]; if (nomap > 0) { nomap--; } else if (c2 == K_SPECIAL) { nomap = 2; } else { LANGMAP_ADJUST(c2, true); } if (mp->m_keys[mlen] != c2) { break; } } // Don't allow mapping the first byte(s) of a multi-byte char. // Happens when mapping and then changing 'encoding'. // Beware that 0x80 is escaped. char_u *p1 = mp->m_keys; char_u *p2 = (char_u *)mb_unescape((const char **)&p1); if (p2 != NULL && MB_BYTE2LEN(tb_c1) > utfc_ptr2len((char *)p2)) { mlen = 0; } // Check an entry whether it matches. // - Full match: mlen == keylen // - Partly match: mlen == typebuf.tb_len keylen = mp->m_keylen; if (mlen == keylen || (mlen == typebuf.tb_len && typebuf.tb_len < keylen)) { char_u *s; int n; // If only script-local mappings are allowed, check if the // mapping starts with K_SNR. s = typebuf.tb_noremap + typebuf.tb_off; if (*s == RM_SCRIPT && (mp->m_keys[0] != K_SPECIAL || mp->m_keys[1] != KS_EXTRA || mp->m_keys[2] != KE_SNR)) { continue; } // If one of the typed keys cannot be remapped, skip the entry. for (n = mlen; --n >= 0;) { if (*s++ & (RM_NONE|RM_ABBR)) { break; } } if (!is_plug_map && n >= 0) { continue; } if (keylen > typebuf.tb_len) { if (!*timedout && !(mp_match != NULL && mp_match->m_nowait)) { // break at a partly match keylen = KEYLEN_PART_MAP; break; } } else if (keylen > mp_match_len || (keylen == mp_match_len && mp_match != NULL && (mp_match->m_mode & MODE_LANGMAP) == 0 && (mp->m_mode & MODE_LANGMAP) != 0)) { // found a longer match mp_match = mp; mp_match_len = keylen; } } else { // No match; may have to check for termcode at next character. if (max_mlen < mlen) { max_mlen = mlen; } } } } // If no partly match found, use the longest full match. if (keylen != KEYLEN_PART_MAP && mp_match != NULL) { mp = mp_match; keylen = mp_match_len; } } // Check for match with 'pastetoggle' if (*p_pt != NUL && mp == NULL && (State & (MODE_INSERT | MODE_NORMAL))) { bool match = typebuf_match_len(p_pt, &mlen); if (match) { // write chars to script file(s) if (mlen > typebuf.tb_maplen) { gotchars(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_maplen, (size_t)(mlen - typebuf.tb_maplen)); } del_typebuf(mlen, 0); // remove the chars set_option_value("paste", !p_paste, NULL, 0); if (!(State & MODE_INSERT)) { msg_col = 0; msg_row = Rows - 1; msg_clr_eos(); // clear ruler } status_redraw_all(); redraw_statuslines(); showmode(); setcursor(); *keylenp = keylen; return map_result_retry; } // Need more chars for partly match. if (mlen == typebuf.tb_len) { keylen = KEYLEN_PART_KEY; } else if (max_mlen < mlen) { // no match, may have to check for termcode at next character max_mlen = mlen + 1; } } if ((mp == NULL || max_mlen > mp_match_len) && keylen != KEYLEN_PART_MAP) { // When no matching mapping found or found a non-matching mapping that // matches at least what the matching mapping matched: // Try to include the modifier into the key when mapping is allowed. if (no_mapping == 0 || allow_keys != 0) { if (tb_c1 == K_SPECIAL && (typebuf.tb_len < 2 || (typebuf.tb_buf[typebuf.tb_off + 1] == KS_MODIFIER && typebuf.tb_len < 4))) { // Incomplete modifier sequence: cannot decide whether to simplify yet. keylen = KEYLEN_PART_KEY; } else if (keylen == KEYLEN_PART_KEY && !*timedout) { // If 'pastetoggle' matched partially, don't simplify. // When the last characters were not typed, don't wait for a typed character to // complete 'pastetoggle'. if (typebuf.tb_len == typebuf.tb_maplen) { keylen = 0; } } else { // Try to include the modifier into the key. keylen = check_simplify_modifier(max_mlen + 1); if (keylen < 0) { // ins_typebuf() failed return map_result_fail; } } } else { keylen = 0; } if (keylen == 0) { // no simplication has been done // If there was no mapping at all use the character from the // typeahead buffer right here. if (mp == NULL) { *keylenp = keylen; return map_result_get; // get character from typeahead } } if (keylen > 0) { // keys have been simplified *keylenp = keylen; return map_result_retry; // try mapping again } if (keylen < 0) { // Incomplete key sequence: get some more characters. assert(keylen == KEYLEN_PART_KEY); } else { assert(mp != NULL); // When a matching mapping was found use that one. keylen = mp_match_len; } } // complete match if (keylen >= 0 && keylen <= typebuf.tb_len) { char_u *map_str = NULL; // Write chars to script file(s). // Note: :lmap mappings are written *after* being applied. #5658 if (keylen > typebuf.tb_maplen && (mp->m_mode & MODE_LANGMAP) == 0) { gotchars(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_maplen, (size_t)(keylen - typebuf.tb_maplen)); } cmd_silent = (typebuf.tb_silent > 0); del_typebuf(keylen, 0); // remove the mapped keys // Put the replacement string in front of mapstr. // The depth check catches ":map x y" and ":map y x". if (++*mapdepth >= p_mmd) { emsg(_("E223: recursive mapping")); if (State & MODE_CMDLINE) { redrawcmdline(); } else { setcursor(); } flush_buffers(FLUSH_MINIMAL); *mapdepth = 0; // for next one *keylenp = keylen; return map_result_fail; } // In Select mode and a Visual mode mapping is used: Switch to Visual // mode temporarily. Append K_SELECT to switch back to Select mode. if (VIsual_active && VIsual_select && (mp->m_mode & MODE_VISUAL)) { VIsual_select = false; (void)ins_typebuf((char *)K_SELECT_STRING, REMAP_NONE, 0, true, false); } // Copy the values from *mp that are used, because evaluating the // expression may invoke a function that redefines the mapping, thereby // making *mp invalid. char save_m_expr = mp->m_expr; int save_m_noremap = mp->m_noremap; char save_m_silent = mp->m_silent; char_u *save_m_keys = NULL; // only saved when needed char_u *save_m_str = NULL; // only saved when needed LuaRef save_m_luaref = mp->m_luaref; // Handle ":map ": evaluate the {rhs} as an // expression. Also save and restore the command line // for "normal :". if (mp->m_expr) { const int save_vgetc_busy = vgetc_busy; const bool save_may_garbage_collect = may_garbage_collect; const int save_cursor_row = ui_current_row(); const int save_cursor_col = ui_current_col(); const handle_T save_cursor_grid = ui_cursor_grid(); const int prev_did_emsg = did_emsg; vgetc_busy = 0; may_garbage_collect = false; save_m_keys = vim_strsave(mp->m_keys); if (save_m_luaref == LUA_NOREF) { save_m_str = vim_strsave(mp->m_str); } map_str = eval_map_expr(mp, NUL); // The mapping may do anything, but we expect it to take care of // redrawing. Do put the cursor back where it was. ui_grid_cursor_goto(save_cursor_grid, save_cursor_row, save_cursor_col); ui_flush(); // If an error was displayed and the expression returns an empty // string, generate a to allow for a redraw. if (prev_did_emsg != did_emsg && (map_str == NULL || *map_str == NUL)) { char_u buf[4]; xfree(map_str); buf[0] = K_SPECIAL; buf[1] = KS_EXTRA; buf[2] = KE_IGNORE; buf[3] = NUL; map_str = vim_strsave(buf); if (State & MODE_CMDLINE) { // redraw the command below the error msg_didout = true; if (msg_row < cmdline_row) { msg_row = cmdline_row; } redrawcmd(); } } vgetc_busy = save_vgetc_busy; may_garbage_collect = save_may_garbage_collect; } else { map_str = mp->m_str; } // Insert the 'to' part in the typebuf.tb_buf. // If 'from' field is the same as the start of the 'to' field, don't // remap the first character (but do allow abbreviations). // If m_noremap is set, don't remap the whole 'to' part. if (map_str == NULL) { i = FAIL; } else { int noremap; // If this is a LANGMAP mapping, then we didn't record the keys // at the start of the function and have to record them now. if (keylen > typebuf.tb_maplen && (mp->m_mode & MODE_LANGMAP) != 0) { gotchars(map_str, STRLEN(map_str)); } if (save_m_noremap != REMAP_YES) { noremap = save_m_noremap; } else if (STRNCMP(map_str, save_m_keys != NULL ? save_m_keys : mp->m_keys, (size_t)keylen) != 0) { noremap = REMAP_YES; } else { noremap = REMAP_SKIP; } i = ins_typebuf((char *)map_str, noremap, 0, true, cmd_silent || save_m_silent); if (save_m_expr) { xfree(map_str); } } xfree(save_m_keys); xfree(save_m_str); *keylenp = keylen; if (i == FAIL) { return map_result_fail; } return map_result_retry; } *keylenp = keylen; return map_result_nomatch; } /// unget one character (can only be done once!) /// If the character was stuffed, vgetc() will get it next time it is called. /// Otherwise vgetc() will only get it when the stuff buffer is empty. void vungetc(int c) { old_char = c; old_mod_mask = mod_mask; old_mouse_grid = mouse_grid; old_mouse_row = mouse_row; old_mouse_col = mouse_col; old_KeyStuffed = KeyStuffed; } /// When peeking and not getting a character, reg_executing cannot be cleared /// yet, so set a flag to clear it later. void check_end_reg_executing(bool advance) { if (reg_executing != 0 && (typebuf.tb_maplen == 0 || pending_end_reg_executing)) { if (advance) { reg_executing = 0; pending_end_reg_executing = false; } else { pending_end_reg_executing = true; } } } /// Gets a byte: /// 1. from the stuffbuffer /// This is used for abbreviated commands like "D" -> "d$". /// Also used to redo a command for ".". /// 2. from the typeahead buffer /// Stores text obtained previously but not used yet. /// Also stores the result of mappings. /// Also used for the ":normal" command. /// 3. from the user /// This may do a blocking wait if "advance" is true. /// /// if "advance" is true (vgetc()): /// Really get the character. /// KeyTyped is set to TRUE in the case the user typed the key. /// KeyStuffed is TRUE if the character comes from the stuff buffer. /// if "advance" is false (vpeekc()): /// Just look whether there is a character available. /// Return NUL if not. /// /// When `no_mapping` (global) is zero, checks for mappings in the current mode. /// Only returns one byte (of a multi-byte character). /// K_SPECIAL may be escaped, need to get two more bytes then. static int vgetorpeek(bool advance) { int c, c1; bool timedout = false; // waited for more than 1 second for mapping to complete int mapdepth = 0; // check for recursive mapping bool mode_deleted = false; // set when mode has been deleted int new_wcol, new_wrow; int n; int old_wcol, old_wrow; int wait_tb_len; // This function doesn't work very well when called recursively. This may // happen though, because of: // 1. The call to add_to_showcmd(). char_avail() is then used to check if // there is a character available, which calls this function. In that // case we must return NUL, to indicate no character is available. // 2. A GUI callback function writes to the screen, causing a // wait_return(). // Using ":normal" can also do this, but it saves the typeahead buffer, // thus it should be OK. But don't get a key from the user then. if (vgetc_busy > 0 && ex_normal_busy == 0) { return NUL; } vgetc_busy++; if (advance) { KeyStuffed = FALSE; } init_typebuf(); start_stuff(); check_end_reg_executing(advance); do { // get a character: 1. from the stuffbuffer if (typeahead_char != 0) { c = typeahead_char; if (advance) { typeahead_char = 0; } } else { c = read_readbuffers(advance); } if (c != NUL && !got_int) { if (advance) { // KeyTyped = false; When the command that stuffed something // was typed, behave like the stuffed command was typed. // needed for CTRL-W CTRL-] to open a fold, for example. KeyStuffed = true; } if (typebuf.tb_no_abbr_cnt == 0) { typebuf.tb_no_abbr_cnt = 1; // no abbreviations now } } else { // Loop until we either find a matching mapped key, or we // are sure that it is not a mapped key. // If a mapped key sequence is found we go back to the start to // try re-mapping. for (;;) { check_end_reg_executing(advance); // os_breakcheck() is slow, don't use it too often when // inside a mapping. But call it each time for typed // characters. if (typebuf.tb_maplen) { line_breakcheck(); } else { // os_breakcheck() can call input_enqueue() if ((mapped_ctrl_c | curbuf->b_mapped_ctrl_c) & get_real_state()) { ctrl_c_interrupts = false; } os_breakcheck(); // check for CTRL-C ctrl_c_interrupts = true; } int keylen = 0; if (got_int) { // flush all input c = inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 0L); // If inchar() returns TRUE (script file was active) or we // are inside a mapping, get out of Insert mode. // Otherwise we behave like having gotten a CTRL-C. // As a result typing CTRL-C in insert mode will // really insert a CTRL-C. if ((c || typebuf.tb_maplen) && (State & (MODE_INSERT | MODE_CMDLINE))) { c = ESC; } else { c = Ctrl_C; } flush_buffers(FLUSH_INPUT); // flush all typeahead if (advance) { // Also record this character, it might be needed to // get out of Insert mode. *typebuf.tb_buf = (char_u)c; gotchars(typebuf.tb_buf, 1); } cmd_silent = false; break; } else if (typebuf.tb_len > 0) { // Check for a mapping in "typebuf". map_result_T result = (map_result_T)handle_mapping(&keylen, &timedout, &mapdepth); if (result == map_result_retry) { // try mapping again continue; } if (result == map_result_fail) { // failed, use the outer loop c = -1; break; } if (result == map_result_get) { // get a character: 2. from the typeahead buffer c = typebuf.tb_buf[typebuf.tb_off] & 255; if (advance) { // remove chars from tb_buf cmd_silent = (typebuf.tb_silent > 0); if (typebuf.tb_maplen > 0) { KeyTyped = false; } else { KeyTyped = true; // write char to script file(s) gotchars(typebuf.tb_buf + typebuf.tb_off, 1); } KeyNoremap = typebuf.tb_noremap[typebuf.tb_off]; del_typebuf(1, 0); } break; // got character, break the for loop } // not enough characters, get more } // get a character: 3. from the user - handle in Insert mode // special case: if we get an in insert mode and there // are no more characters at once, we pretend to go out of // insert mode. This prevents the one second delay after // typing an . If we get something after all, we may // have to redisplay the mode. That the cursor is in the wrong // place does not matter. c = 0; new_wcol = curwin->w_wcol; new_wrow = curwin->w_wrow; if (advance && typebuf.tb_len == 1 && typebuf.tb_buf[typebuf.tb_off] == ESC && !no_mapping && ex_normal_busy == 0 && typebuf.tb_maplen == 0 && (State & MODE_INSERT) && (p_timeout || (keylen == KEYLEN_PART_KEY && p_ttimeout)) && (c = inchar(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len, 3, 25L)) == 0) { colnr_T col = 0, vcol; char_u *ptr; if (mode_displayed) { unshowmode(true); mode_deleted = true; } validate_cursor(); old_wcol = curwin->w_wcol; old_wrow = curwin->w_wrow; // move cursor left, if possible if (curwin->w_cursor.col != 0) { if (curwin->w_wcol > 0) { if (did_ai) { // We are expecting to truncate the trailing // white-space, so find the last non-white // character -- webb col = vcol = curwin->w_wcol = 0; ptr = get_cursor_line_ptr(); while (col < curwin->w_cursor.col) { if (!ascii_iswhite(ptr[col])) { curwin->w_wcol = vcol; } vcol += lbr_chartabsize(ptr, ptr + col, vcol); col += utfc_ptr2len((char *)ptr + col); } curwin->w_wrow = curwin->w_cline_row + curwin->w_wcol / curwin->w_width_inner; curwin->w_wcol %= curwin->w_width_inner; curwin->w_wcol += curwin_col_off(); col = 0; // no correction needed } else { --curwin->w_wcol; col = curwin->w_cursor.col - 1; } } else if (curwin->w_p_wrap && curwin->w_wrow) { curwin->w_wrow--; curwin->w_wcol = curwin->w_width_inner - 1; col = curwin->w_cursor.col - 1; } if (col > 0 && curwin->w_wcol > 0) { // Correct when the cursor is on the right halve // of a double-wide character. ptr = get_cursor_line_ptr(); col -= utf_head_off(ptr, ptr + col); if (utf_ptr2cells((char *)ptr + col) > 1) { curwin->w_wcol--; } } } setcursor(); ui_flush(); new_wcol = curwin->w_wcol; new_wrow = curwin->w_wrow; curwin->w_wcol = old_wcol; curwin->w_wrow = old_wrow; } if (c < 0) { continue; // end of input script reached } // Allow mapping for just typed characters. When we get here c // is the number of extra bytes and typebuf.tb_len is 1. for (n = 1; n <= c; n++) { typebuf.tb_noremap[typebuf.tb_off + n] = RM_YES; } typebuf.tb_len += c; // buffer full, don't map if (typebuf.tb_len >= typebuf.tb_maplen + MAXMAPLEN) { timedout = true; continue; } if (ex_normal_busy > 0) { static int tc = 0; // No typeahead left and inside ":normal". Must return // something to avoid getting stuck. When an incomplete // mapping is present, behave like it timed out. if (typebuf.tb_len > 0) { timedout = true; continue; } // In Ex-mode \n is compatible with original Vim behaviour. // For the command line only CTRL-C always breaks it. // For the cmdline window: Alternate between ESC and // CTRL-C: ESC for most situations and CTRL-C to close the // cmdline window. if ((State & MODE_CMDLINE) || (cmdwin_type > 0 && tc == ESC)) { c = Ctrl_C; } else { c = ESC; } tc = c; // return 0 in normal_check() if (pending_exmode_active) { exmode_active = true; } // no chars to block abbreviations for typebuf.tb_no_abbr_cnt = 0; break; } // get a character: 3. from the user - update display // In insert mode a screen update is skipped when characters // are still available. But when those available characters // are part of a mapping, and we are going to do a blocking // wait here. Need to update the screen to display the // changed text so far. Also for when 'lazyredraw' is set and // redrawing was postponed because there was something in the // input buffer (e.g., termresponse). if (((State & MODE_INSERT) != 0 || p_lz) && (State & MODE_CMDLINE) == 0 && advance && must_redraw != 0 && !need_wait_return) { update_screen(0); setcursor(); // put cursor back where it belongs } // If we have a partial match (and are going to wait for more // input from the user), show the partially matched characters // to the user with showcmd. int showcmd_idx = 0; c1 = 0; if (typebuf.tb_len > 0 && advance && !exmode_active) { if (((State & (MODE_NORMAL | MODE_INSERT)) || State == MODE_LANGMAP) && State != MODE_HITRETURN) { // this looks nice when typing a dead character map if (State & MODE_INSERT && ptr2cells((char *)typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len - 1) == 1) { edit_putchar(typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len - 1], false); setcursor(); // put cursor back where it belongs c1 = 1; } // need to use the col and row from above here old_wcol = curwin->w_wcol; old_wrow = curwin->w_wrow; curwin->w_wcol = new_wcol; curwin->w_wrow = new_wrow; push_showcmd(); if (typebuf.tb_len > SHOWCMD_COLS) { showcmd_idx = typebuf.tb_len - SHOWCMD_COLS; } while (showcmd_idx < typebuf.tb_len) { (void)add_to_showcmd(typebuf.tb_buf[typebuf.tb_off + showcmd_idx++]); } curwin->w_wcol = old_wcol; curwin->w_wrow = old_wrow; } // this looks nice when typing a dead character map if ((State & MODE_CMDLINE) && cmdline_star == 0) { char *p = (char *)typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len - 1; if (ptr2cells(p) == 1 && (uint8_t)(*p) < 128) { putcmdline(*p, false); c1 = 1; } } } // get a character: 3. from the user - get it if (typebuf.tb_len == 0) { // timedout may have been set if a mapping with empty RHS // fully matched while longer mappings timed out. 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, wait_time); if (showcmd_idx != 0) { pop_showcmd(); } if (c1 == 1) { if (State & MODE_INSERT) { edit_unputchar(); } if (State & MODE_CMDLINE) { unputcmdline(); } else { setcursor(); // put cursor back where it belongs } } if (c < 0) { continue; // end of input script reached } if (c == NUL) { // no character available if (!advance) { break; } if (wait_tb_len > 0) { // timed out timedout = true; continue; } } else { // allow mapping for just typed characters while (typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len] != NUL) { typebuf.tb_noremap[typebuf.tb_off + typebuf.tb_len++] = RM_YES; } } } // for (;;) } // if (!character from stuffbuf) // if advance is false don't loop on NULs } while (c < 0 || (advance && c == NUL)); // The "INSERT" message is taken care of here: // if we return an ESC to exit insert mode, the message is deleted // if we don't return an ESC but deleted the message before, redisplay it if (advance && p_smd && msg_silent == 0 && (State & MODE_INSERT)) { if (c == ESC && !mode_deleted && !no_mapping && mode_displayed) { if (typebuf.tb_len && !KeyTyped) { redraw_cmdline = true; // delete mode later } else { unshowmode(false); } } else if (c != ESC && mode_deleted) { if (typebuf.tb_len && !KeyTyped) { redraw_cmdline = true; // show mode later } else { showmode(); } } } if (timedout && c == ESC) { char_u nop_buf[3]; // When recording there will be no timeout. Add a after the ESC // to avoid that it forms a key code with following characters. nop_buf[0] = K_SPECIAL; nop_buf[1] = KS_EXTRA; nop_buf[2] = KE_NOP; gotchars(nop_buf, 3); } vgetc_busy--; return c; } /// inchar() - get one character from /// 1. a scriptfile /// 2. the keyboard /// /// As many characters as we can get (up to 'maxlen') are put in "buf" and /// NUL terminated (buffer length must be 'maxlen' + 1). /// Minimum for "maxlen" is 3!!!! /// /// "tb_change_cnt" is the value of typebuf.tb_change_cnt if "buf" points into /// it. When typebuf.tb_change_cnt changes (e.g., when a message is received /// from a remote client) "buf" can no longer be used. "tb_change_cnt" is 0 /// otherwise. /// /// If we got an interrupt all input is read until none is available. /// /// If wait_time == 0 there is no waiting for the char. /// If wait_time == n we wait for n msec for a character to arrive. /// If wait_time == -1 we wait forever for a character to arrive. /// /// Return the number of obtained characters. /// Return -1 when end of input script reached. /// /// @param wait_time milliseconds int inchar(char_u *buf, int maxlen, long wait_time) { int len = 0; // Init for GCC. int retesc = false; // Return ESC with gotint. const int tb_change_cnt = typebuf.tb_change_cnt; if (wait_time == -1L || wait_time > 100L) { // flush output before waiting ui_flush(); } /* * Don't reset these when at the hit-return prompt, otherwise an endless * recursive loop may result (write error in swapfile, hit-return, timeout * on char wait, flush swapfile, write error....). */ if (State != MODE_HITRETURN) { did_outofmem_msg = false; // display out of memory message (again) did_swapwrite_msg = false; // display swap file write error again } // Get a character from a script file if there is one. // If interrupted: Stop reading script files, close them all. ptrdiff_t read_size = -1; while (scriptin[curscript] != NULL && read_size <= 0 && !ignore_script) { char script_char; if (got_int || (read_size = file_read(scriptin[curscript], &script_char, 1)) != 1) { // Reached EOF or some error occurred. // Careful: closescript() frees typebuf.tb_buf[] and buf[] may // point inside typebuf.tb_buf[]. Don't use buf[] after this! closescript(); // When reading script file is interrupted, return an ESC to get // back to normal mode. // Otherwise return -1, because typebuf.tb_buf[] has changed. if (got_int) { retesc = true; } else { return -1; } } else { buf[0] = (char_u)script_char; len = 1; } } if (read_size <= 0) { // Did not get a character from script. // If we got an interrupt, skip all previously typed characters and // return TRUE if quit reading script file. // Stop reading typeahead when a single CTRL-C was read, // fill_input_buf() returns this when not able to read from stdin. // Don't use buf[] here, closescript() may have freed typebuf.tb_buf[] // and buf may be pointing inside typebuf.tb_buf[]. if (got_int) { #define DUM_LEN (MAXMAPLEN * 3 + 3) char_u dum[DUM_LEN + 1]; for (;;) { len = os_inchar(dum, DUM_LEN, 0L, 0, NULL); if (len == 0 || (len == 1 && dum[0] == Ctrl_C)) { break; } } return retesc; } // Always flush the output characters when getting input characters // from the user. ui_flush(); // Fill up to a third of the buffer, because each character may be // tripled below. len = os_inchar(buf, maxlen / 3, (int)wait_time, tb_change_cnt, NULL); } // If the typebuf was changed further down, it is like nothing was added by // this call. if (typebuf_changed(tb_change_cnt)) { return 0; } // Note the change in the typeahead buffer, this matters for when // vgetorpeek() is called recursively, e.g. using getchar(1) in a timer // function. if (len > 0 && ++typebuf.tb_change_cnt == 0) { typebuf.tb_change_cnt = 1; } return fix_input_buffer(buf, len); } // Fix typed characters for use by vgetc() and check_termcode(). // "buf[]" must have room to triple the number of bytes! // Returns the new length. int fix_input_buffer(char_u *buf, int len) FUNC_ATTR_NONNULL_ALL { if (!using_script()) { // Should not escape K_SPECIAL reading input from the user because vim // key codes keys are processed in input.c/input_enqueue. buf[len] = NUL; return len; } // Reading from script, need to process special bytes int i; char_u *p = buf; // Two characters are special: NUL and K_SPECIAL. // Replace NUL by K_SPECIAL KS_ZERO KE_FILLER // Replace K_SPECIAL by K_SPECIAL KS_SPECIAL KE_FILLER for (i = len; --i >= 0; ++p) { if (p[0] == NUL || (p[0] == K_SPECIAL && (i < 2 || p[1] != KS_EXTRA))) { memmove(p + 3, p + 1, (size_t)i); p[2] = (char_u)K_THIRD(p[0]); p[1] = (char_u)K_SECOND(p[0]); p[0] = K_SPECIAL; p += 2; len += 2; } } *p = NUL; // add trailing NUL return len; } static bool typebuf_match_len(const uint8_t *str, int *mlen) { int i; for (i = 0; i < typebuf.tb_len && str[i]; i++) { if (str[i] != typebuf.tb_buf[typebuf.tb_off + i]) { break; } } *mlen = i; return str[i] == NUL; // matched the whole string } /// Get command argument for key char *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat) { garray_T line_ga; int c1 = -1, c2; int cmod = 0; bool aborted = false; ga_init(&line_ga, 1, 32); no_mapping++; got_int = false; while (c1 != NUL && !aborted) { ga_grow(&line_ga, 32); if (vgetorpeek(false) == NUL) { // incomplete is an error, because there is not much the user // could do in this state. emsg(e_cmdmap_err); aborted = true; break; } // Get one character at a time. c1 = vgetorpeek(true); // Get two extra bytes for special keys if (c1 == K_SPECIAL) { c1 = vgetorpeek(true); // no mapping for these chars c2 = vgetorpeek(true); if (c1 == KS_MODIFIER) { cmod = c2; continue; } c1 = TO_SPECIAL(c1, c2); } if (got_int) { aborted = true; } else if (c1 == '\r' || c1 == '\n') { c1 = NUL; // end the line } else if (c1 == ESC) { aborted = true; } else if (c1 == K_COMMAND) { // special case to give nicer error message emsg(e_cmdmap_repeated); aborted = true; } else if (c1 == K_SNR) { ga_concat(&line_ga, ""); } else { if (cmod != 0) { ga_append(&line_ga, (char)K_SPECIAL); ga_append(&line_ga, (char)KS_MODIFIER); ga_append(&line_ga, (char)cmod); } if (IS_SPECIAL(c1)) { ga_append(&line_ga, (char)K_SPECIAL); ga_append(&line_ga, (char)K_SECOND(c1)); ga_append(&line_ga, (char)K_THIRD(c1)); } else { ga_append(&line_ga, (char)c1); } } cmod = 0; } no_mapping--; if (aborted) { ga_clear(&line_ga); } return line_ga.ga_data; } bool map_execute_lua(void) { garray_T line_ga; int c1 = -1; bool aborted = false; ga_init(&line_ga, 1, 32); no_mapping++; got_int = false; while (c1 != NUL && !aborted) { ga_grow(&line_ga, 32); // Get one character at a time. c1 = vgetorpeek(true); if (got_int) { aborted = true; } else if (c1 == '\r' || c1 == '\n') { c1 = NUL; // end the line } else { ga_append(&line_ga, (char)c1); } } no_mapping--; if (aborted) { ga_clear(&line_ga); return false; } LuaRef ref = (LuaRef)atoi(line_ga.ga_data); Error err = ERROR_INIT; Array args = ARRAY_DICT_INIT; nlua_call_ref(ref, NULL, args, false, &err); if (err.type != kErrorTypeNone) { semsg_multiline("E5108: %s", err.msg); api_clear_error(&err); } ga_clear(&line_ga); return true; }