Commit message: patch 8.2.3610: crash when ModeChanged triggered too early

Problem:    Crash when ModeChanged triggered too early.
Solution:   Trigger ModeChanged after setting VIsual.
CVE: CVE-2021-3968
CWE: CWE-122
Vulnerability description: vim is vulnerable to Heap-based Buffer Overflow

Project: vim

normal_cmd(
    oparg_T	*oap,
    int		toplevel UNUSED)	// TRUE when called from main()
{
    cmdarg_T	ca;			// command arguments
    int		c;
    int		ctrl_w = FALSE;		// got CTRL-W command
    int		old_col = curwin->w_curswant;
#ifdef FEAT_CMDL_INFO
    int		need_flushbuf;		// need to call out_flush()
#endif
    pos_T	old_pos;		// cursor position before command
    int		mapped_len;
    static int	old_mapped_len = 0;
    int		idx;
#ifdef FEAT_EVAL
    int		set_prevcount = FALSE;
#endif
    int		save_did_cursorhold = did_cursorhold;

    CLEAR_FIELD(ca);	// also resets ca.retval
    ca.oap = oap;

    // Use a count remembered from before entering an operator.  After typing
    // "3d" we return from normal_cmd() and come back here, the "3" is
    // remembered in "opcount".
    ca.opcount = opcount;

    /*
     * If there is an operator pending, then the command we take this time
     * will terminate it. Finish_op tells us to finish the operation before
     * returning this time (unless the operation was cancelled).
     */
#ifdef CURSOR_SHAPE
    c = finish_op;
#endif
    finish_op = (oap->op_type != OP_NOP);
#ifdef CURSOR_SHAPE
    if (finish_op != c)
    {
	ui_cursor_shape();		// may show different cursor shape
# ifdef FEAT_MOUSESHAPE
	update_mouseshape(-1);
# endif
    }
#endif
    trigger_modechanged();

    // When not finishing an operator and no register name typed, reset the
    // count.
    if (!finish_op && !oap->regname)
    {
	ca.opcount = 0;
#ifdef FEAT_EVAL
	set_prevcount = TRUE;
#endif
    }

    // Restore counts from before receiving K_CURSORHOLD.  This means after
    // typing "3", handling K_CURSORHOLD and then typing "2" we get "32", not
    // "3 * 2".
    if (oap->prev_opcount > 0 || oap->prev_count0 > 0)
    {
	ca.opcount = oap->prev_opcount;
	ca.count0 = oap->prev_count0;
	oap->prev_opcount = 0;
	oap->prev_count0 = 0;
    }

    mapped_len = typebuf_maplen();

    State = NORMAL_BUSY;
#ifdef USE_ON_FLY_SCROLL
    dont_scroll = FALSE;	// allow scrolling here
#endif

#ifdef FEAT_EVAL
    // Set v:count here, when called from main() and not a stuffed
    // command, so that v:count can be used in an expression mapping
    // when there is no count. Do set it for redo.
    if (toplevel && readbuf1_empty())
	set_vcount_ca(&ca, &set_prevcount);
#endif

    /*
     * Get the command character from the user.
     */
    c = safe_vgetc();
    LANGMAP_ADJUST(c, get_real_state() != SELECTMODE);

    /*
     * If a mapping was started in Visual or Select mode, remember the length
     * of the mapping.  This is used below to not return to Insert mode for as
     * long as the mapping is being executed.
     */
    if (restart_edit == 0)
	old_mapped_len = 0;
    else if (old_mapped_len
		|| (VIsual_active && mapped_len == 0 && typebuf_maplen() > 0))
	old_mapped_len = typebuf_maplen();

    if (c == NUL)
	c = K_ZERO;

    /*
     * In Select mode, typed text replaces the selection.
     */
    if (VIsual_active
	    && VIsual_select
	    && (vim_isprintc(c) || c == NL || c == CAR || c == K_KENTER))
    {
	// Fake a "c"hange command.  When "restart_edit" is set (e.g., because
	// 'insertmode' is set) fake a "d"elete command, Insert mode will
	// restart automatically.
	// Insert the typed character in the typeahead buffer, so that it can
	// be mapped in Insert mode.  Required for ":lmap" to work.
	ins_char_typebuf(vgetc_char, vgetc_mod_mask);
	if (restart_edit != 0)
	    c = 'd';
	else
	    c = 'c';
	msg_nowait = TRUE;	// don't delay going to insert mode
	old_mapped_len = 0;	// do go to Insert mode
    }

#ifdef FEAT_CMDL_INFO
    need_flushbuf = add_to_showcmd(c);
#endif

getcount:
    if (!(VIsual_active && VIsual_select))
    {
	/*
	 * Handle a count before a command and compute ca.count0.
	 * Note that '0' is a command and not the start of a count, but it's
	 * part of a count after other digits.
	 */
	while (    (c >= '1' && c <= '9')
		|| (ca.count0 != 0 && (c == K_DEL || c == K_KDEL || c == '0')))
	{
	    if (c == K_DEL || c == K_KDEL)
	    {
		ca.count0 /= 10;
#ifdef FEAT_CMDL_INFO
		del_from_showcmd(4);	// delete the digit and ~@%
#endif
	    }
	    else
		ca.count0 = ca.count0 * 10 + (c - '0');
	    if (ca.count0 < 0)	    // overflow
		ca.count0 = 999999999L;
#ifdef FEAT_EVAL
	    // Set v:count here, when called from main() and not a stuffed
	    // command, so that v:count can be used in an expression mapping
	    // right after the count. Do set it for redo.
	    if (toplevel && readbuf1_empty())
		set_vcount_ca(&ca, &set_prevcount);
#endif
	    if (ctrl_w)
	    {
		++no_mapping;
		++allow_keys;		// no mapping for nchar, but keys
	    }
	    ++no_zero_mapping;		// don't map zero here
	    c = plain_vgetc();
	    LANGMAP_ADJUST(c, TRUE);
	    --no_zero_mapping;
	    if (ctrl_w)
	    {
		--no_mapping;
		--allow_keys;
	    }
#ifdef FEAT_CMDL_INFO
	    need_flushbuf |= add_to_showcmd(c);
#endif
	}

	/*
	 * If we got CTRL-W there may be a/another count
	 */
	if (c == Ctrl_W && !ctrl_w && oap->op_type == OP_NOP)
	{
	    ctrl_w = TRUE;
	    ca.opcount = ca.count0;	// remember first count
	    ca.count0 = 0;
	    ++no_mapping;
	    ++allow_keys;		// no mapping for nchar, but keys
	    c = plain_vgetc();		// get next character
	    LANGMAP_ADJUST(c, TRUE);
	    --no_mapping;
	    --allow_keys;
#ifdef FEAT_CMDL_INFO
	    need_flushbuf |= add_to_showcmd(c);
#endif
	    goto getcount;		// jump back
	}
    }

    if (c == K_CURSORHOLD)
    {
	// Save the count values so that ca.opcount and ca.count0 are exactly
	// the same when coming back here after handling K_CURSORHOLD.
	oap->prev_opcount = ca.opcount;
	oap->prev_count0 = ca.count0;
    }
    else if (ca.opcount != 0)
    {
	/*
	 * If we're in the middle of an operator (including after entering a
	 * yank buffer with '"') AND we had a count before the operator, then
	 * that count overrides the current value of ca.count0.
	 * What this means effectively, is that commands like "3dw" get turned
	 * into "d3w" which makes things fall into place pretty neatly.
	 * If you give a count before AND after the operator, they are
	 * multiplied.
	 */
	if (ca.count0)
	    ca.count0 *= ca.opcount;
	else
	    ca.count0 = ca.opcount;
	if (ca.count0 < 0)	    // overflow
	    ca.count0 = 999999999L;
    }

    /*
     * Always remember the count.  It will be set to zero (on the next call,
     * above) when there is no pending operator.
     * When called from main(), save the count for use by the "count" built-in
     * variable.
     */
    ca.opcount = ca.count0;
    ca.count1 = (ca.count0 == 0 ? 1 : ca.count0);

#ifdef FEAT_EVAL
    /*
     * Only set v:count when called from main() and not a stuffed command.
     * Do set it for redo.
     */
    if (toplevel && readbuf1_empty())
	set_vcount(ca.count0, ca.count1, set_prevcount);
#endif

    /*
     * Find the command character in the table of commands.
     * For CTRL-W we already got nchar when looking for a count.
     */
    if (ctrl_w)
    {
	ca.nchar = c;
	ca.cmdchar = Ctrl_W;
    }
    else
	ca.cmdchar = c;
    idx = find_command(ca.cmdchar);
    if (idx < 0)
    {
	// Not a known command: beep.
	clearopbeep(oap);
	goto normal_end;
    }

    if (text_locked() && (nv_cmds[idx].cmd_flags & NV_NCW))
    {
	// This command is not allowed while editing a cmdline: beep.
	clearopbeep(oap);
	text_locked_msg();
	goto normal_end;
    }
    if ((nv_cmds[idx].cmd_flags & NV_NCW) && curbuf_locked())
	goto normal_end;

    /*
     * In Visual/Select mode, a few keys are handled in a special way.
     */
    if (VIsual_active)
    {
	// when 'keymodel' contains "stopsel" may stop Select/Visual mode
	if (km_stopsel
		&& (nv_cmds[idx].cmd_flags & NV_STS)
		&& !(mod_mask & MOD_MASK_SHIFT))
	{
	    end_visual_mode();
	    redraw_curbuf_later(INVERTED);
	}

	// Keys that work different when 'keymodel' contains "startsel"
	if (km_startsel)
	{
	    if (nv_cmds[idx].cmd_flags & NV_SS)
	    {
		unshift_special(&ca);
		idx = find_command(ca.cmdchar);
		if (idx < 0)
		{
		    // Just in case
		    clearopbeep(oap);
		    goto normal_end;
		}
	    }
	    else if ((nv_cmds[idx].cmd_flags & NV_SSS)
					       && (mod_mask & MOD_MASK_SHIFT))
		mod_mask &= ~MOD_MASK_SHIFT;
	}
    }

#ifdef FEAT_RIGHTLEFT
    if (curwin->w_p_rl && KeyTyped && !KeyStuffed
					  && (nv_cmds[idx].cmd_flags & NV_RL))
    {
	// Invert horizontal movements and operations.  Only when typed by the
	// user directly, not when the result of a mapping or "x" translated
	// to "dl".
	switch (ca.cmdchar)
	{
	    case 'l':	    ca.cmdchar = 'h'; break;
	    case K_RIGHT:   ca.cmdchar = K_LEFT; break;
	    case K_S_RIGHT: ca.cmdchar = K_S_LEFT; break;
	    case K_C_RIGHT: ca.cmdchar = K_C_LEFT; break;
	    case 'h':	    ca.cmdchar = 'l'; break;
	    case K_LEFT:    ca.cmdchar = K_RIGHT; break;
	    case K_S_LEFT:  ca.cmdchar = K_S_RIGHT; break;
	    case K_C_LEFT:  ca.cmdchar = K_C_RIGHT; break;
	    case '>':	    ca.cmdchar = '<'; break;
	    case '<':	    ca.cmdchar = '>'; break;
	}
	idx = find_command(ca.cmdchar);
    }
#endif

    /*
     * Get an additional character if we need one.
     */
    if ((nv_cmds[idx].cmd_flags & NV_NCH)
	    && (((nv_cmds[idx].cmd_flags & NV_NCH_NOP) == NV_NCH_NOP
		    && oap->op_type == OP_NOP)
		|| (nv_cmds[idx].cmd_flags & NV_NCH_ALW) == NV_NCH_ALW
		|| (ca.cmdchar == 'q'
		    && oap->op_type == OP_NOP
		    && reg_recording == 0
		    && reg_executing == 0)
		|| ((ca.cmdchar == 'a' || ca.cmdchar == 'i')
		    && (oap->op_type != OP_NOP || VIsual_active))))
    {
	int	*cp;
	int	repl = FALSE;	// get character for replace mode
	int	lit = FALSE;	// get extra character literally
	int	langmap_active = FALSE;    // using :lmap mappings
	int	lang;		// getting a text character
#ifdef HAVE_INPUT_METHOD
	int	save_smd;	// saved value of p_smd
#endif

	++no_mapping;
	++allow_keys;		// no mapping for nchar, but allow key codes
	// Don't generate a CursorHold event here, most commands can't handle
	// it, e.g., nv_replace(), nv_csearch().
	did_cursorhold = TRUE;
	if (ca.cmdchar == 'g')
	{
	    /*
	     * For 'g' get the next character now, so that we can check for
	     * "gr", "g'" and "g`".
	     */
	    ca.nchar = plain_vgetc();
	    LANGMAP_ADJUST(ca.nchar, TRUE);
#ifdef FEAT_CMDL_INFO
	    need_flushbuf |= add_to_showcmd(ca.nchar);
#endif
	    if (ca.nchar == 'r' || ca.nchar == '\'' || ca.nchar == '`'
						       || ca.nchar == Ctrl_BSL)
	    {
		cp = &ca.extra_char;	// need to get a third character
		if (ca.nchar != 'r')
		    lit = TRUE;			// get it literally
		else
		    repl = TRUE;		// get it in replace mode
	    }
	    else
		cp = NULL;		// no third character needed
	}
	else
	{
	    if (ca.cmdchar == 'r')		// get it in replace mode
		repl = TRUE;
	    cp = &ca.nchar;
	}
	lang = (repl || (nv_cmds[idx].cmd_flags & NV_LANG));

	/*
	 * Get a second or third character.
	 */
	if (cp != NULL)
	{
	    if (repl)
	    {
		State = REPLACE;	// pretend Replace mode
#ifdef CURSOR_SHAPE
		ui_cursor_shape();	// show different cursor shape
#endif
	    }
	    if (lang && curbuf->b_p_iminsert == B_IMODE_LMAP)
	    {
		// Allow mappings defined with ":lmap".
		--no_mapping;
		--allow_keys;
		if (repl)
		    State = LREPLACE;
		else
		    State = LANGMAP;
		langmap_active = TRUE;
	    }
#ifdef HAVE_INPUT_METHOD
	    save_smd = p_smd;
	    p_smd = FALSE;	// Don't let the IM code show the mode here
	    if (lang && curbuf->b_p_iminsert == B_IMODE_IM)
		im_set_active(TRUE);
#endif
	    if ((State & INSERT) && !p_ek)
	    {
#ifdef FEAT_JOB_CHANNEL
		ch_log_output = TRUE;
#endif
		// Disable bracketed paste and modifyOtherKeys here, we won't
		// recognize the escape sequences with 'esckeys' off.
		out_str(T_BD);
		out_str(T_CTE);
	    }

	    *cp = plain_vgetc();

	    if ((State & INSERT) && !p_ek)
	    {
#ifdef FEAT_JOB_CHANNEL
		ch_log_output = TRUE;
#endif
		// Re-enable bracketed paste mode and modifyOtherKeys
		out_str(T_BE);
		out_str(T_CTI);
	    }

	    if (langmap_active)
	    {
		// Undo the decrement done above
		++no_mapping;
		++allow_keys;
		State = NORMAL_BUSY;
	    }
#ifdef HAVE_INPUT_METHOD
	    if (lang)
	    {
		if (curbuf->b_p_iminsert != B_IMODE_LMAP)
		    im_save_status(&curbuf->b_p_iminsert);
		im_set_active(FALSE);
	    }
	    p_smd = save_smd;
#endif
	    State = NORMAL_BUSY;
#ifdef FEAT_CMDL_INFO
	    need_flushbuf |= add_to_showcmd(*cp);
#endif

	    if (!lit)
	    {
#ifdef FEAT_DIGRAPHS
		// Typing CTRL-K gets a digraph.
		if (*cp == Ctrl_K
			&& ((nv_cmds[idx].cmd_flags & NV_LANG)
			    || cp == &ca.extra_char)
			&& vim_strchr(p_cpo, CPO_DIGRAPH) == NULL)
		{
		    c = get_digraph(FALSE);
		    if (c > 0)
		    {
			*cp = c;
# ifdef FEAT_CMDL_INFO
			// Guessing how to update showcmd here...
			del_from_showcmd(3);
			need_flushbuf |= add_to_showcmd(*cp);
# endif
		    }
		}
#endif

		// adjust chars > 127, except after "tTfFr" commands
		LANGMAP_ADJUST(*cp, !lang);
#ifdef FEAT_RIGHTLEFT
		// adjust Hebrew mapped char
		if (p_hkmap && lang && KeyTyped)
		    *cp = hkmap(*cp);
#endif
	    }

	    /*
	     * When the next character is CTRL-\ a following CTRL-N means the
	     * command is aborted and we go to Normal mode.
	     */
	    if (cp == &ca.extra_char
		    && ca.nchar == Ctrl_BSL
		    && (ca.extra_char == Ctrl_N || ca.extra_char == Ctrl_G))
	    {
		ca.cmdchar = Ctrl_BSL;
		ca.nchar = ca.extra_char;
		idx = find_command(ca.cmdchar);
	    }
	    else if ((ca.nchar == 'n' || ca.nchar == 'N') && ca.cmdchar == 'g')
		ca.oap->op_type = get_op_type(*cp, NUL);
	    else if (*cp == Ctrl_BSL)
	    {
		long towait = (p_ttm >= 0 ? p_ttm : p_tm);

		// There is a busy wait here when typing "f<C-\>" and then
		// something different from CTRL-N.  Can't be avoided.
		while ((c = vpeekc()) <= 0 && towait > 0L)
		{
		    do_sleep(towait > 50L ? 50L : towait, FALSE);
		    towait -= 50L;
		}
		if (c > 0)
		{
		    c = plain_vgetc();
		    if (c != Ctrl_N && c != Ctrl_G)
			vungetc(c);
		    else
		    {
			ca.cmdchar = Ctrl_BSL;
			ca.nchar = c;
			idx = find_command(ca.cmdchar);
		    }
		}
	    }

	    // When getting a text character and the next character is a
	    // multi-byte character, it could be a composing character.
	    // However, don't wait for it to arrive. Also, do enable mapping,
	    // because if it's put back with vungetc() it's too late to apply
	    // mapping.
	    --no_mapping;
	    while (enc_utf8 && lang && (c = vpeekc()) > 0
				 && (c >= 0x100 || MB_BYTE2LEN(vpeekc()) > 1))
	    {
		c = plain_vgetc();
		if (!utf_iscomposing(c))
		{
		    vungetc(c);		// it wasn't, put it back
		    break;
		}
		else if (ca.ncharC1 == 0)
		    ca.ncharC1 = c;
		else
		    ca.ncharC2 = c;
	    }
	    ++no_mapping;
	}
	--no_mapping;
	--allow_keys;
    }

#ifdef FEAT_CMDL_INFO
    /*
     * Flush the showcmd characters onto the screen so we can see them while
     * the command is being executed.  Only do this when the shown command was
     * actually displayed, otherwise this will slow down a lot when executing
     * mappings.
     */
    if (need_flushbuf)
	out_flush();
#endif
    if (ca.cmdchar != K_IGNORE)
    {
	if (ex_normal_busy)
	    did_cursorhold = save_did_cursorhold;
	else
	    did_cursorhold = FALSE;
    }

    State = NORMAL;

    if (ca.nchar == ESC)
    {
	clearop(oap);
	if (restart_edit == 0 && goto_im())
	    restart_edit = 'a';
	goto normal_end;
    }

    if (ca.cmdchar != K_IGNORE)
    {
	msg_didout = FALSE;    // don't scroll screen up for normal command
	msg_col = 0;
    }

    old_pos = curwin->w_cursor;		// remember where cursor was

    // When 'keymodel' contains "startsel" some keys start Select/Visual
    // mode.
    if (!VIsual_active && km_startsel)
    {
	if (nv_cmds[idx].cmd_flags & NV_SS)
	{
	    start_selection();
	    unshift_special(&ca);
	    idx = find_command(ca.cmdchar);
	}
	else if ((nv_cmds[idx].cmd_flags & NV_SSS)
					   && (mod_mask & MOD_MASK_SHIFT))
	{
	    start_selection();
	    mod_mask &= ~MOD_MASK_SHIFT;
	}
    }

    /*
     * Execute the command!
     * Call the command function found in the commands table.
     */
    ca.arg = nv_cmds[idx].cmd_arg;
    (nv_cmds[idx].cmd_func)(&ca);

    /*
     * If we didn't start or finish an operator, reset oap->regname, unless we
     * need it later.
     */
    if (!finish_op
	    && !oap->op_type
	    && (idx < 0 || !(nv_cmds[idx].cmd_flags & NV_KEEPREG)))
    {
	clearop(oap);
#ifdef FEAT_EVAL
	reset_reg_var();
#endif
    }

    // Get the length of mapped chars again after typing a count, second
    // character or "z333<cr>".
    if (old_mapped_len > 0)
	old_mapped_len = typebuf_maplen();

    /*
     * If an operation is pending, handle it.  But not for K_IGNORE or
     * K_MOUSEMOVE.
     */
    if (ca.cmdchar != K_IGNORE && ca.cmdchar != K_MOUSEMOVE)
	do_pending_operator(&ca, old_col, FALSE);

    /*
     * Wait for a moment when a message is displayed that will be overwritten
     * by the mode message.
     * In Visual mode and with "^O" in Insert mode, a short message will be
     * overwritten by the mode message.  Wait a bit, until a key is hit.
     * In Visual mode, it's more important to keep the Visual area updated
     * than keeping a message (e.g. from a /pat search).
     * Only do this if the command was typed, not from a mapping.
     * Don't wait when emsg_silent is non-zero.
     * Also wait a bit after an error message, e.g. for "^O:".
     * Don't redraw the screen, it would remove the message.
     */
    if (       ((p_smd
		    && msg_silent == 0
		    && (restart_edit != 0
			|| (VIsual_active
			    && old_pos.lnum == curwin->w_cursor.lnum
			    && old_pos.col == curwin->w_cursor.col)
		       )
		    && (clear_cmdline
			|| redraw_cmdline)
		    && (msg_didout || (msg_didany && msg_scroll))
		    && !msg_nowait
		    && KeyTyped)
		|| (restart_edit != 0
		    && !VIsual_active
		    && (msg_scroll
			|| emsg_on_display)))
	    && oap->regname == 0
	    && !(ca.retval & CA_COMMAND_BUSY)
	    && stuff_empty()
	    && typebuf_typed()
	    && emsg_silent == 0
	    && !in_assert_fails
	    && !did_wait_return
	    && oap->op_type == OP_NOP)
    {
	int	save_State = State;

	// Draw the cursor with the right shape here
	if (restart_edit != 0)
	    State = INSERT;

	// If need to redraw, and there is a "keep_msg", redraw before the
	// delay
	if (must_redraw && keep_msg != NULL && !emsg_on_display)
	{
	    char_u	*kmsg;

	    kmsg = keep_msg;
	    keep_msg = NULL;
	    // Showmode() will clear keep_msg, but we want to use it anyway.
	    // First update w_topline.
	    setcursor();
	    update_screen(0);
	    // now reset it, otherwise it's put in the history again
	    keep_msg = kmsg;

	    kmsg = vim_strsave(keep_msg);
	    if (kmsg != NULL)
	    {
		msg_attr((char *)kmsg, keep_msg_attr);
		vim_free(kmsg);
	    }
	}
	setcursor();
#ifdef CURSOR_SHAPE
	ui_cursor_shape();		// may show different cursor shape
#endif
	cursor_on();
	out_flush();
	if (msg_scroll || emsg_on_display)
	    ui_delay(1003L, TRUE);	// wait at least one second
	ui_delay(3003L, FALSE);		// wait up to three seconds
	State = save_State;

	msg_scroll = FALSE;
	emsg_on_display = FALSE;
    }

    /*
     * Finish up after executing a Normal mode command.
     */
normal_end:

    msg_nowait = FALSE;

#ifdef FEAT_EVAL
    if (finish_op)
	reset_reg_var();
#endif

    // Reset finish_op, in case it was set
#ifdef CURSOR_SHAPE
    c = finish_op;
#endif
    finish_op = FALSE;
    trigger_modechanged();
#ifdef CURSOR_SHAPE
    // Redraw the cursor with another shape, if we were in Operator-pending
    // mode or did a replace command.
    if (c || ca.cmdchar == 'r')
    {
	ui_cursor_shape();		// may show different cursor shape
# ifdef FEAT_MOUSESHAPE
	update_mouseshape(-1);
# endif
    }
#endif

#ifdef FEAT_CMDL_INFO
    if (oap->op_type == OP_NOP && oap->regname == 0
	    && ca.cmdchar != K_CURSORHOLD)
	clear_showcmd();
#endif

    checkpcmark();		// check if we moved since setting pcmark
    vim_free(ca.searchbuf);

    if (has_mbyte)
	mb_adjust_cursor();

    if (curwin->w_p_scb && toplevel)
    {
	validate_cursor();	// may need to update w_leftcol
	do_check_scrollbind(TRUE);
    }

    if (curwin->w_p_crb && toplevel)
    {
	validate_cursor();	// may need to update w_leftcol
	do_check_cursorbind();
    }

#ifdef FEAT_TERMINAL
    // don't go to Insert mode if a terminal has a running job
    if (term_job_running(curbuf->b_term))
	restart_edit = 0;
#endif

    /*
     * May restart edit(), if we got here with CTRL-O in Insert mode (but not
     * if still inside a mapping that started in Visual mode).
     * May switch from Visual to Select mode after CTRL-O command.
     */
    if (       oap->op_type == OP_NOP
	    && ((restart_edit != 0 && !VIsual_active && old_mapped_len == 0)
		|| restart_VIsual_select == 1)
	    && !(ca.retval & CA_COMMAND_BUSY)
	    && stuff_empty()
	    && oap->regname == 0)
    {
	if (restart_VIsual_select == 1)
	{
	    VIsual_select = TRUE;
	    trigger_modechanged();
	    showmode();
	    restart_VIsual_select = 0;
	}
	if (restart_edit != 0 && !VIsual_active && old_mapped_len == 0)
	    (void)edit(restart_edit, FALSE, 1L);
    }

    if (restart_VIsual_select == 2)
	restart_VIsual_select = 1;

    // Save count before an operator for next time.
    opcount = ca.opcount;
}

