changeset 17600:ca42eb789472 v8.1.1797

patch 8.1.1797: the vgetorpeek() function is too long commit https://github.com/vim/vim/commit/edd680f3649c47d7ed5818e356e7c47f874f5cf8 Author: Bram Moolenaar <Bram@vim.org> Date: Sat Aug 3 14:23:48 2019 +0200 patch 8.1.1797: the vgetorpeek() function is too long Problem: The vgetorpeek() function is too long. Solution: Split off the part that handles mappings, with fix.
author Bram Moolenaar <Bram@vim.org>
date Sat, 03 Aug 2019 14:30:05 +0200
parents c4e9b24b4834
children de9cdfda3326
files src/getchar.c src/version.c
diffstat 2 files changed, 555 insertions(+), 507 deletions(-) [+]
line wrap: on
line diff
--- a/src/getchar.c
+++ b/src/getchar.c
@@ -1900,6 +1900,530 @@ char_avail(void)
     return (retval != NUL);
 }
 
+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;
+
+/*
+ * 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.
+ */
+    static int
+handle_mapping(
+	    int *keylenp,
+	    int *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;
+#ifdef FEAT_LANGMAP
+    int		nolmaplen;
+#endif
+    int		keylen = *keylenp;
+    int		i;
+    int		local_State = get_real_state();
+
+    /*
+     * 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 && is_maphash_valid()
+	    && (no_zero_mapping == 0 || tb_c1 != '0')
+	    && (typebuf.tb_maplen == 0
+		|| (p_remap
+		    && (typebuf.tb_noremap[typebuf.tb_off]
+				    & (RM_NONE|RM_ABBR)) == 0))
+	    && !(p_paste && (State & (INSERT + CMDLINE)))
+	    && !(State == HITRETURN && (tb_c1 == CAR || tb_c1 == ' '))
+	    && State != ASKMORE
+	    && State != CONFIRM
+#ifdef FEAT_INS_EXPAND
+	    && !((ctrl_x_mode_not_default()
+				   && vim_is_ctrl_x_key(tb_c1))
+		    || ((compl_cont_status & CONT_LOCAL)
+			&& (tb_c1 == Ctrl_N || tb_c1 == Ctrl_P)))
+#endif
+	    )
+    {
+#ifdef FEAT_LANGMAP
+	if (tb_c1 == K_SPECIAL)
+	    nolmaplen = 2;
+	else
+	{
+	    LANGMAP_ADJUST(tb_c1,
+			   (State & (CMDLINE | INSERT)) == 0
+			   && get_real_state() != SELECTMODE);
+	    nolmaplen = 0;
+	}
+#endif
+	// 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 & LANGMAP) == 0
+			|| typebuf.tb_maplen == 0))
+	    {
+#ifdef FEAT_LANGMAP
+		int	nomap = nolmaplen;
+		int	c2;
+#endif
+		// find the match length of this mapping
+		for (mlen = 1; mlen < typebuf.tb_len; ++mlen)
+		{
+#ifdef FEAT_LANGMAP
+		    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)
+#else
+		    if (mp->m_keys[mlen] !=
+			typebuf.tb_buf[typebuf.tb_off + mlen])
+#endif
+			break;
+		}
+
+		// Don't allow mapping the first byte(s) of a
+		// multi-byte char.  Happens when mapping
+		// <M-a> and then changing 'encoding'. Beware
+		// that 0x80 is escaped.
+		{
+		    char_u *p1 = mp->m_keys;
+		    char_u *p2 = mb_unescape(&p1);
+
+		    if (has_mbyte && p2 != NULL
+			  && MB_BYTE2LEN(tb_c1) > MB_PTR2LEN(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]
+					      != (int)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 (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)
+		    {
+			// 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 = mp_match;
+	    keylen = mp_match_len;
+	}
+    }
+
+    /*
+     * Check for match with 'pastetoggle'
+     */
+    if (*p_pt != NUL && mp == NULL && (State & (INSERT|NORMAL)))
+    {
+	for (mlen = 0; mlen < typebuf.tb_len && p_pt[mlen];
+						       ++mlen)
+	    if (p_pt[mlen] != typebuf.tb_buf[typebuf.tb_off
+						      + mlen])
+		    break;
+	if (p_pt[mlen] == NUL)	// match
+	{
+	    // write chars to script file(s)
+	    if (mlen > typebuf.tb_maplen)
+		gotchars(typebuf.tb_buf + typebuf.tb_off
+					  + typebuf.tb_maplen,
+				    mlen - typebuf.tb_maplen);
+
+	    del_typebuf(mlen, 0); // remove the chars
+	    set_option_value((char_u *)"paste",
+				     (long)!p_paste, NULL, 0);
+	    if (!(State & 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)
+    {
+	int	save_keylen = keylen;
+
+	/*
+	 * When no matching mapping found or found a
+	 * non-matching mapping that matches at least what the
+	 * matching mapping matched:
+	 * Check if we have a terminal code, when:
+	 *  mapping is allowed,
+	 *  keys have not been mapped,
+	 *  and not an ESC sequence, not in insert mode or
+	 *	p_ek is on,
+	 *  and when not timed out,
+	 */
+	if ((no_mapping == 0 || allow_keys != 0)
+		&& (typebuf.tb_maplen == 0
+		    || (p_remap && typebuf.tb_noremap[
+				   typebuf.tb_off] == RM_YES))
+		&& !*timedout)
+	{
+	    keylen = check_termcode(max_mlen + 1,
+					       NULL, 0, NULL);
+
+	    // If no termcode matched but 'pastetoggle'
+	    // matched partially it's like an incomplete key
+	    // sequence.
+	    if (keylen == 0 && save_keylen == KEYLEN_PART_KEY)
+		keylen = KEYLEN_PART_KEY;
+
+	    // When getting a partial match, but the last
+	    // characters were not typed, don't wait for a
+	    // typed character to complete the termcode.
+	    // This helps a lot when a ":normal" command ends
+	    // in an ESC.
+	    if (keylen < 0
+		       && typebuf.tb_len == typebuf.tb_maplen)
+		keylen = 0;
+	}
+	else
+	    keylen = 0;
+	if (keylen == 0)	// no matching terminal code
+	{
+#ifdef AMIGA			// check for window bounds report
+	    if (typebuf.tb_maplen == 0 && (typebuf.tb_buf[
+			       typebuf.tb_off] & 0xff) == CSI)
+	    {
+		char_u *s;
+
+		for (s = typebuf.tb_buf + typebuf.tb_off + 1;
+			s < typebuf.tb_buf + typebuf.tb_off
+					      + typebuf.tb_len
+		   && (VIM_ISDIGIT(*s) || *s == ';'
+						|| *s == ' ');
+			++s)
+		    ;
+		if (*s == 'r' || *s == '|') // found one
+		{
+		    del_typebuf((int)(s + 1 -
+		       (typebuf.tb_buf + typebuf.tb_off)), 0);
+		    // get size and redraw screen
+		    shell_resized();
+		    *keylenp = keylen;
+		    return map_result_retry;
+		}
+		if (*s == NUL)	    // need more characters
+		    keylen = KEYLEN_PART_KEY;
+	    }
+	    if (keylen >= 0)
+#endif
+	      // When there was a matching mapping and no
+	      // termcode could be replaced after another one,
+	      // use that mapping (loop around). 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;	    // got character, break for loop
+	      }
+	}
+
+	if (keylen > 0)	    // full matching terminal code
+	{
+#if defined(FEAT_GUI) && defined(FEAT_MENU)
+	    if (typebuf.tb_len >= 2
+		&& typebuf.tb_buf[typebuf.tb_off] == K_SPECIAL
+			 && typebuf.tb_buf[typebuf.tb_off + 1]
+						   == KS_MENU)
+	    {
+		int	idx;
+
+		// Using a menu may cause a break in undo!
+		// It's like using gotchars(), but without
+		// recording or writing to a script file.
+		may_sync_undo();
+		del_typebuf(3, 0);
+		idx = get_menu_index(current_menu, local_State);
+		if (idx != MENU_INDEX_INVALID)
+		{
+		    // In Select mode and a Visual mode menu
+		    // is used:  Switch to Visual mode
+		    // temporarily.  Append K_SELECT to switch
+		    // back to Select mode.
+		    if (VIsual_active && VIsual_select
+			    && (current_menu->modes & VISUAL))
+		    {
+			VIsual_select = FALSE;
+			(void)ins_typebuf(K_SELECT_STRING,
+				  REMAP_NONE, 0, TRUE, FALSE);
+		    }
+		    ins_typebuf(current_menu->strings[idx],
+				current_menu->noremap[idx],
+				0, TRUE,
+				   current_menu->silent[idx]);
+		}
+	    }
+#endif // FEAT_GUI && FEAT_MENU
+	    *keylenp = keylen;
+	    return map_result_retry;	// try mapping again
+	}
+
+	// Partial match: get some more characters.  When a
+	// matching mapping was found use that one.
+	if (mp == NULL || keylen < 0)
+	    keylen = KEYLEN_PART_KEY;
+	else
+	    keylen = mp_match_len;
+    }
+
+    /*
+     * complete match
+     */
+    if (keylen >= 0 && keylen <= typebuf.tb_len)
+    {
+	char_u *map_str;
+
+#ifdef FEAT_EVAL
+	int save_m_expr;
+	int save_m_noremap;
+	int save_m_silent;
+	char_u *save_m_keys;
+	char_u *save_m_str;
+#else
+# define save_m_noremap mp->m_noremap
+# define save_m_silent mp->m_silent
+#endif
+
+	// write chars to script file(s)
+	if (keylen > typebuf.tb_maplen)
+	    gotchars(typebuf.tb_buf + typebuf.tb_off
+					  + typebuf.tb_maplen,
+				  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 & 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 & VISUAL))
+	{
+	    VIsual_select = FALSE;
+	    (void)ins_typebuf(K_SELECT_STRING, REMAP_NONE,
+					      0, TRUE, FALSE);
+	}
+
+#ifdef FEAT_EVAL
+	// Copy the values from *mp that are used, because
+	// evaluating the expression may invoke a function
+	// that redefines the mapping, thereby making *mp
+	// invalid.
+	save_m_expr = mp->m_expr;
+	save_m_noremap = mp->m_noremap;
+	save_m_silent = mp->m_silent;
+	save_m_keys = NULL;  // only saved when needed
+	save_m_str = NULL;  // only saved when needed
+
+	/*
+	 * Handle ":map <expr>": evaluate the {rhs} as an
+	 * expression.  Also save and restore the command line
+	 * for "normal :".
+	 */
+	if (mp->m_expr)
+	{
+	    int save_vgetc_busy = vgetc_busy;
+	    int save_may_garbage_collect = may_garbage_collect;
+
+	    vgetc_busy = 0;
+	    may_garbage_collect = FALSE;
+
+	    save_m_keys = vim_strsave(mp->m_keys);
+	    save_m_str = vim_strsave(mp->m_str);
+	    map_str = eval_map_expr(save_m_str, NUL);
+
+	    vgetc_busy = save_vgetc_busy;
+	    may_garbage_collect = save_may_garbage_collect;
+	}
+	else
+#endif
+	    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 (save_m_noremap != REMAP_YES)
+		noremap = save_m_noremap;
+	    else if (
+#ifdef FEAT_EVAL
+		STRNCMP(map_str, save_m_keys != NULL
+				   ? save_m_keys : mp->m_keys,
+					 (size_t)keylen)
+#else
+		STRNCMP(map_str, mp->m_keys, (size_t)keylen)
+#endif
+		   != 0)
+		noremap = REMAP_YES;
+	    else
+		noremap = REMAP_SKIP;
+	    i = ins_typebuf(map_str, noremap,
+					 0, TRUE, cmd_silent || save_m_silent);
+#ifdef FEAT_EVAL
+	    if (save_m_expr)
+		vim_free(map_str);
+#endif
+	}
+#ifdef FEAT_EVAL
+	vim_free(save_m_keys);
+	vim_free(save_m_str);
+#endif
+	*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!)
  */
@@ -1942,33 +2466,18 @@ vungetc(int c)
 vgetorpeek(int advance)
 {
     int		c, c1;
-    int		keylen;
-    char_u	*s;
-    mapblock_T	*mp;
-    mapblock_T	*mp2;
-    mapblock_T	*mp_match;
-    int		mp_match_len = 0;
     int		timedout = FALSE;	    /* waited for more than 1 second
 						for mapping to complete */
     int		mapdepth = 0;	    /* check for recursive mapping */
     int		mode_deleted = FALSE;   /* set when mode has been deleted */
-    int		local_State;
-    int		mlen;
-    int		max_mlen;
     int		i;
 #ifdef FEAT_CMDL_INFO
     int		new_wcol, new_wrow;
 #endif
 #ifdef FEAT_GUI
-# ifdef FEAT_MENU
-    int		idx;
-# endif
     int		shape_changed = FALSE;  /* adjusted cursor shape */
 #endif
     int		n;
-#ifdef FEAT_LANGMAP
-    int		nolmaplen;
-#endif
     int		old_wcol, old_wrow;
     int		wait_tb_len;
 
@@ -1986,8 +2495,6 @@ vgetorpeek(int advance)
     if (vgetc_busy > 0 && ex_normal_busy == 0)
 	return NUL;
 
-    local_State = get_real_state();
-
     ++vgetc_busy;
 
     if (advance)
@@ -2032,7 +2539,8 @@ vgetorpeek(int advance)
 	     */
 	    for (;;)
 	    {
-		long	    wait_time;
+		long	wait_time;
+		int	keylen = 0;
 
 		/*
 		 * ui_breakcheck() is slow, don't use it too often when
@@ -2043,11 +2551,11 @@ vgetorpeek(int advance)
 		    line_breakcheck();
 		else
 		    ui_breakcheck();		/* check for CTRL-C */
-		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.
@@ -2076,508 +2584,46 @@ vgetorpeek(int advance)
 		else if (typebuf.tb_len > 0)
 		{
 		    /*
-		     * 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
+		     * Check for a mapping in "typebuf".
 		     */
-		    mp = NULL;
-		    max_mlen = 0;
-		    c1 = typebuf.tb_buf[typebuf.tb_off];
-		    if (no_mapping == 0 && is_maphash_valid()
-			    && (no_zero_mapping == 0 || c1 != '0')
-			    && (typebuf.tb_maplen == 0
-				|| (p_remap
-				    && (typebuf.tb_noremap[typebuf.tb_off]
-						    & (RM_NONE|RM_ABBR)) == 0))
-			    && !(p_paste && (State & (INSERT + CMDLINE)))
-			    && !(State == HITRETURN && (c1 == CAR || c1 == ' '))
-			    && State != ASKMORE
-			    && State != CONFIRM
-#ifdef FEAT_INS_EXPAND
-			    && !((ctrl_x_mode_not_default()
-						      && vim_is_ctrl_x_key(c1))
-				    || ((compl_cont_status & CONT_LOCAL)
-					&& (c1 == Ctrl_N || c1 == Ctrl_P)))
-#endif
-			    )
-		    {
-#ifdef FEAT_LANGMAP
-			if (c1 == K_SPECIAL)
-			    nolmaplen = 2;
-			else
-			{
-			    LANGMAP_ADJUST(c1,
-					   (State & (CMDLINE | INSERT)) == 0
-					   && get_real_state() != SELECTMODE);
-			    nolmaplen = 0;
-			}
-#endif
-			// First try buffer-local mappings.
-			mp = get_buf_maphash_list(local_State, c1);
-			mp2 = get_maphash_list(local_State, 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] == c1
-				    && (mp->m_mode & local_State)
-				    && ((mp->m_mode & LANGMAP) == 0
-					|| typebuf.tb_maplen == 0))
-			    {
-#ifdef FEAT_LANGMAP
-				int	nomap = nolmaplen;
-				int	c2;
-#endif
-				/* find the match length of this mapping */
-				for (mlen = 1; mlen < typebuf.tb_len; ++mlen)
-				{
-#ifdef FEAT_LANGMAP
-				    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)
-#else
-				    if (mp->m_keys[mlen] !=
-					typebuf.tb_buf[typebuf.tb_off + mlen])
-#endif
-					break;
-				}
-
-				/* Don't allow mapping the first byte(s) of a
-				 * multi-byte char.  Happens when mapping
-				 * <M-a> and then changing 'encoding'. Beware
-				 * that 0x80 is escaped. */
-				{
-				    char_u *p1 = mp->m_keys;
-				    char_u *p2 = mb_unescape(&p1);
-
-				    if (has_mbyte && p2 != NULL
-					  && MB_BYTE2LEN(c1) > MB_PTR2LEN(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))
-				{
-				    /*
-				     * 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]
-							      != (int)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 (n >= 0)
-					continue;
+		    map_result_T result = handle_mapping(
+						&keylen, &timedout, &mapdepth);
 
-				    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)
-				    {
-					/* 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 = mp_match;
-			    keylen = mp_match_len;
-			}
-		    }
-
-		    /* Check for match with 'pastetoggle' */
-		    if (*p_pt != NUL && mp == NULL && (State & (INSERT|NORMAL)))
+		    if (result == map_result_retry)
+			// try mapping again
+			continue;
+		    if (result == map_result_fail)
 		    {
-			for (mlen = 0; mlen < typebuf.tb_len && p_pt[mlen];
-								       ++mlen)
-			    if (p_pt[mlen] != typebuf.tb_buf[typebuf.tb_off
-								      + mlen])
-				    break;
-			if (p_pt[mlen] == NUL)	/* match */
-			{
-			    /* write chars to script file(s) */
-			    if (mlen > typebuf.tb_maplen)
-				gotchars(typebuf.tb_buf + typebuf.tb_off
-							  + typebuf.tb_maplen,
-						    mlen - typebuf.tb_maplen);
-
-			    del_typebuf(mlen, 0); /* remove the chars */
-			    set_option_value((char_u *)"paste",
-						     (long)!p_paste, NULL, 0);
-			    if (!(State & INSERT))
-			    {
-				msg_col = 0;
-				msg_row = Rows - 1;
-				msg_clr_eos();		/* clear ruler */
-			    }
-			    status_redraw_all();
-			    redraw_statuslines();
-			    showmode();
-			    setcursor();
-			    continue;
-			}
-			/* 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;
+			// failed, use the outer loop
+			c = -1;
+			break;
 		    }
-
-		    if ((mp == NULL || max_mlen >= mp_match_len)
-						 && keylen != KEYLEN_PART_MAP)
+		    if (result == map_result_get)
 		    {
-			int	save_keylen = keylen;
-
-			/*
-			 * When no matching mapping found or found a
-			 * non-matching mapping that matches at least what the
-			 * matching mapping matched:
-			 * Check if we have a terminal code, when:
-			 *  mapping is allowed,
-			 *  keys have not been mapped,
-			 *  and not an ESC sequence, not in insert mode or
-			 *	p_ek is on,
-			 *  and when not timed out,
-			 */
-			if ((no_mapping == 0 || allow_keys != 0)
-				&& (typebuf.tb_maplen == 0
-				    || (p_remap && typebuf.tb_noremap[
-						   typebuf.tb_off] == RM_YES))
-				&& !timedout)
-			{
-			    keylen = check_termcode(max_mlen + 1,
-							       NULL, 0, NULL);
-
-			    /* If no termcode matched but 'pastetoggle'
-			     * matched partially it's like an incomplete key
-			     * sequence. */
-			    if (keylen == 0 && save_keylen == KEYLEN_PART_KEY)
-				keylen = KEYLEN_PART_KEY;
-
-			    /*
-			     * When getting a partial match, but the last
-			     * characters were not typed, don't wait for a
-			     * typed character to complete the termcode.
-			     * This helps a lot when a ":normal" command ends
-			     * in an ESC.
-			     */
-			    if (keylen < 0
-				       && typebuf.tb_len == typebuf.tb_maplen)
-				keylen = 0;
-			}
-			else
-			    keylen = 0;
-			if (keylen == 0)	/* no matching terminal code */
-			{
-#ifdef AMIGA			/* check for window bounds report */
-			    if (typebuf.tb_maplen == 0 && (typebuf.tb_buf[
-					       typebuf.tb_off] & 0xff) == CSI)
-			    {
-				for (s = typebuf.tb_buf + typebuf.tb_off + 1;
-					s < typebuf.tb_buf + typebuf.tb_off
-							      + typebuf.tb_len
-				   && (VIM_ISDIGIT(*s) || *s == ';'
-								|| *s == ' ');
-					++s)
-				    ;
-				if (*s == 'r' || *s == '|') /* found one */
-				{
-				    del_typebuf((int)(s + 1 -
-				       (typebuf.tb_buf + typebuf.tb_off)), 0);
-				    /* get size and redraw screen */
-				    shell_resized();
-				    continue;
-				}
-				if (*s == NUL)	    /* need more characters */
-				    keylen = KEYLEN_PART_KEY;
-			    }
-			    if (keylen >= 0)
-#endif
-			      /* When there was a matching mapping and no
-			       * termcode could be replaced after another one,
-			       * use that mapping (loop around). If there was
-			       * no mapping use the character from the
-			       * typeahead buffer right here. */
-			      if (mp == NULL)
-			      {
 /*
  * 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 for loop */
-			      }
-			}
-			if (keylen > 0)	    /* full matching terminal code */
+			c = typebuf.tb_buf[typebuf.tb_off] & 255;
+			if (advance)	/* remove chars from tb_buf */
 			{
-#if defined(FEAT_GUI) && defined(FEAT_MENU)
-			    if (typebuf.tb_len >= 2
-				&& typebuf.tb_buf[typebuf.tb_off] == K_SPECIAL
-					 && typebuf.tb_buf[typebuf.tb_off + 1]
-								   == KS_MENU)
+			    cmd_silent = (typebuf.tb_silent > 0);
+			    if (typebuf.tb_maplen > 0)
+				KeyTyped = FALSE;
+			    else
 			    {
-				/*
-				 * Using a menu may cause a break in undo!
-				 * It's like using gotchars(), but without
-				 * recording or writing to a script file.
-				 */
-				may_sync_undo();
-				del_typebuf(3, 0);
-				idx = get_menu_index(current_menu, local_State);
-				if (idx != MENU_INDEX_INVALID)
-				{
-				    /*
-				     * In Select mode and a Visual mode menu
-				     * is used:  Switch to Visual mode
-				     * temporarily.  Append K_SELECT to switch
-				     * back to Select mode.
-				     */
-				    if (VIsual_active && VIsual_select
-					    && (current_menu->modes & VISUAL))
-				    {
-					VIsual_select = FALSE;
-					(void)ins_typebuf(K_SELECT_STRING,
-						  REMAP_NONE, 0, TRUE, FALSE);
-				    }
-				    ins_typebuf(current_menu->strings[idx],
-						current_menu->noremap[idx],
-						0, TRUE,
-						   current_menu->silent[idx]);
-				}
+				KeyTyped = TRUE;
+				/* write char to script file(s) */
+				gotchars(typebuf.tb_buf
+						 + typebuf.tb_off, 1);
 			    }
-#endif /* FEAT_GUI && FEAT_MENU */
-			    continue;	/* try mapping again */
+			    KeyNoremap = typebuf.tb_noremap[
+						      typebuf.tb_off];
+			    del_typebuf(1, 0);
 			}
-
-			/* Partial match: get some more characters.  When a
-			 * matching mapping was found use that one. */
-			if (mp == NULL || keylen < 0)
-			    keylen = KEYLEN_PART_KEY;
-			else
-			    keylen = mp_match_len;
+			break;
 		    }
 
-		    /* complete match */
-		    if (keylen >= 0 && keylen <= typebuf.tb_len)
-		    {
-#ifdef FEAT_EVAL
-			int save_m_expr;
-			int save_m_noremap;
-			int save_m_silent;
-			char_u *save_m_keys;
-			char_u *save_m_str;
-#else
-# define save_m_noremap mp->m_noremap
-# define save_m_silent mp->m_silent
-#endif
-
-			/* write chars to script file(s) */
-			if (keylen > typebuf.tb_maplen)
-			    gotchars(typebuf.tb_buf + typebuf.tb_off
-							  + typebuf.tb_maplen,
-						  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 & CMDLINE)
-				redrawcmdline();
-			    else
-				setcursor();
-			    flush_buffers(FLUSH_MINIMAL);
-			    mapdepth = 0;	/* for next one */
-			    c = -1;
-			    break;
-			}
-
-			/*
-			 * 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 & VISUAL))
-			{
-			    VIsual_select = FALSE;
-			    (void)ins_typebuf(K_SELECT_STRING, REMAP_NONE,
-							      0, TRUE, FALSE);
-			}
-
-#ifdef FEAT_EVAL
-			/* Copy the values from *mp that are used, because
-			 * evaluating the expression may invoke a function
-			 * that redefines the mapping, thereby making *mp
-			 * invalid. */
-			save_m_expr = mp->m_expr;
-			save_m_noremap = mp->m_noremap;
-			save_m_silent = mp->m_silent;
-			save_m_keys = NULL;  /* only saved when needed */
-			save_m_str = NULL;  /* only saved when needed */
-
-			/*
-			 * Handle ":map <expr>": evaluate the {rhs} as an
-			 * expression.  Also save and restore the command line
-			 * for "normal :".
-			 */
-			if (mp->m_expr)
-			{
-			    int save_vgetc_busy = vgetc_busy;
-			    int save_may_garbage_collect = may_garbage_collect;
-
-			    vgetc_busy = 0;
-			    may_garbage_collect = FALSE;
-
-			    save_m_keys = vim_strsave(mp->m_keys);
-			    save_m_str = vim_strsave(mp->m_str);
-			    s = eval_map_expr(save_m_str, NUL);
-
-			    vgetc_busy = save_vgetc_busy;
-			    may_garbage_collect = save_may_garbage_collect;
-			}
-			else
-#endif
-			    s = 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 (s == NULL)
-			    i = FAIL;
-			else
-			{
-			    int noremap;
-
-			    if (save_m_noremap != REMAP_YES)
-				noremap = save_m_noremap;
-			    else if (
-#ifdef FEAT_EVAL
-				STRNCMP(s, save_m_keys != NULL
-						   ? save_m_keys : mp->m_keys,
-							 (size_t)keylen)
-#else
-				STRNCMP(s, mp->m_keys, (size_t)keylen)
-#endif
-				   != 0)
-				noremap = REMAP_YES;
-			    else
-				noremap = REMAP_SKIP;
-			    i = ins_typebuf(s, noremap,
-					0, TRUE, cmd_silent || save_m_silent);
-#ifdef FEAT_EVAL
-			    if (save_m_expr)
-				vim_free(s);
-#endif
-			}
-#ifdef FEAT_EVAL
-			vim_free(save_m_keys);
-			vim_free(save_m_str);
-#endif
-			if (i == FAIL)
-			{
-			    c = -1;
-			    break;
-			}
-			continue;
-		    }
+		    // not enough characters, get more
 		}
 
 /*
--- a/src/version.c
+++ b/src/version.c
@@ -774,6 +774,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1797,
+/**/
     1796,
 /**/
     1795,