aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/normal.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/normal.c')
-rw-r--r--src/nvim/normal.c1443
1 files changed, 889 insertions, 554 deletions
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 713aa55500..de575c0234 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -61,10 +61,35 @@
#include "nvim/mouse.h"
#include "nvim/undo.h"
#include "nvim/window.h"
+#include "nvim/state.h"
#include "nvim/event/loop.h"
#include "nvim/os/time.h"
#include "nvim/os/input.h"
+typedef struct normal_state {
+ VimState state;
+ linenr_T conceal_old_cursor_line;
+ linenr_T conceal_new_cursor_line;
+ bool command_finished;
+ bool ctrl_w;
+ bool need_flushbuf;
+ bool conceal_update_lines;
+ bool set_prevcount;
+ bool previous_got_int; // `got_int` was true
+ bool cmdwin; // command-line window normal mode
+ bool noexmode; // true if the normal mode was pushed from
+ // ex mode(:global or :visual for example)
+ bool toplevel; // top-level normal mode
+ oparg_T oa; // operator arguments
+ cmdarg_T ca; // command arguments
+ int mapped_len;
+ int old_mapped_len;
+ int idx;
+ int c;
+ int old_col;
+ pos_T old_pos;
+} NormalState;
+
/*
* The Visual area is remembered for reselection.
*/
@@ -79,6 +104,14 @@ static int restart_VIsual_select = 0;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "normal.c.generated.h"
#endif
+
+static inline void normal_state_init(NormalState *s)
+{
+ memset(s, 0, sizeof(NormalState));
+ s->state.check = normal_check;
+ s->state.execute = normal_execute;
+}
+
/*
* nv_*(): functions called to handle Normal and Visual mode commands.
* n_*(): functions called to handle Normal mode commands.
@@ -315,7 +348,7 @@ static const struct nv_cmd {
{K_SELECT, nv_select, 0, 0},
{K_F8, farsi_fkey, 0, 0},
{K_F9, farsi_fkey, 0, 0},
- {K_CURSORHOLD, nv_cursorhold, NV_KEEPREG, 0},
+ {K_EVENT, nv_event, NV_KEEPREG, 0},
};
/* Number of commands in nv_cmds[]. */
@@ -418,650 +451,946 @@ static int find_command(int cmdchar)
return idx;
}
-/*
- * Execute a command in Normal mode.
- */
-void
-normal_cmd (
- oparg_T *oap,
- bool toplevel /* true when called from main() */
-)
-{
- cmdarg_T ca; /* command arguments */
- int c;
- bool ctrl_w = false; /* got CTRL-W command */
- int old_col = curwin->w_curswant;
- bool need_flushbuf; /* need to call ui_flush() */
- pos_T old_pos; /* cursor position before command */
- int mapped_len;
- static int old_mapped_len = 0;
- int idx;
- bool set_prevcount = false;
+// Normal state entry point. This is called on:
+//
+// - Startup, In this case the function never returns.
+// - The command-line window is opened(`q:`). Returns when `cmdwin_result` != 0.
+// - The :visual command is called from :global in ex mode, `:global/PAT/visual`
+// for example. Returns when re-entering ex mode(because ex mode recursion is
+// not allowed)
+//
+// This used to be called main_loop on main.c
+void normal_enter(bool cmdwin, bool noexmode)
+{
+ NormalState state;
+ normal_state_init(&state);
+ state.cmdwin = cmdwin;
+ state.noexmode = noexmode;
+ state.toplevel = !cmdwin && !noexmode;
+ state_enter(&state.state);
+}
+
+static void normal_prepare(NormalState *s)
+{
+ memset(&s->ca, 0, sizeof(s->ca)); // also resets ca.retval
+ s->ca.oap = &s->oa;
+
+ // 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".
+ s->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).
+ int c = finish_op;
+ finish_op = (s->oa.op_type != OP_NOP);
+ if (finish_op != c) {
+ ui_cursor_shape(); // may show different cursor shape
+ }
- memset(&ca, 0, sizeof(ca)); /* also resets ca.retval */
- ca.oap = oap;
+ // When not finishing an operator and no register name typed, reset the count.
+ if (!finish_op && !s->oa.regname) {
+ s->ca.opcount = 0;
+ s->set_prevcount = true;
+ }
- /* 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;
+ // Restore counts from before receiving K_EVENT. This means after
+ // typing "3", handling K_EVENT and then typing "2" we get "32", not
+ // "3 * 2".
+ if (s->oa.prev_opcount > 0 || s->oa.prev_count0 > 0) {
+ s->ca.opcount = s->oa.prev_opcount;
+ s->ca.count0 = s->oa.prev_count0;
+ s->oa.prev_opcount = 0;
+ s->oa.prev_count0 = 0;
+ }
+ s->mapped_len = typebuf_maplen();
+ State = NORMAL_BUSY;
- /*
- * 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).
- */
- c = finish_op;
- finish_op = (oap->op_type != OP_NOP);
- if (finish_op != c) {
- ui_cursor_shape(); /* may show different cursor shape */
+ // 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 (s->toplevel && readbuf1_empty()) {
+ set_vcount_ca(&s->ca, &s->set_prevcount);
}
+}
- /* When not finishing an operator and no register name typed, reset the
- * count. */
- if (!finish_op && !oap->regname) {
- ca.opcount = 0;
- set_prevcount = true;
+static bool normal_handle_special_visual_command(NormalState *s)
+{
+ // when 'keymodel' contains "stopsel" may stop Select/Visual mode
+ if (km_stopsel
+ && (nv_cmds[s->idx].cmd_flags & NV_STS)
+ && !(mod_mask & MOD_MASK_SHIFT)) {
+ end_visual_mode();
+ redraw_curbuf_later(INVERTED);
}
- /* 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;
+ // Keys that work different when 'keymodel' contains "startsel"
+ if (km_startsel) {
+ if (nv_cmds[s->idx].cmd_flags & NV_SS) {
+ unshift_special(&s->ca);
+ s->idx = find_command(s->ca.cmdchar);
+ if (s->idx < 0) {
+ // Just in case
+ clearopbeep(&s->oa);
+ return true;
+ }
+ } else if ((nv_cmds[s->idx].cmd_flags & NV_SSS)
+ && (mod_mask & MOD_MASK_SHIFT)) {
+ mod_mask &= ~MOD_MASK_SHIFT;
+ }
}
+ return false;
+}
- mapped_len = typebuf_maplen();
+static bool normal_need_aditional_char(NormalState *s)
+{
+ int flags = nv_cmds[s->idx].cmd_flags;
+ bool pending_op = s->oa.op_type != OP_NOP;
+ int cmdchar = s->ca.cmdchar;
+ return
+ // without NV_NCH we never need to check for an additional char
+ flags & NV_NCH && (
+ // NV_NCH_NOP is set and no operator is pending, get a second char
+ ((flags & NV_NCH_NOP) == NV_NCH_NOP && !pending_op)
+ // NV_NCH_ALW is set, always get a second char
+ || (flags & NV_NCH_ALW) == NV_NCH_ALW
+ // 'q' without a pending operator, recording or executing a register,
+ // needs to be followed by a second char, examples:
+ // - qc => record using register c
+ // - q: => open command-line window
+ || (cmdchar == 'q' && !pending_op && !Recording && !Exec_reg)
+ // 'a' or 'i' after an operator is a text object, examples:
+ // - ciw => change inside word
+ // - da( => delete parenthesis and everything inside.
+ // Also, don't do anything when these keys are received in visual mode
+ // so just get another char.
+ //
+ // TODO(tarruda): Visual state needs to be refactored into a
+ // separate state that "inherits" from normal state.
+ || ((cmdchar == 'a' || cmdchar == 'i') && (pending_op || VIsual_active)));
+}
+
+static bool normal_need_redraw_mode_message(NormalState *s)
+{
+ return (
+ (
+ // 'showmode' is set and messages can be printed
+ p_smd && msg_silent == 0
+ // must restart insert mode(ctrl+o or ctrl+l) or we just entered visual
+ // mode
+ && (restart_edit != 0 || (VIsual_active
+ && s->old_pos.lnum == curwin->w_cursor.lnum
+ && s->old_pos.col == curwin->w_cursor.col))
+ // command-line must be cleared or redrawn
+ && (clear_cmdline || redraw_cmdline)
+ // some message was printed or scrolled
+ && (msg_didout || (msg_didany && msg_scroll))
+ // it is fine to remove the current message
+ && !msg_nowait
+ // the command was the result of direct user input and not a mapping
+ && KeyTyped
+ )
+ ||
+ // must restart insert mode, not in visual mode and error message is
+ // being shown
+ (restart_edit != 0 && !VIsual_active && (msg_scroll && emsg_on_display))
+ )
+ // no register was used
+ && s->oa.regname == 0
+ && !(s->ca.retval & CA_COMMAND_BUSY)
+ && stuff_empty()
+ && typebuf_typed()
+ && emsg_silent == 0
+ && !did_wait_return
+ && s->oa.op_type == OP_NOP;
+}
+
+static void normal_redraw_mode_message(NormalState *s)
+{
+ 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
+ update_screen(0);
+ // now reset it, otherwise it's put in the history again
+ keep_msg = kmsg;
+ msg_attr(kmsg, keep_msg_attr);
+ xfree(kmsg);
+ }
+ setcursor();
+ ui_flush();
+ if (msg_scroll || emsg_on_display) {
+ os_delay(1000L, true); // wait at least one second
+ }
+ os_delay(3000L, false); // wait up to three seconds
+ State = save_State;
+
+ msg_scroll = false;
+ emsg_on_display = false;
+}
+
+// TODO(tarruda): Split into a "normal pending" state that can handle K_EVENT
+static void normal_get_additional_char(NormalState *s)
+{
+ int *cp;
+ bool repl = false; // get character for replace mode
+ bool lit = false; // get extra character literally
+ bool langmap_active = false; // using :lmap mappings
+ int lang; // getting a text character
+
+ ++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 (s->ca.cmdchar == 'g') {
+ // For 'g' get the next character now, so that we can check for
+ // "gr", "g'" and "g`".
+ s->ca.nchar = plain_vgetc();
+ LANGMAP_ADJUST(s->ca.nchar, true);
+ s->need_flushbuf |= add_to_showcmd(s->ca.nchar);
+ if (s->ca.nchar == 'r' || s->ca.nchar == '\'' || s->ca.nchar == '`'
+ || s->ca.nchar == Ctrl_BSL) {
+ cp = &s->ca.extra_char; // need to get a third character
+ if (s->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 (s->ca.cmdchar == 'r') {
+ // get it in replace mode
+ repl = true;
+ }
+ cp = &s->ca.nchar;
+ }
+ lang = (repl || (nv_cmds[s->idx].cmd_flags & NV_LANG));
- State = NORMAL_BUSY;
+ // Get a second or third character.
+ if (cp != NULL) {
+ if (repl) {
+ State = REPLACE; // pretend Replace mode
+ ui_cursor_shape(); // show different cursor shape
+ }
+ 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;
+ }
- /* 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);
+ *cp = plain_vgetc();
- /*
- * Get the command character from the user.
- */
- input_enable_events();
- c = safe_vgetc();
- input_disable_events();
+ if (langmap_active) {
+ // Undo the decrement done above
+ ++no_mapping;
+ ++allow_keys;
+ State = NORMAL_BUSY;
+ }
+ State = NORMAL_BUSY;
+ s->need_flushbuf |= add_to_showcmd(*cp);
+
+ if (!lit) {
+ // Typing CTRL-K gets a digraph.
+ if (*cp == Ctrl_K && ((nv_cmds[s->idx].cmd_flags & NV_LANG)
+ || cp == &s->ca.extra_char)
+ && vim_strchr(p_cpo, CPO_DIGRAPH) == NULL) {
+ s->c = get_digraph(false);
+ if (s->c > 0) {
+ *cp = s->c;
+ // Guessing how to update showcmd here...
+ del_from_showcmd(3);
+ s->need_flushbuf |= add_to_showcmd(*cp);
+ }
+ }
- if (c == K_EVENT) {
- queue_process_events(loop.events);
- return;
- }
+ // adjust chars > 127, except after "tTfFr" commands
+ LANGMAP_ADJUST(*cp, !lang);
+ // adjust Hebrew mapped char
+ if (p_hkmap && lang && KeyTyped) {
+ *cp = hkmap(*cp);
+ }
+ // adjust Farsi mapped char
+ if (p_fkmap && lang && KeyTyped) {
+ *cp = fkmap(*cp);
+ }
+ }
- LANGMAP_ADJUST(c, true);
+ // When the next character is CTRL-\ a following CTRL-N means the
+ // command is aborted and we go to Normal mode.
+ if (cp == &s->ca.extra_char
+ && s->ca.nchar == Ctrl_BSL
+ && (s->ca.extra_char == Ctrl_N || s->ca.extra_char == Ctrl_G)) {
+ s->ca.cmdchar = Ctrl_BSL;
+ s->ca.nchar = s->ca.extra_char;
+ s->idx = find_command(s->ca.cmdchar);
+ } else if ((s->ca.nchar == 'n' || s->ca.nchar == 'N')
+ && s->ca.cmdchar == 'g') {
+ s->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 ((s->c = vpeekc()) <= 0 && towait > 0L) {
+ do_sleep(towait > 50L ? 50L : towait);
+ towait -= 50L;
+ }
+ if (s->c > 0) {
+ s->c = plain_vgetc();
+ if (s->c != Ctrl_N && s->c != Ctrl_G) {
+ vungetc(s->c);
+ } else {
+ s->ca.cmdchar = Ctrl_BSL;
+ s->ca.nchar = s->c;
+ s->idx = find_command(s->ca.cmdchar);
+ assert(s->idx >= 0);
+ }
+ }
+ }
- /*
- * 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();
+ // 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 && (s->c = vpeekc()) > 0
+ && (s->c >= 0x100 || MB_BYTE2LEN(vpeekc()) > 1)) {
+ s->c = plain_vgetc();
+ if (!utf_iscomposing(s->c)) {
+ vungetc(s->c); /* it wasn't, put it back */
+ break;
+ } else if (s->ca.ncharC1 == 0) {
+ s->ca.ncharC1 = s->c;
+ } else {
+ s->ca.ncharC2 = s->c;
+ }
+ }
+ no_mapping++;
+ }
+ --no_mapping;
+ --allow_keys;
+}
- if (c == NUL)
- c = K_ZERO;
+static void normal_invert_horizontal(NormalState *s)
+{
+ switch (s->ca.cmdchar) {
+ case 'l': s->ca.cmdchar = 'h'; break;
+ case K_RIGHT: s->ca.cmdchar = K_LEFT; break;
+ case K_S_RIGHT: s->ca.cmdchar = K_S_LEFT; break;
+ case K_C_RIGHT: s->ca.cmdchar = K_C_LEFT; break;
+ case 'h': s->ca.cmdchar = 'l'; break;
+ case K_LEFT: s->ca.cmdchar = K_RIGHT; break;
+ case K_S_LEFT: s->ca.cmdchar = K_S_RIGHT; break;
+ case K_C_LEFT: s->ca.cmdchar = K_C_RIGHT; break;
+ case '>': s->ca.cmdchar = '<'; break;
+ case '<': s->ca.cmdchar = '>'; break;
+ }
+ s->idx = find_command(s->ca.cmdchar);
+}
- /*
- * 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(c);
- 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 */
+static bool normal_get_command_count(NormalState *s)
+{
+ if (VIsual_active && VIsual_select) {
+ return false;
}
+ // 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 ((s->c >= '1' && s->c <= '9') || (s->ca.count0 != 0
+ && (s->c == K_DEL || s->c == K_KDEL || s->c == '0'))) {
+ if (s->c == K_DEL || s->c == K_KDEL) {
+ s->ca.count0 /= 10;
+ del_from_showcmd(4); // delete the digit and ~@%
+ } else {
+ s->ca.count0 = s->ca.count0 * 10 + (s->c - '0');
+ }
- need_flushbuf = add_to_showcmd(c);
+ if (s->ca.count0 < 0) {
+ // got too large!
+ s->ca.count0 = 999999999L;
+ }
-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;
- del_from_showcmd(4); /* delete the digit and ~@% */
- } else
- ca.count0 = ca.count0 * 10 + (c - '0');
- if (ca.count0 < 0) /* got too large! */
- ca.count0 = 999999999L;
- /* 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);
- 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;
- }
- need_flushbuf |= add_to_showcmd(c);
+ // 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 (s->toplevel && readbuf1_empty()) {
+ set_vcount_ca(&s->ca, &s->set_prevcount);
}
- /*
- * 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;
+ if (s->ctrl_w) {
++no_mapping;
- ++allow_keys; /* no mapping for nchar, but keys */
- c = plain_vgetc(); /* get next character */
- LANGMAP_ADJUST(c, true);
+ ++allow_keys; // no mapping for nchar, but keys
+ }
+
+ ++no_zero_mapping; // don't map zero here
+ s->c = plain_vgetc();
+ LANGMAP_ADJUST(s->c, true);
+ --no_zero_mapping;
+ if (s->ctrl_w) {
--no_mapping;
--allow_keys;
- need_flushbuf |= add_to_showcmd(c);
- goto getcount; /* jump back */
}
+ s->need_flushbuf |= add_to_showcmd(s->c);
}
- 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 we got CTRL-W there may be a/another count
+ if (s->c == Ctrl_W && !s->ctrl_w && s->oa.op_type == OP_NOP) {
+ s->ctrl_w = true;
+ s->ca.opcount = s->ca.count0; // remember first count
+ s->ca.count0 = 0;
+ ++no_mapping;
+ ++allow_keys; // no mapping for nchar, but keys
+ s->c = plain_vgetc(); // get next character
+ LANGMAP_ADJUST(s->c, true);
+ --no_mapping;
+ --allow_keys;
+ s->need_flushbuf |= add_to_showcmd(s->c);
+ return true;
}
- /*
- * 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);
-
- /*
- * 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);
+ return false;
+}
- /*
- * 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);
+static void normal_finish_command(NormalState *s)
+{
+ if (s->command_finished) {
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 we didn't start or finish an operator, reset oap->regname, unless we
+ // need it later.
+ if (!finish_op
+ && !s->oa.op_type
+ && (s->idx < 0 || !(nv_cmds[s->idx].cmd_flags & NV_KEEPREG))) {
+ clearop(&s->oa);
+ set_reg_var(get_default_register_name());
}
- 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);
- }
+ // Get the length of mapped chars again after typing a count, second
+ // character or "z333<cr>".
+ if (s->old_mapped_len > 0) {
+ s->old_mapped_len = typebuf_maplen();
+ }
- /* 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;
- }
+ // If an operation is pending, handle it...
+ do_pending_operator(&s->ca, s->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 (normal_need_redraw_mode_message(s)) {
+ normal_redraw_mode_message(s);
+ }
+
+ // Finish up after executing a Normal mode command.
+normal_end:
+
+ msg_nowait = false;
+
+ // Reset finish_op, in case it was set
+ s->c = finish_op;
+ finish_op = false;
+ // Redraw the cursor with another shape, if we were in Operator-pending
+ // mode or did a replace command.
+ if (s->c || s->ca.cmdchar == 'r') {
+ ui_cursor_shape(); // may show different cursor shape
+ }
+
+ if (s->oa.op_type == OP_NOP && s->oa.regname == 0
+ && s->ca.cmdchar != K_EVENT) {
+ clear_showcmd();
+ }
+
+ checkpcmark(); // check if we moved since setting pcmark
+ xfree(s->ca.searchbuf);
+
+ if (has_mbyte) {
+ mb_adjust_cursor();
+ }
+
+ if (curwin->w_p_scb && s->toplevel) {
+ validate_cursor(); // may need to update w_leftcol
+ do_check_scrollbind(true);
+ }
+
+ if (curwin->w_p_crb && s->toplevel) {
+ validate_cursor(); // may need to update w_leftcol
+ do_check_cursorbind();
+ }
+
+ // 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 (s->oa.op_type == OP_NOP
+ && ((restart_edit != 0 && !VIsual_active && s->old_mapped_len == 0)
+ || restart_VIsual_select == 1)
+ && !(s->ca.retval & CA_COMMAND_BUSY)
+ && stuff_empty()
+ && s->oa.regname == 0) {
+ if (restart_VIsual_select == 1) {
+ VIsual_select = true;
+ showmode();
+ restart_VIsual_select = 0;
+ }
+ if (restart_edit != 0 && !VIsual_active && s->old_mapped_len == 0) {
+ (void)edit(restart_edit, false, 1L);
}
}
- 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);
+ if (restart_VIsual_select == 2) {
+ restart_VIsual_select = 1;
}
- /*
- * 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
- && !Recording
- && !Exec_reg)
- || ((ca.cmdchar == 'a' || ca.cmdchar == 'i')
- && (oap->op_type != OP_NOP
- || VIsual_active
- )))) {
- int *cp;
- bool repl = false; /* get character for replace mode */
- bool lit = false; /* get extra character literally */
- bool langmap_active = false; /* using :lmap mappings */
- int lang; /* getting a text character */
+ // Save count before an operator for next time
+ opcount = s->ca.opcount;
+}
- ++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);
- need_flushbuf |= add_to_showcmd(ca.nchar);
- 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 */
+static int normal_execute(VimState *state, int key)
+{
+ NormalState *s = (NormalState *)state;
+ s->command_finished = false;
+ s->ctrl_w = false; /* got CTRL-W command */
+ s->old_col = curwin->w_curswant;
+ s->c = key;
+
+ LANGMAP_ADJUST(s->c, true);
+
+ // 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) {
+ s->old_mapped_len = 0;
+ } else if (s->old_mapped_len || (VIsual_active && s->mapped_len == 0
+ && typebuf_maplen() > 0)) {
+ s->old_mapped_len = typebuf_maplen();
+ }
+
+ if (s->c == NUL) {
+ s->c = K_ZERO;
+ }
+
+ // In Select mode, typed text replaces the selection.
+ if (VIsual_active && VIsual_select && (vim_isprintc(s->c)
+ || s->c == NL || s->c == CAR || s->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(s->c);
+ if (restart_edit != 0) {
+ s->c = 'd';
} else {
- if (ca.cmdchar == 'r') /* get it in replace mode */
- repl = true;
- cp = &ca.nchar;
+ s->c = 'c';
}
- lang = (repl || (nv_cmds[idx].cmd_flags & NV_LANG));
+ msg_nowait = true; // don't delay going to insert mode
+ s->old_mapped_len = 0; // do go to Insert mode
+ }
+
+ s->need_flushbuf = add_to_showcmd(s->c);
+
+ while (normal_get_command_count(s)) continue;
+
+ if (s->c == K_EVENT) {
+ // Save the count values so that ca.opcount and ca.count0 are exactly
+ // the same when coming back here after handling K_EVENT.
+ s->oa.prev_opcount = s->ca.opcount;
+ s->oa.prev_count0 = s->ca.count0;
+ } else if (s->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 (s->ca.count0) {
+ s->ca.count0 *= s->ca.opcount;
+ } else {
+ s->ca.count0 = s->ca.opcount;
+ }
+ }
- /*
- * Get a second or third character.
- */
- if (cp != NULL) {
- if (repl) {
- State = REPLACE; /* pretend Replace mode */
- ui_cursor_shape(); /* show different cursor shape */
- }
- 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;
- }
+ // 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.
+ s->ca.opcount = s->ca.count0;
+ s->ca.count1 = (s->ca.count0 == 0 ? 1 : s->ca.count0);
- *cp = plain_vgetc();
+ // Only set v:count when called from main() and not a stuffed command.
+ // Do set it for redo.
+ if (s->toplevel && readbuf1_empty()) {
+ set_vcount(s->ca.count0, s->ca.count1, s->set_prevcount);
+ }
- if (langmap_active) {
- /* Undo the decrement done above */
- ++no_mapping;
- ++allow_keys;
- State = NORMAL_BUSY;
- }
- State = NORMAL_BUSY;
- need_flushbuf |= add_to_showcmd(*cp);
-
- if (!lit) {
- /* 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;
- /* Guessing how to update showcmd here... */
- del_from_showcmd(3);
- need_flushbuf |= add_to_showcmd(*cp);
- }
- }
+ // Find the command character in the table of commands.
+ // For CTRL-W we already got nchar when looking for a count.
+ if (s->ctrl_w) {
+ s->ca.nchar = s->c;
+ s->ca.cmdchar = Ctrl_W;
+ } else {
+ s->ca.cmdchar = s->c;
+ }
- /* adjust chars > 127, except after "tTfFr" commands */
- LANGMAP_ADJUST(*cp, !lang);
- /* adjust Hebrew mapped char */
- if (p_hkmap && lang && KeyTyped)
- *cp = hkmap(*cp);
- /* adjust Farsi mapped char */
- if (p_fkmap && lang && KeyTyped)
- *cp = fkmap(*cp);
- }
+ s->idx = find_command(s->ca.cmdchar);
- /*
- * 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);
- 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);
- assert(idx >= 0);
- }
- }
- }
+ if (s->idx < 0) {
+ // Not a known command: beep.
+ clearopbeep(&s->oa);
+ s->command_finished = true;
+ goto finish;
+ }
- /* 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;
+ if (text_locked() && (nv_cmds[s->idx].cmd_flags & NV_NCW)) {
+ // This command is not allowed while editing a cmdline: beep.
+ clearopbeep(&s->oa);
+ text_locked_msg();
+ s->command_finished = true;
+ goto finish;
}
- /*
- * 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)
+ if ((nv_cmds[s->idx].cmd_flags & NV_NCW) && curbuf_locked()) {
+ s->command_finished = true;
+ goto finish;
+ }
+
+ // In Visual/Select mode, a few keys are handled in a special way.
+ if (VIsual_active && normal_handle_special_visual_command(s)) {
+ s->command_finished = true;
+ goto finish;
+ }
+
+ if (curwin->w_p_rl && KeyTyped && !KeyStuffed
+ && (nv_cmds[s->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".
+ normal_invert_horizontal(s);
+ }
+
+ // Get an additional character if we need one.
+ if (normal_need_aditional_char(s)) {
+ normal_get_additional_char(s);
+ }
+
+ // 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 (s->need_flushbuf) {
ui_flush();
- if (ca.cmdchar != K_IGNORE)
+ }
+ if (s->ca.cmdchar != K_IGNORE && s->ca.cmdchar != K_EVENT) {
did_cursorhold = false;
+ }
State = NORMAL;
- if (ca.nchar == ESC) {
- clearop(oap);
- if (restart_edit == 0 && goto_im())
+ if (s->ca.nchar == ESC) {
+ clearop(&s->oa);
+ if (restart_edit == 0 && goto_im()) {
restart_edit = 'a';
- goto normal_end;
+ }
+ s->command_finished = true;
+ goto finish;
}
- if (ca.cmdchar != K_IGNORE) {
- msg_didout = false; /* don't scroll screen up for normal command */
+ if (s->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 */
+ s->old_pos = curwin->w_cursor; // remember where cursor was
- /* When 'keymodel' contains "startsel" some keys start Select/Visual
- * mode. */
+ // When 'keymodel' contains "startsel" some keys start Select/Visual
+ // mode.
if (!VIsual_active && km_startsel) {
- if (nv_cmds[idx].cmd_flags & NV_SS) {
+ if (nv_cmds[s->idx].cmd_flags & NV_SS) {
start_selection();
- unshift_special(&ca);
- idx = find_command(ca.cmdchar);
- } else if ((nv_cmds[idx].cmd_flags & NV_SSS)
+ unshift_special(&s->ca);
+ s->idx = find_command(s->ca.cmdchar);
+ } else if ((nv_cmds[s->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);
+ // Execute the command!
+ // Call the command function found in the commands table.
+ s->ca.arg = nv_cmds[s->idx].cmd_arg;
+ (nv_cmds[s->idx].cmd_func)(&s->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);
- set_reg_var(get_default_register_name());
+finish:
+ normal_finish_command(s);
+ return 1;
+}
+
+static void normal_check_stuff_buffer(NormalState *s)
+{
+ if (stuff_empty()) {
+ did_check_timestamps = false;
+
+ if (need_check_timestamps) {
+ check_timestamps(false);
+ }
+
+ if (need_wait_return) {
+ // if wait_return still needed call it now
+ wait_return(false);
+ }
+
+ if (need_start_insertmode && goto_im() && !VIsual_active) {
+ need_start_insertmode = false;
+ stuffReadbuff((uint8_t *)"i"); // start insert mode next
+ // skip the fileinfo message now, because it would be shown
+ // after insert mode finishes!
+ need_fileinfo = false;
+ }
+ }
+}
+
+static void normal_check_interrupt(NormalState *s)
+{
+ // Reset "got_int" now that we got back to the main loop. Except when
+ // inside a ":g/pat/cmd" command, then the "got_int" needs to abort
+ // the ":g" command.
+ // For ":g/pat/vi" we reset "got_int" when used once. When used
+ // a second time we go back to Ex mode and abort the ":g" command.
+ if (got_int) {
+ if (s->noexmode && global_busy && !exmode_active
+ && s->previous_got_int) {
+ // Typed two CTRL-C in a row: go back to ex mode as if "Q" was
+ // used and keep "got_int" set, so that it aborts ":g".
+ exmode_active = EXMODE_NORMAL;
+ State = NORMAL;
+ } else if (!global_busy || !exmode_active) {
+ if (!quit_more) {
+ // flush all buffers
+ (void)vgetc();
+ }
+ got_int = false;
+ }
+ s->previous_got_int = true;
+ } else {
+ s->previous_got_int = false;
}
+}
- /* 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();
+static void normal_check_cursor_moved(NormalState *s)
+{
+ // Trigger CursorMoved if the cursor moved.
+ if (!finish_op && (has_cursormoved() || curwin->w_p_cole > 0)
+ && !equalpos(last_cursormoved, curwin->w_cursor)) {
+ if (has_cursormoved()) {
+ apply_autocmds(EVENT_CURSORMOVED, NULL, NULL, false, curbuf);
+ }
- /*
- * If an operation is pending, handle it...
- */
- do_pending_operator(&ca, old_col, false);
+ if (curwin->w_p_cole > 0) {
+ s->conceal_old_cursor_line = last_cursormoved.lnum;
+ s->conceal_new_cursor_line = curwin->w_cursor.lnum;
+ s->conceal_update_lines = true;
+ }
- /*
- * 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
- && !did_wait_return
- && oap->op_type == OP_NOP) {
- int save_State = State;
+ last_cursormoved = curwin->w_cursor;
+ }
+}
- /* Draw the cursor with the right shape here */
- if (restart_edit != 0)
- State = INSERT;
+static void normal_check_text_changed(NormalState *s)
+{
+ // Trigger TextChanged if b_changedtick differs.
+ if (!finish_op && has_textchanged()
+ && last_changedtick != curbuf->b_changedtick) {
+ if (last_changedtick_buf == curbuf) {
+ apply_autocmds(EVENT_TEXTCHANGED, NULL, NULL, false, curbuf);
+ }
- /* 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;
+ last_changedtick_buf = curbuf;
+ last_changedtick = curbuf->b_changedtick;
+ }
+}
+
+static void normal_check_folds(NormalState *s)
+{
+ // Include a closed fold completely in the Visual area.
+ foldAdjustVisual();
+
+ // When 'foldclose' is set, apply 'foldlevel' to folds that don't
+ // contain the cursor.
+ // When 'foldopen' is "all", open the fold(s) under the cursor.
+ // This may mark the window for redrawing.
+ if (hasAnyFolding(curwin) && !char_avail()) {
+ foldCheckClose();
- kmsg = keep_msg;
- keep_msg = NULL;
- /* showmode() will clear keep_msg, but we want to use it anyway */
- update_screen(0);
- /* now reset it, otherwise it's put in the history again */
- keep_msg = kmsg;
- msg_attr(kmsg, keep_msg_attr);
- xfree(kmsg);
+ if (fdo_flags & FDO_ALL) {
+ foldOpenCursor();
}
- setcursor();
- ui_flush();
- if (msg_scroll || emsg_on_display)
- os_delay(1000L, true); /* wait at least one second */
- os_delay(3000L, false); /* wait up to three seconds */
- State = save_State;
+ }
+}
- msg_scroll = false;
- emsg_on_display = false;
+static void normal_redraw(NormalState *s)
+{
+ // Before redrawing, make sure w_topline is correct, and w_leftcol
+ // if lines don't wrap, and w_skipcol if lines wrap.
+ update_topline();
+ validate_cursor();
+
+ if (VIsual_active) {
+ update_curbuf(INVERTED); // update inverted part
+ } else if (must_redraw) {
+ update_screen(0);
+ } else if (redraw_cmdline || clear_cmdline) {
+ showmode();
}
- /*
- * Finish up after executing a Normal mode command.
- */
-normal_end:
+ redraw_statuslines();
- msg_nowait = false;
+ if (need_maketitle) {
+ maketitle();
+ }
- /* Reset finish_op, in case it was set */
- c = finish_op;
- finish_op = false;
- /* 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 */
+ // display message after redraw
+ if (keep_msg != NULL) {
+ // msg_attr_keep() will set keep_msg to NULL, must free the string here.
+ // Don't reset keep_msg, msg_attr_keep() uses it to check for duplicates.
+ char *p = (char *)keep_msg;
+ msg_attr((uint8_t *)p, keep_msg_attr);
+ xfree(p);
}
- if (oap->op_type == OP_NOP && oap->regname == 0
- && ca.cmdchar != K_CURSORHOLD
- )
- clear_showcmd();
+ if (need_fileinfo) { // show file info after redraw
+ fileinfo(false, true, false);
+ need_fileinfo = false;
+ }
- checkpcmark(); /* check if we moved since setting pcmark */
- xfree(ca.searchbuf);
+ emsg_on_display = false; // can delete error message now
+ did_emsg = false;
+ msg_didany = false; // reset lines_left in msg_start()
+ may_clear_sb_text(); // clear scroll-back text on next msg
+ showruler(false);
- if (has_mbyte)
- mb_adjust_cursor();
+ if (s->conceal_update_lines
+ && (s->conceal_old_cursor_line !=
+ s->conceal_new_cursor_line
+ || conceal_cursor_line(curwin)
+ || need_cursor_line_redraw)) {
+ if (s->conceal_old_cursor_line !=
+ s->conceal_new_cursor_line
+ && s->conceal_old_cursor_line <=
+ curbuf->b_ml.ml_line_count) {
+ update_single_line(curwin, s->conceal_old_cursor_line);
+ }
- if (curwin->w_p_scb && toplevel) {
- validate_cursor(); /* may need to update w_leftcol */
- do_check_scrollbind(true);
+ update_single_line(curwin, s->conceal_new_cursor_line);
+ curwin->w_valid &= ~VALID_CROW;
}
- if (curwin->w_p_crb && toplevel) {
- validate_cursor(); /* may need to update w_leftcol */
- do_check_cursorbind();
+ setcursor();
+}
+
+// Function executed before each iteration of normal mode.
+// Return:
+// 1 if the iteration should continue normally
+// -1 if the iteration should be skipped
+// 0 if the main loop must exit
+static int normal_check(VimState *state)
+{
+ NormalState *s = (NormalState *)state;
+ normal_check_stuff_buffer(s);
+ normal_check_interrupt(s);
+
+ if (!exmode_active) {
+ msg_scroll = false;
}
+ quit_more = false;
+
+ // If skip redraw is set (for ":" in wait_return()), don't redraw now.
+ // If there is nothing in the stuff_buffer or do_redraw is TRUE,
+ // update cursor and redraw.
+ if (skip_redraw || exmode_active) {
+ skip_redraw = false;
+ } else if (do_redraw || stuff_empty()) {
+ normal_check_cursor_moved(s);
+ normal_check_text_changed(s);
+
+ // Scroll-binding for diff mode may have been postponed until
+ // here. Avoids doing it for every change.
+ if (diff_need_scrollbind) {
+ check_scrollbind((linenr_T)0, 0L);
+ diff_need_scrollbind = false;
+ }
- /*
- * 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;
- showmode();
- restart_VIsual_select = 0;
+ normal_check_folds(s);
+ normal_redraw(s);
+ do_redraw = false;
+
+ // Now that we have drawn the first screen all the startup stuff
+ // has been done, close any file for startup messages.
+ if (time_fd != NULL) {
+ TIME_MSG("first screen update");
+ TIME_MSG("--- NVIM STARTED ---");
+ fclose(time_fd);
+ time_fd = NULL;
}
- 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;
+ // May perform garbage collection when waiting for a character, but
+ // only at the very toplevel. Otherwise we may be using a List or
+ // Dict internally somewhere.
+ // "may_garbage_collect" is reset in vgetc() which is invoked through
+ // do_exmode() and normal_cmd().
+ may_garbage_collect = s->toplevel;
+
+ // Update w_curswant if w_set_curswant has been set.
+ // Postponed until here to avoid computing w_virtcol too often.
+ update_curswant();
+
+ if (exmode_active) {
+ if (s->noexmode) {
+ return 0;
+ }
+ do_exmode(exmode_active == EXMODE_VIM);
+ return -1;
+ }
- /* Save count before an operator for next time. */
- opcount = ca.opcount;
+ if (s->cmdwin && cmdwin_result != 0) {
+ // command-line window and cmdwin_result is set
+ return 0;
+ }
+
+ normal_prepare(s);
+ return 1;
}
/*
@@ -2937,7 +3266,7 @@ bool add_to_showcmd(int c)
K_RIGHTMOUSE, K_RIGHTDRAG, K_RIGHTRELEASE,
K_MOUSEDOWN, K_MOUSEUP, K_MOUSELEFT, K_MOUSERIGHT,
K_X1MOUSE, K_X1DRAG, K_X1RELEASE, K_X2MOUSE, K_X2DRAG, K_X2RELEASE,
- K_CURSORHOLD,
+ K_EVENT,
0
};
@@ -7357,16 +7686,11 @@ static void nv_open(cmdarg_T *cap)
n_opencmd(cap);
}
-/*
- * Trigger CursorHold event.
- * When waiting for a character for 'updatetime' K_CURSORHOLD is put in the
- * input buffer. "did_cursorhold" is set to avoid retriggering.
- */
-static void nv_cursorhold(cmdarg_T *cap)
+// Handle an arbitrary event in normal mode
+static void nv_event(cmdarg_T *cap)
{
- apply_autocmds(EVENT_CURSORHOLD, NULL, NULL, false, curbuf);
- did_cursorhold = true;
- cap->retval |= CA_COMMAND_BUSY; /* don't call edit() now */
+ queue_process_events(loop.events);
+ cap->retval |= CA_COMMAND_BUSY; // don't call edit() now
}
/*
@@ -7376,3 +7700,14 @@ static int mouse_model_popup(void)
{
return p_mousem[0] == 'p';
}
+
+void normal_cmd(oparg_T *oap, bool toplevel)
+{
+ NormalState s;
+ normal_state_init(&s);
+ s.toplevel = toplevel;
+ s.oa = *oap;
+ normal_prepare(&s);
+ (void)normal_execute(&s.state, safe_vgetc());
+ *oap = s.oa;
+}