Project: vim

n_start_visual_mode(int c)
{
#ifdef FEAT_CONCEAL
    int cursor_line_was_concealed = curwin->w_p_cole > 0
						&& conceal_cursor_line(curwin);
#endif

    VIsual_mode = c;
    VIsual_active = TRUE;
    VIsual_reselect = TRUE;

    // Corner case: the 0 position in a tab may change when going into
    // virtualedit.  Recalculate curwin->w_cursor to avoid bad highlighting.
    if (c == Ctrl_V && (get_ve_flags() & VE_BLOCK) && gchar_cursor() == TAB)
    {
	validate_virtcol();
	coladvance(curwin->w_virtcol);
    }
    VIsual = curwin->w_cursor;

#ifdef FEAT_FOLDING
    foldAdjustVisual();
#endif

    trigger_modechanged();
    setmouse();
#ifdef FEAT_CONCEAL
    // Check if redraw is needed after changing the state.
    conceal_check_cursor_line(cursor_line_was_concealed);
#endif

    if (p_smd && msg_silent == 0)
	redraw_cmdline = TRUE;	// show visual mode later
#ifdef FEAT_CLIPBOARD
    // Make sure the clipboard gets updated.  Needed because start and
    // end may still be the same, and the selection needs to be owned
    clip_star.vmode = NUL;
#endif

    // Only need to redraw this line, unless still need to redraw an old
    // Visual area (when 'lazyredraw' is set).
    if (curwin->w_redr_type < INVERTED)
    {
	curwin->w_old_cursor_lnum = curwin->w_cursor.lnum;
	curwin->w_old_visual_lnum = curwin->w_cursor.lnum;
    }
}

Project: vim

nv_ctrlo(cmdarg_T *cap)
{
    if (VIsual_active && VIsual_select)
    {
	VIsual_select = FALSE;
	trigger_modechanged();
	showmode();
	restart_VIsual_select = 2;	// restart Select mode later
    }
    else
    {
	cap->count1 = -cap->count1;
	nv_pcmark(cap);
    }
}

Project: vim

nv_visual(cmdarg_T *cap)
{
    if (cap->cmdchar == Ctrl_Q)
	cap->cmdchar = Ctrl_V;

    // 'v', 'V' and CTRL-V can be used while an operator is pending to make it
    // characterwise, linewise, or blockwise.
    if (cap->oap->op_type != OP_NOP)
    {
	motion_force = cap->oap->motion_force = cap->cmdchar;
	finish_op = FALSE;	// operator doesn't finish now but later
	return;
    }

    VIsual_select = cap->arg;
    if (VIsual_active)	    // change Visual mode
    {
	if (VIsual_mode == cap->cmdchar)    // stop visual mode
	    end_visual_mode();
	else				    // toggle char/block mode
	{				    //	   or char/line mode
	    VIsual_mode = cap->cmdchar;
	    showmode();
	    trigger_modechanged();
	}
	redraw_curbuf_later(INVERTED);	    // update the inversion
    }
    else		    // start Visual mode
    {
	check_visual_highlight();
	if (cap->count0 > 0 && resel_VIsual_mode != NUL)
	{
	    // use previously selected part
	    VIsual = curwin->w_cursor;

	    VIsual_active = TRUE;
	    VIsual_reselect = TRUE;
	    if (!cap->arg)
		// start Select mode when 'selectmode' contains "cmd"
		may_start_select('c');
	    setmouse();
	    if (p_smd && msg_silent == 0)
		redraw_cmdline = TRUE;	    // show visual mode later
	    /*
	     * For V and ^V, we multiply the number of lines even if there
	     * was only one -- webb
	     */
	    if (resel_VIsual_mode != 'v' || resel_VIsual_line_count > 1)
	    {
		curwin->w_cursor.lnum +=
				    resel_VIsual_line_count * cap->count0 - 1;
		check_cursor();
	    }
	    VIsual_mode = resel_VIsual_mode;
	    if (VIsual_mode == 'v')
	    {
		if (resel_VIsual_line_count <= 1)
		{
		    validate_virtcol();
		    curwin->w_curswant = curwin->w_virtcol
					+ resel_VIsual_vcol * cap->count0 - 1;
		}
		else
		    curwin->w_curswant = resel_VIsual_vcol;
		coladvance(curwin->w_curswant);
	    }
	    if (resel_VIsual_vcol == MAXCOL)
	    {
		curwin->w_curswant = MAXCOL;
		coladvance((colnr_T)MAXCOL);
	    }
	    else if (VIsual_mode == Ctrl_V)
	    {
		validate_virtcol();
		curwin->w_curswant = curwin->w_virtcol
					+ resel_VIsual_vcol * cap->count0 - 1;
		coladvance(curwin->w_curswant);
	    }
	    else
		curwin->w_set_curswant = TRUE;
	    redraw_curbuf_later(INVERTED);	// show the inversion
	}
	else
	{
	    if (!cap->arg)
		// start Select mode when 'selectmode' contains "cmd"
		may_start_select('c');
	    n_start_visual_mode(cap->cmdchar);
	    if (VIsual_mode != 'V' && *p_sel == 'e')
		++cap->count1;  // include one more char
	    if (cap->count0 > 0 && --cap->count1 > 0)
	    {
		// With a count select that many characters or lines.
		if (VIsual_mode == 'v' || VIsual_mode == Ctrl_V)
		    nv_right(cap);
		else if (VIsual_mode == 'V')
		    nv_down(cap);
	    }
	}
    }
}

Project: vim

nv_ident(cmdarg_T *cap)
{
    char_u	*ptr = NULL;
    char_u	*buf;
    unsigned	buflen;
    char_u	*newbuf;
    char_u	*p;
    char_u	*kp;		// value of 'keywordprg'
    int		kp_help;	// 'keywordprg' is ":he"
    int		kp_ex;		// 'keywordprg' starts with ":"
    int		n = 0;		// init for GCC
    int		cmdchar;
    int		g_cmd;		// "g" command
    int		tag_cmd = FALSE;
    char_u	*aux_ptr;
    int		isman;
    int		isman_s;

    if (cap->cmdchar == 'g')	// "g*", "g#", "g]" and "gCTRL-]"
    {
	cmdchar = cap->nchar;
	g_cmd = TRUE;
    }
    else
    {
	cmdchar = cap->cmdchar;
	g_cmd = FALSE;
    }

    if (cmdchar == POUND)	// the pound sign, '#' for English keyboards
	cmdchar = '#';

    /*
     * The "]", "CTRL-]" and "K" commands accept an argument in Visual mode.
     */
    if (cmdchar == ']' || cmdchar == Ctrl_RSB || cmdchar == 'K')
    {
	if (VIsual_active && get_visual_text(cap, &ptr, &n) == FAIL)
	    return;
	if (checkclearopq(cap->oap))
	    return;
    }

    if (ptr == NULL && (n = find_ident_under_cursor(&ptr,
		    (cmdchar == '*' || cmdchar == '#')
				 ? FIND_IDENT|FIND_STRING : FIND_IDENT)) == 0)
    {
	clearop(cap->oap);
	return;
    }

    // Allocate buffer to put the command in.  Inserting backslashes can
    // double the length of the word.  p_kp / curbuf->b_p_kp could be added
    // and some numbers.
    kp = (*curbuf->b_p_kp == NUL ? p_kp : curbuf->b_p_kp);
    kp_help = (*kp == NUL || STRCMP(kp, ":he") == 0
						 || STRCMP(kp, ":help") == 0);
    if (kp_help && *skipwhite(ptr) == NUL)
    {
	emsg(_(e_noident));	 // found white space only
	return;
    }
    kp_ex = (*kp == ':');
    buflen = (unsigned)(n * 2 + 30 + STRLEN(kp));
    buf = alloc(buflen);
    if (buf == NULL)
	return;
    buf[0] = NUL;

    switch (cmdchar)
    {
	case '*':
	case '#':
	    /*
	     * Put cursor at start of word, makes search skip the word
	     * under the cursor.
	     * Call setpcmark() first, so "*``" puts the cursor back where
	     * it was.
	     */
	    setpcmark();
	    curwin->w_cursor.col = (colnr_T) (ptr - ml_get_curline());

	    if (!g_cmd && vim_iswordp(ptr))
		STRCPY(buf, "\\<");
	    no_smartcase = TRUE;	// don't use 'smartcase' now
	    break;

	case 'K':
	    if (kp_help)
		STRCPY(buf, "he! ");
	    else if (kp_ex)
	    {
		if (cap->count0 != 0)
		    vim_snprintf((char *)buf, buflen, "%s %ld",
							     kp, cap->count0);
		else
		    STRCPY(buf, kp);
		STRCAT(buf, " ");
	    }
	    else
	    {
		// An external command will probably use an argument starting
		// with "-" as an option.  To avoid trouble we skip the "-".
		while (*ptr == '-' && n > 0)
		{
		    ++ptr;
		    --n;
		}
		if (n == 0)
		{
		    emsg(_(e_noident));	 // found dashes only
		    vim_free(buf);
		    return;
		}

		// When a count is given, turn it into a range.  Is this
		// really what we want?
		isman = (STRCMP(kp, "man") == 0);
		isman_s = (STRCMP(kp, "man -s") == 0);
		if (cap->count0 != 0 && !(isman || isman_s))
		    sprintf((char *)buf, ".,.+%ld", cap->count0 - 1);

		STRCAT(buf, "! ");
		if (cap->count0 == 0 && isman_s)
		    STRCAT(buf, "man");
		else
		    STRCAT(buf, kp);
		STRCAT(buf, " ");
		if (cap->count0 != 0 && (isman || isman_s))
		{
		    sprintf((char *)buf + STRLEN(buf), "%ld", cap->count0);
		    STRCAT(buf, " ");
		}
	    }
	    break;

	case ']':
	    tag_cmd = TRUE;
#ifdef FEAT_CSCOPE
	    if (p_cst)
		STRCPY(buf, "cstag ");
	    else
#endif
		STRCPY(buf, "ts ");
	    break;

	default:
	    tag_cmd = TRUE;
	    if (curbuf->b_help)
		STRCPY(buf, "he! ");
	    else
	    {
		if (g_cmd)
		    STRCPY(buf, "tj ");
		else if (cap->count0 == 0)
		    STRCPY(buf, "ta ");
		else
		    sprintf((char *)buf, ":%ldta ", cap->count0);
	    }
    }

    /*
     * Now grab the chars in the identifier
     */
    if (cmdchar == 'K' && !kp_help)
    {
	ptr = vim_strnsave(ptr, n);
	if (kp_ex)
	    // Escape the argument properly for an Ex command
	    p = vim_strsave_fnameescape(ptr, VSE_NONE);
	else
	    // Escape the argument properly for a shell command
	    p = vim_strsave_shellescape(ptr, TRUE, TRUE);
	vim_free(ptr);
	if (p == NULL)
	{
	    vim_free(buf);
	    return;
	}
	newbuf = vim_realloc(buf, STRLEN(buf) + STRLEN(p) + 1);
	if (newbuf == NULL)
	{
	    vim_free(buf);
	    vim_free(p);
	    return;
	}
	buf = newbuf;
	STRCAT(buf, p);
	vim_free(p);
    }
    else
    {
	if (cmdchar == '*')
	    aux_ptr = (char_u *)(magic_isset() ? "/.*~[^$\\" : "/^$\\");
	else if (cmdchar == '#')
	    aux_ptr = (char_u *)(magic_isset() ? "/?.*~[^$\\" : "/?^$\\");
	else if (tag_cmd)
	{
	    if (curbuf->b_help)
		// ":help" handles unescaped argument
		aux_ptr = (char_u *)"";
	    else
		aux_ptr = (char_u *)"\\|\"\n[";
	}
	else
	    aux_ptr = (char_u *)"\\|\"\n*?[";

	p = buf + STRLEN(buf);
	while (n-- > 0)
	{
	    // put a backslash before \ and some others
	    if (vim_strchr(aux_ptr, *ptr) != NULL)
		*p++ = '\\';
	    // When current byte is a part of multibyte character, copy all
	    // bytes of that character.
	    if (has_mbyte)
	    {
		int i;
		int len = (*mb_ptr2len)(ptr) - 1;

		for (i = 0; i < len && n >= 1; ++i, --n)
		    *p++ = *ptr++;
	    }
	    *p++ = *ptr++;
	}
	*p = NUL;
    }

    /*
     * Execute the command.
     */
    if (cmdchar == '*' || cmdchar == '#')
    {
	if (!g_cmd && (has_mbyte
		    ? vim_iswordp(mb_prevptr(ml_get_curline(), ptr))
		    : vim_iswordc(ptr[-1])))
	    STRCAT(buf, "\\>");

	// put pattern in search history
	init_history();
	add_to_history(HIST_SEARCH, buf, TRUE, NUL);

	(void)normal_search(cap, cmdchar == '*' ? '/' : '?', buf, 0, NULL);
    }
    else
    {
	g_tag_at_cursor = TRUE;
	do_cmdline_cmd(buf);
	g_tag_at_cursor = FALSE;
    }

    vim_free(buf);
}

Project: vim

end_visual_mode_keep_button()
{
#ifdef FEAT_CLIPBOARD
    /*
     * If we are using the clipboard, then remember what was selected in case
     * we need to paste it somewhere while we still own the selection.
     * Only do this when the clipboard is already owned.  Don't want to grab
     * the selection when hitting ESC.
     */
    if (clip_star.available && clip_star.owned)
	clip_auto_select();

# if defined(FEAT_EVAL)
    // Emit a TextYankPost for the automatic copy of the selection into the
    // star and/or plus register.
    if (has_textyankpost())
    {
	if (clip_isautosel_star())
	    call_yank_do_autocmd('*');
	if (clip_isautosel_plus())
	    call_yank_do_autocmd('+');
    }
# endif
#endif

    VIsual_active = FALSE;
    setmouse();
    mouse_dragging = 0;

    // Save the current VIsual area for '< and '> marks, and "gv"
    curbuf->b_visual.vi_mode = VIsual_mode;
    curbuf->b_visual.vi_start = VIsual;
    curbuf->b_visual.vi_end = curwin->w_cursor;
    curbuf->b_visual.vi_curswant = curwin->w_curswant;
#ifdef FEAT_EVAL
    curbuf->b_visual_mode_eval = VIsual_mode;
#endif
    if (!virtual_active())
	curwin->w_cursor.coladd = 0;
    may_clear_cmdline();

    adjust_cursor_eol();
    trigger_modechanged();
}

Project: vim

nv_screengo(oparg_T *oap, int dir, long dist)
{
    int		linelen = linetabsize(ml_get_curline());
    int		retval = OK;
    int		atend = FALSE;
    int		n;
    int		col_off1;	// margin offset for first screen line
    int		col_off2;	// margin offset for wrapped screen line
    int		width1;		// text width for first screen line
    int		width2;		// text width for wrapped screen line

    oap->motion_type = MCHAR;
    oap->inclusive = (curwin->w_curswant == MAXCOL);

    col_off1 = curwin_col_off();
    col_off2 = col_off1 - curwin_col_off2();
    width1 = curwin->w_width - col_off1;
    width2 = curwin->w_width - col_off2;
    if (width2 == 0)
	width2 = 1; // avoid divide by zero

    if (curwin->w_width != 0)
    {
      /*
       * Instead of sticking at the last character of the buffer line we
       * try to stick in the last column of the screen.
       */
      if (curwin->w_curswant == MAXCOL)
      {
	atend = TRUE;
	validate_virtcol();
	if (width1 <= 0)
	    curwin->w_curswant = 0;
	else
	{
	    curwin->w_curswant = width1 - 1;
	    if (curwin->w_virtcol > curwin->w_curswant)
		curwin->w_curswant += ((curwin->w_virtcol
			     - curwin->w_curswant - 1) / width2 + 1) * width2;
	}
      }
      else
      {
	if (linelen > width1)
	    n = ((linelen - width1 - 1) / width2 + 1) * width2 + width1;
	else
	    n = width1;
	if (curwin->w_curswant >= (colnr_T)n)
	    curwin->w_curswant = n - 1;
      }

      while (dist--)
      {
	if (dir == BACKWARD)
	{
	    if ((long)curwin->w_curswant >= width1
#ifdef FEAT_FOLDING
		    && !hasFolding(curwin->w_cursor.lnum, NULL, NULL)
#endif
	       )
		// Move back within the line. This can give a negative value
		// for w_curswant if width1 < width2 (with cpoptions+=n),
		// which will get clipped to column 0.
		curwin->w_curswant -= width2;
	    else
	    {
		// to previous line
#ifdef FEAT_FOLDING
		// Move to the start of a closed fold.  Don't do that when
		// 'foldopen' contains "all": it will open in a moment.
		if (!(fdo_flags & FDO_ALL))
		    (void)hasFolding(curwin->w_cursor.lnum,
						&curwin->w_cursor.lnum, NULL);
#endif
		if (curwin->w_cursor.lnum == 1)
		{
		    retval = FAIL;
		    break;
		}
		--curwin->w_cursor.lnum;

		linelen = linetabsize(ml_get_curline());
		if (linelen > width1)
		    curwin->w_curswant += (((linelen - width1 - 1) / width2)
								+ 1) * width2;
	    }
	}
	else // dir == FORWARD
	{
	    if (linelen > width1)
		n = ((linelen - width1 - 1) / width2 + 1) * width2 + width1;
	    else
		n = width1;
	    if (curwin->w_curswant + width2 < (colnr_T)n
#ifdef FEAT_FOLDING
		    && !hasFolding(curwin->w_cursor.lnum, NULL, NULL)
#endif
		    )
		// move forward within line
		curwin->w_curswant += width2;
	    else
	    {
		// to next line
#ifdef FEAT_FOLDING
		// Move to the end of a closed fold.
		(void)hasFolding(curwin->w_cursor.lnum, NULL,
						      &curwin->w_cursor.lnum);
#endif
		if (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count)
		{
		    retval = FAIL;
		    break;
		}
		curwin->w_cursor.lnum++;
		curwin->w_curswant %= width2;
		// Check if the cursor has moved below the number display
		// when width1 < width2 (with cpoptions+=n). Subtract width2
		// to get a negative value for w_curswant, which will get
		// clipped to column 0.
		if (curwin->w_curswant >= width1)
		    curwin->w_curswant -= width2;
		linelen = linetabsize(ml_get_curline());
	    }
	}
      }
    }

    if (virtual_active() && atend)
	coladvance(MAXCOL);
    else
	coladvance(curwin->w_curswant);

    if (curwin->w_cursor.col > 0 && curwin->w_p_wrap)
    {
	colnr_T virtcol;
	int	c;

	/*
	 * Check for landing on a character that got split at the end of the
	 * last line.  We want to advance a screenline, not end up in the same
	 * screenline or move two screenlines.
	 */
	validate_virtcol();
	virtcol = curwin->w_virtcol;
#if defined(FEAT_LINEBREAK)
	if (virtcol > (colnr_T)width1 && *get_showbreak_value(curwin) != NUL)
	    virtcol -= vim_strsize(get_showbreak_value(curwin));
#endif

	c = (*mb_ptr2char)(ml_get_cursor());
	if (dir == FORWARD && virtcol < curwin->w_curswant
		&& (curwin->w_curswant <= (colnr_T)width1)
		&& !vim_isprintc(c) && c > 255)
	    oneright();

	if (virtcol > curwin->w_curswant
		&& (curwin->w_curswant < (colnr_T)width1
		    ? (curwin->w_curswant > (colnr_T)width1 / 2)
		    : ((curwin->w_curswant - width1) % width2
						      > (colnr_T)width2 / 2)))
	    --curwin->w_cursor.col;
    }

    if (atend)
	curwin->w_curswant = MAXCOL;	    // stick in the last column

    return retval;
}

Project: vim

nv_ctrlg(cmdarg_T *cap)
{
    if (VIsual_active)	// toggle Selection/Visual mode
    {
	VIsual_select = !VIsual_select;
	trigger_modechanged();
	showmode();
    }
    else if (!checkclearop(cap->oap))
	// print full name if count given or :cd used
	fileinfo((int)cap->count0, FALSE, TRUE);
}