aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThiago de Arruda <tpadilha84@gmail.com>2015-10-26 11:24:51 -0300
committerThiago de Arruda <tpadilha84@gmail.com>2015-10-26 11:24:51 -0300
commit29d64a901df6eff436b100374b2a240eea1906d9 (patch)
tree613b820fdc3d1d48a68146a4b516d05898798513
parent424b00ea610b90ed4041711f27d71d0e359038a7 (diff)
parent1726c7d999e68b4ed8aee234b7dfa339ed0784b2 (diff)
downloadrneovim-29d64a901df6eff436b100374b2a240eea1906d9.tar.gz
rneovim-29d64a901df6eff436b100374b2a240eea1906d9.tar.bz2
rneovim-29d64a901df6eff436b100374b2a240eea1906d9.zip
Merge PR #3413 'Refactor Neovim to remove the side effects of `K_EVENT`'
-rw-r--r--src/nvim/README.md190
-rw-r--r--src/nvim/edit.c1719
-rw-r--r--src/nvim/ex_docmd.c2
-rw-r--r--src/nvim/ex_getln.c2564
-rw-r--r--src/nvim/getchar.c2
-rw-r--r--src/nvim/keymap.c1
-rw-r--r--src/nvim/keymap.h2
-rw-r--r--src/nvim/main.c219
-rw-r--r--src/nvim/normal.c1443
-rw-r--r--src/nvim/normal.h4
-rw-r--r--src/nvim/os/input.c40
-rw-r--r--src/nvim/state.c62
-rw-r--r--src/nvim/state.h20
-rw-r--r--src/nvim/terminal.c160
-rw-r--r--test/functional/api/vim_spec.lua18
-rw-r--r--test/functional/terminal/tui_spec.lua47
16 files changed, 3574 insertions, 2919 deletions
diff --git a/src/nvim/README.md b/src/nvim/README.md
new file mode 100644
index 0000000000..e4939d94fd
--- /dev/null
+++ b/src/nvim/README.md
@@ -0,0 +1,190 @@
+## Source code overview
+
+Since Neovim has inherited most code from Vim, some information in [its
+README](https://raw.githubusercontent.com/vim/vim/master/src/README.txt) still
+applies.
+
+This document aims to give a high level overview of how Neovim works internally,
+focusing on parts that are different from Vim. Currently this is still a work in
+progress, especially because I have avoided adding too many details about parts
+that are constantly changing. As the code becomes more organized and stable,
+this document will be updated to reflect the changes.
+
+If you are looking for module-specific details, it is best to read the source
+code. Some files are extensively commented at the top(eg: terminal.c,
+screen.c).
+
+### Top-level program loops
+
+First let's understand what a Vim-like program does by analyzing the workflow of
+a typical editing session:
+
+01. Vim dispays the welcome screen
+02. User types: `:`
+03. Vim enters command-line mode
+04. User types: `edit README.txt<CR>`
+05. Vim opens the file and returns to normal mode
+06. User types: `G`
+07. Vim navigates to the end of the file
+09. User types: `5`
+10. Vim enters count-pending mode
+11. User types: `d`
+12. Vim enters operator-pending mode
+13. User types: `w`
+14. Vim deletes 5 words
+15. User types: `g`
+16. Vim enters the "g command mode"
+17. User types: `g`
+18. Vim goes to the beginning of the file
+19. User types: `i`
+20. Vim enters insert mode
+21. User types: `word<ESC>`
+22. Vim inserts "word" at the beginning and returns to normal mode
+
+Note that we have split user actions into sequences of inputs that change the
+state of the editor. While there's no documentation about a "g command
+mode"(step 16), internally it is implemented similarly to "operator-pending
+mode".
+
+From this we can see that Vim has the behavior of a input-driven state
+machine(more specifically, a pushdown automaton since it requires a stack for
+transitioning back from states). Assuming each state has a callback responsible
+for handling keys, this pseudocode(a python-like language) shows a good
+representation of the main program loop:
+
+```py
+def state_enter(state_callback, data):
+ do
+ key = readkey() # read a key from the user
+ while state_callback(data, key) # invoke the callback for the current state
+```
+
+That is, each state is entered by calling `state_enter` and passing a
+state-specific callback and data. Here is a high-level pseudocode for a program
+that implements something like the workflow described above:
+
+```py
+def main()
+ state_enter(normal_state, {}):
+
+def normal_state(data, key):
+ if key == ':':
+ state_enter(command_line_state, {})
+ elif key == 'i':
+ state_enter(insert_state, {})
+ elif key == 'd':
+ state_enter(delete_operator_state, {})
+ elif key == 'g':
+ state_enter(g_command_state, {})
+ elif is_number(key):
+ state_enter(get_operator_count_state, {'count': key})
+ elif key == 'G'
+ jump_to_eof()
+ return true
+
+def command_line_state(data, key):
+ if key == '<cr>':
+ if data['input']:
+ execute_ex_command(data['input'])
+ return false
+ elif key == '<esc>'
+ return false
+
+ if not data['input']:
+ data['input'] = ''
+
+ data['input'] += key
+ return true
+
+def delete_operator_state(data, key):
+ count = data['count'] or 1
+ if key == 'w':
+ delete_word(count)
+ elif key == '$':
+ delete_to_eol(count)
+ return false # return to normal mode
+
+def g_command_state(data, key):
+ if key == 'g':
+ go_top()
+ elif key == 'v':
+ reselect()
+ return false # return to normal mode
+
+def get_operator_count_state(data, key):
+ if is_number(key):
+ data['count'] += key
+ return true
+ unshift_key(key) # return key to the input buffer
+ state_enter(delete_operator_state, data)
+ return false
+
+def insert_state(data, key):
+ if key == '<esc>':
+ return false # exit insert mode
+ self_insert(key)
+ return true
+```
+
+While the actual code is much more complicated, the above gives an idea of how
+Neovim is organized internally. Some states like the `g_command_state` or
+`get_operator_count_state` do not have a dedicated `state_enter` callback, but
+are implicitly embedded into other states(this will change later as we continue
+the refactoring effort). To start reading the actual code, here's the
+recommended order:
+
+1. `state_enter()` function(state.c). This is the actual program loop,
+ note that a `VimState` structure is used, which contains function pointers
+ for the callback and state data.
+2. `main()` function(main.c). After all startup, `normal_enter` is called
+ at the end of function to enter normal mode.
+3. `normal_enter()` function(normal.c) is a small wrapper for setting
+ up the NormalState structure and calling `state_enter`.
+4. `normal_check()` function(normal.c) is called before each iteration of
+ normal mode.
+5. `normal_execute()` function(normal.c) is called when a key is read in normal
+ mode.
+
+The basic structure described for normal mode in 3, 4 and 5 is used for other
+modes managed by the `state_enter` loop:
+
+- command-line mode: `command_line_{enter,check,execute}()`(`ex_getln.c`)
+- insert mode: `insert_{enter,check,execute}()`(`edit.c`)
+- terminal mode: `terminal_{enter,execute}()`(`terminal.c`)
+
+### Async event support
+
+One of the features Neovim added is the support for handling arbitrary
+asynchronous events, which can include:
+
+- msgpack-rpc requests
+- job control callbacks
+- timers(not implemented yet but the support code is already there)
+
+Neovim implements this functionality by entering another event loop while
+waiting for characters, so instead of:
+
+```py
+def state_enter(state_callback, data):
+ do
+ key = readkey() # read a key from the user
+ while state_callback(data, key) # invoke the callback for the current state
+```
+
+Neovim program loop is more like:
+
+```py
+def state_enter(state_callback, data):
+ do
+ event = read_next_event() # read an event from the operating system
+ while state_callback(data, event) # invoke the callback for the current state
+```
+
+where `event` is something the operating system delivers to us, including(but
+not limited to) user input. The `read_next_event()` part is internally
+implemented by libuv, the platform layer used by Neovim.
+
+Since Neovim inherited its code from Vim, the states are not prepared to receive
+"arbitrary events", so we use a special key to represent those(When a state
+receives an "arbitrary event", it normally doesn't do anything other update the
+screen).
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index b2f9601f82..abd16e57ae 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -52,6 +52,7 @@
#include "nvim/search.h"
#include "nvim/spell.h"
#include "nvim/strings.h"
+#include "nvim/state.h"
#include "nvim/syntax.h"
#include "nvim/tag.h"
#include "nvim/ui.h"
@@ -192,6 +193,28 @@ static expand_T compl_xp;
static int compl_opt_refresh_always = FALSE;
+typedef struct insert_state {
+ VimState state;
+ cmdarg_T *ca;
+ int mincol;
+ int cmdchar;
+ int startln;
+ long count;
+ int c;
+ int lastc;
+ int i;
+ bool did_backspace; // previous char was backspace
+ bool line_is_white; // line is empty before insert
+ linenr_T old_topline; // topline before insertion
+ int old_topfill;
+ int inserted_space; // just inserted a space
+ int replaceState;
+ int did_restart_edit; // remember if insert mode was restarted
+ // after a ctrl+o
+ bool nomove;
+ uint8_t *ptr;
+} InsertState;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "edit.c.generated.h"
@@ -228,113 +251,48 @@ static int ins_need_undo; /* call u_save() before inserting a
static int did_add_space = FALSE; /* auto_format() added an extra space
under the cursor */
-static int dont_sync_undo = false; /* CTRL-G U prevents syncing undo for
- the next left/right cursor */
+static int dont_sync_undo = false; // CTRL-G U prevents syncing undo
+ // for the next left/right cursor
-/*
- * edit(): Start inserting text.
- *
- * "cmdchar" can be:
- * 'i' normal insert command
- * 'a' normal append command
- * 'R' replace command
- * 'r' "r<CR>" command: insert one <CR>. Note: count can be > 1, for redo,
- * but still only one <CR> is inserted. The <Esc> is not used for redo.
- * 'g' "gI" command.
- * 'V' "gR" command for Virtual Replace mode.
- * 'v' "gr" command for single character Virtual Replace mode.
- *
- * This function is not called recursively. For CTRL-O commands, it returns
- * and lets the caller handle the Normal-mode command.
- *
- * Return TRUE if a CTRL-O command caused the return (insert mode pending).
- */
-int
-edit (
- int cmdchar,
- int startln, /* if set, insert at start of line */
- long count
-)
-{
- if (curbuf->terminal) {
- if (ex_normal_busy) {
- // don't enter terminal mode from `ex_normal`, which can result in all
- // kinds of havoc(such as terminal mode recursiveness). Instead, set a
- // flag that allow us to force-set the value of `restart_edit` before
- // `ex_normal` returns
- restart_edit = 'i';
- force_restart_edit = true;
- } else {
- terminal_enter();
- }
- return false;
- }
+static linenr_T o_lnum = 0;
- int c = 0;
- char_u *ptr;
- int lastc;
- int mincol;
- static linenr_T o_lnum = 0;
- int i;
- int did_backspace = TRUE; /* previous char was backspace */
- int line_is_white = FALSE; /* line is empty before insert */
- linenr_T old_topline = 0; /* topline before insertion */
- int old_topfill = -1;
- int inserted_space = FALSE; /* just inserted a space */
- int replaceState = REPLACE;
- int nomove = FALSE; /* don't move cursor on return */
-
- /* Remember whether editing was restarted after CTRL-O. */
+static void insert_enter(InsertState *s)
+{
+ s->did_backspace = true;
+ s->old_topfill = -1;
+ s->replaceState = REPLACE;
+ // Remember whether editing was restarted after CTRL-O
did_restart_edit = restart_edit;
-
- /* sleep before redrawing, needed for "CTRL-O :" that results in an
- * error message */
- check_for_delay(TRUE);
-
+ // sleep before redrawing, needed for "CTRL-O :" that results in an
+ // error message
+ check_for_delay(true);
// set Insstart_orig to Insstart
update_Insstart_orig = true;
- // Don't allow inserting in the sandbox.
- if (sandbox != 0) {
- EMSG(_(e_sandbox));
- return FALSE;
- }
- /* Don't allow changes in the buffer while editing the cmdline. The
- * caller of getcmdline() may get confused. */
- if (textlock != 0) {
- EMSG(_(e_secure));
- return FALSE;
- }
-
- /* Don't allow recursive insert mode when busy with completion. */
- if (compl_started || compl_busy || pum_visible()) {
- EMSG(_(e_secure));
- return FALSE;
- }
- ins_compl_clear(); /* clear stuff for CTRL-X mode */
+ ins_compl_clear(); // clear stuff for CTRL-X mode
- /*
- * Trigger InsertEnter autocommands. Do not do this for "r<CR>" or "grx".
- */
- if (cmdchar != 'r' && cmdchar != 'v') {
+ // Trigger InsertEnter autocommands. Do not do this for "r<CR>" or "grx".
+ if (s->cmdchar != 'r' && s->cmdchar != 'v') {
pos_T save_cursor = curwin->w_cursor;
- if (cmdchar == 'R')
- ptr = (char_u *)"r";
- else if (cmdchar == 'V')
- ptr = (char_u *)"v";
- else
- ptr = (char_u *)"i";
- set_vim_var_string(VV_INSERTMODE, ptr, 1);
+ if (s->cmdchar == 'R') {
+ s->ptr = (char_u *)"r";
+ } else if (s->cmdchar == 'V') {
+ s->ptr = (char_u *)"v";
+ } else {
+ s->ptr = (char_u *)"i";
+ }
+
+ set_vim_var_string(VV_INSERTMODE, s->ptr, 1);
set_vim_var_string(VV_CHAR, NULL, -1); /* clear v:char */
- apply_autocmds(EVENT_INSERTENTER, NULL, NULL, FALSE, curbuf);
-
- /* Make sure the cursor didn't move. Do call check_cursor_col() in
- * case the text was modified. Since Insert mode was not started yet
- * a call to check_cursor_col() may move the cursor, especially with
- * the "A" command, thus set State to avoid that. Also check that the
- * line number is still valid (lines may have been deleted).
- * Do not restore if v:char was set to a non-empty string. */
+ apply_autocmds(EVENT_INSERTENTER, NULL, NULL, false, curbuf);
+
+ // Make sure the cursor didn't move. Do call check_cursor_col() in
+ // case the text was modified. Since Insert mode was not started yet
+ // a call to check_cursor_col() may move the cursor, especially with
+ // the "A" command, thus set State to avoid that. Also check that the
+ // line number is still valid (lines may have been deleted).
+ // Do not restore if v:char was set to a non-empty string.
if (!equalpos(curwin->w_cursor, save_cursor)
&& *get_vim_var_str(VV_CHAR) == NUL
&& save_cursor.lnum <= curbuf->b_ml.ml_line_count) {
@@ -347,906 +305,1033 @@ edit (
}
}
- /* Check if the cursor line needs redrawing before changing State. If
- * 'concealcursor' is "n" it needs to be redrawn without concealing. */
+ // Check if the cursor line needs redrawing before changing State. If
+ // 'concealcursor' is "n" it needs to be redrawn without concealing.
conceal_check_cursur_line();
- /*
- * When doing a paste with the middle mouse button, Insstart is set to
- * where the paste started.
- */
- if (where_paste_started.lnum != 0)
+ // When doing a paste with the middle mouse button, Insstart is set to
+ // where the paste started.
+ if (where_paste_started.lnum != 0) {
Insstart = where_paste_started;
- else {
+ } else {
Insstart = curwin->w_cursor;
- if (startln)
+ if (s->startln) {
Insstart.col = 0;
+ }
}
+
Insstart_textlen = (colnr_T)linetabsize(get_cursor_line_ptr());
Insstart_blank_vcol = MAXCOL;
- if (!did_ai)
+
+ if (!did_ai) {
ai_col = 0;
+ }
- if (cmdchar != NUL && restart_edit == 0) {
+ if (s->cmdchar != NUL && restart_edit == 0) {
ResetRedobuff();
- AppendNumberToRedobuff(count);
- if (cmdchar == 'V' || cmdchar == 'v') {
- /* "gR" or "gr" command */
+ AppendNumberToRedobuff(s->count);
+ if (s->cmdchar == 'V' || s->cmdchar == 'v') {
+ // "gR" or "gr" command
AppendCharToRedobuff('g');
- AppendCharToRedobuff((cmdchar == 'v') ? 'r' : 'R');
+ AppendCharToRedobuff((s->cmdchar == 'v') ? 'r' : 'R');
} else {
- AppendCharToRedobuff(cmdchar);
- if (cmdchar == 'g') /* "gI" command */
+ AppendCharToRedobuff(s->cmdchar);
+ if (s->cmdchar == 'g') { // "gI" command
AppendCharToRedobuff('I');
- else if (cmdchar == 'r') /* "r<CR>" command */
- count = 1; /* insert only one <CR> */
+ } else if (s->cmdchar == 'r') { // "r<CR>" command
+ s->count = 1; // insert only one <CR>
+ }
}
}
- if (cmdchar == 'R') {
+ if (s->cmdchar == 'R') {
if (p_fkmap && p_ri) {
beep_flush();
- EMSG(farsi_text_3); /* encoded in Farsi */
+ EMSG(farsi_text_3); // encoded in Farsi
State = INSERT;
- } else
+ } else {
State = REPLACE;
- } else if (cmdchar == 'V' || cmdchar == 'v') {
+ }
+ } else if (s->cmdchar == 'V' || s->cmdchar == 'v') {
State = VREPLACE;
- replaceState = VREPLACE;
+ s->replaceState = VREPLACE;
orig_line_count = curbuf->b_ml.ml_line_count;
vr_lines_changed = 1;
- } else
+ } else {
State = INSERT;
+ }
- stop_insert_mode = FALSE;
+ stop_insert_mode = false;
- /*
- * Need to recompute the cursor position, it might move when the cursor is
- * on a TAB or special character.
- */
- curs_columns(TRUE);
+ // Need to recompute the cursor position, it might move when the cursor is
+ // on a TAB or special character.
+ curs_columns(true);
- /*
- * Enable langmap or IME, indicated by 'iminsert'.
- * Note that IME may enabled/disabled without us noticing here, thus the
- * 'iminsert' value may not reflect what is actually used. It is updated
- * when hitting <Esc>.
- */
- if (curbuf->b_p_iminsert == B_IMODE_LMAP)
+ // Enable langmap or IME, indicated by 'iminsert'.
+ // Note that IME may enabled/disabled without us noticing here, thus the
+ // 'iminsert' value may not reflect what is actually used. It is updated
+ // when hitting <Esc>.
+ if (curbuf->b_p_iminsert == B_IMODE_LMAP) {
State |= LANGMAP;
+ }
setmouse();
clear_showcmd();
- /* there is no reverse replace mode */
+ // there is no reverse replace mode
revins_on = (State == INSERT && p_ri);
- if (revins_on)
+ if (revins_on) {
undisplay_dollar();
+ }
revins_chars = 0;
revins_legal = 0;
revins_scol = -1;
- /*
- * Handle restarting Insert mode.
- * Don't do this for "CTRL-O ." (repeat an insert): we get here with
- * restart_edit non-zero, and something in the stuff buffer.
- */
+ // Handle restarting Insert mode.
+ // Don't do this for "CTRL-O ." (repeat an insert): we get here with
+ // restart_edit non-zero, and something in the stuff buffer.
if (restart_edit != 0 && stuff_empty()) {
- /*
- * After a paste we consider text typed to be part of the insert for
- * the pasted text. You can backspace over the pasted text too.
- */
- if (where_paste_started.lnum)
- arrow_used = FALSE;
- else
- arrow_used = TRUE;
+ // After a paste we consider text typed to be part of the insert for
+ // the pasted text. You can backspace over the pasted text too.
+ if (where_paste_started.lnum) {
+ arrow_used = false;
+ } else {
+ arrow_used = true;
+ }
restart_edit = 0;
- /*
- * If the cursor was after the end-of-line before the CTRL-O and it is
- * now at the end-of-line, put it after the end-of-line (this is not
- * correct in very rare cases).
- * Also do this if curswant is greater than the current virtual
- * column. Eg after "^O$" or "^O80|".
- */
+ // If the cursor was after the end-of-line before the CTRL-O and it is
+ // now at the end-of-line, put it after the end-of-line (this is not
+ // correct in very rare cases).
+ // Also do this if curswant is greater than the current virtual
+ // column. Eg after "^O$" or "^O80|".
validate_virtcol();
update_curswant();
if (((ins_at_eol && curwin->w_cursor.lnum == o_lnum)
|| curwin->w_curswant > curwin->w_virtcol)
- && *(ptr = get_cursor_line_ptr() + curwin->w_cursor.col) != NUL) {
- if (ptr[1] == NUL)
+ && *(s->ptr = get_cursor_line_ptr() + curwin->w_cursor.col) != NUL) {
+ if (s->ptr[1] == NUL) {
++curwin->w_cursor.col;
- else if (has_mbyte) {
- i = (*mb_ptr2len)(ptr);
- if (ptr[i] == NUL)
- curwin->w_cursor.col += i;
+ } else if (has_mbyte) {
+ s->i = (*mb_ptr2len)(s->ptr);
+ if (s->ptr[s->i] == NUL) {
+ curwin->w_cursor.col += s->i;
+ }
}
}
- ins_at_eol = FALSE;
- } else
- arrow_used = FALSE;
+ ins_at_eol = false;
+ } else {
+ arrow_used = false;
+ }
- /* we are in insert mode now, don't need to start it anymore */
- need_start_insertmode = FALSE;
+ // we are in insert mode now, don't need to start it anymore
+ need_start_insertmode = false;
- /* Need to save the line for undo before inserting the first char. */
- ins_need_undo = TRUE;
+ // Need to save the line for undo before inserting the first char.
+ ins_need_undo = true;
where_paste_started.lnum = 0;
- can_cindent = TRUE;
- /* The cursor line is not in a closed fold, unless 'insertmode' is set or
- * restarting. */
- if (!p_im && did_restart_edit == 0)
+ can_cindent = true;
+ // The cursor line is not in a closed fold, unless 'insertmode' is set or
+ // restarting.
+ if (!p_im && did_restart_edit == 0) {
foldOpenCursor();
+ }
- /*
- * If 'showmode' is set, show the current (insert/replace/..) mode.
- * A warning message for changing a readonly file is given here, before
- * actually changing anything. It's put after the mode, if any.
- */
- i = 0;
- if (p_smd && msg_silent == 0)
- i = showmode();
+ // If 'showmode' is set, show the current (insert/replace/..) mode.
+ // A warning message for changing a readonly file is given here, before
+ // actually changing anything. It's put after the mode, if any.
+ s->i = 0;
+ if (p_smd && msg_silent == 0) {
+ s->i = showmode();
+ }
- if (!p_im && did_restart_edit == 0)
- change_warning(i == 0 ? 0 : i + 1);
+ if (!p_im && did_restart_edit == 0) {
+ change_warning(s->i == 0 ? 0 : s->i + 1);
+ }
ui_cursor_shape(); /* may show different cursor shape */
do_digraph(-1); /* clear digraphs */
- /*
- * Get the current length of the redo buffer, those characters have to be
- * skipped if we want to get to the inserted characters.
- */
- ptr = get_inserted();
- if (ptr == NULL)
+ // Get the current length of the redo buffer, those characters have to be
+ // skipped if we want to get to the inserted characters.
+ s->ptr = get_inserted();
+ if (s->ptr == NULL) {
new_insert_skip = 0;
- else {
- new_insert_skip = (int)STRLEN(ptr);
- xfree(ptr);
+ } else {
+ new_insert_skip = (int)STRLEN(s->ptr);
+ xfree(s->ptr);
}
old_indent = 0;
- /*
- * Main loop in Insert mode: repeat until Insert mode is left.
- */
- for (;; ) {
- if (!revins_legal)
- revins_scol = -1; /* reset on illegal motions */
- else
- revins_legal = 0;
- if (arrow_used) /* don't repeat insert when arrow key used */
- count = 0;
+ do {
+ state_enter(&s->state);
+ // If s->count != 0, `ins_esc` will prepare the redo buffer for reprocessing
+ // and return false, causing `state_enter` to be called again.
+ } while (!ins_esc(&s->count, s->cmdchar, s->nomove));
- if (update_Insstart_orig) {
- Insstart_orig = Insstart;
- }
+ // Always update o_lnum, so that a "CTRL-O ." that adds a line
+ // still puts the cursor back after the inserted text.
+ if (ins_at_eol && gchar_cursor() == NUL) {
+ o_lnum = curwin->w_cursor.lnum;
+ }
- if (stop_insert_mode) {
- /* ":stopinsert" used or 'insertmode' reset */
- count = 0;
- goto doESCkey;
- }
+ if (s->cmdchar != 'r' && s->cmdchar != 'v') {
+ apply_autocmds(EVENT_INSERTLEAVE, NULL, NULL, false, curbuf);
+ }
+ did_cursorhold = false;
+}
+
+static int insert_check(VimState *state)
+{
+ InsertState *s = (InsertState *)state;
+
+ // If typed something may trigger CursorHoldI again.
+ if (s->c != K_EVENT) {
+ did_cursorhold = false;
+ }
+
+ // If the cursor was moved we didn't just insert a space */
+ if (arrow_used) {
+ s->inserted_space = false;
+ }
+
+ if (can_cindent && cindent_on() && ctrl_x_mode == 0) {
+ insert_do_cindent(s);
+ }
+
+ if (!revins_legal) {
+ revins_scol = -1; // reset on illegal motions
+ } else {
+ revins_legal = 0;
+ }
+
+ if (arrow_used) { // don't repeat insert when arrow key used
+ s->count = 0;
+ }
+
+ if (update_Insstart_orig) {
+ Insstart_orig = Insstart;
+ }
+
+ if (stop_insert_mode) {
+ // ":stopinsert" used or 'insertmode' reset
+ s->count = 0;
+ return 0; // exit insert mode
+ }
- /* set curwin->w_curswant for next K_DOWN or K_UP */
- if (!arrow_used)
- curwin->w_set_curswant = TRUE;
+ // set curwin->w_curswant for next K_DOWN or K_UP
+ if (!arrow_used) {
+ curwin->w_set_curswant = true;
+ }
- /* If there is no typeahead may check for timestamps (e.g., for when a
- * menu invoked a shell command). */
- if (stuff_empty()) {
- did_check_timestamps = FALSE;
- if (need_check_timestamps)
- check_timestamps(FALSE);
+ // If there is no typeahead may check for timestamps (e.g., for when a
+ // menu invoked a shell command).
+ if (stuff_empty()) {
+ did_check_timestamps = false;
+ if (need_check_timestamps) {
+ check_timestamps(false);
}
+ }
- /*
- * When emsg() was called msg_scroll will have been set.
- */
- msg_scroll = FALSE;
+ // When emsg() was called msg_scroll will have been set.
+ msg_scroll = false;
- /* Open fold at the cursor line, according to 'foldopen'. */
- if (fdo_flags & FDO_INSERT)
- foldOpenCursor();
- /* Close folds where the cursor isn't, according to 'foldclose' */
- if (!char_avail())
- foldCheckClose();
+ // Open fold at the cursor line, according to 'foldopen'.
+ if (fdo_flags & FDO_INSERT) {
+ foldOpenCursor();
+ }
- /*
- * If we inserted a character at the last position of the last line in
- * the window, scroll the window one line up. This avoids an extra
- * redraw.
- * This is detected when the cursor column is smaller after inserting
- * something.
- * Don't do this when the topline changed already, it has
- * already been adjusted (by insertchar() calling open_line())).
- */
- if (curbuf->b_mod_set
- && curwin->w_p_wrap
- && !did_backspace
- && curwin->w_topline == old_topline
- && curwin->w_topfill == old_topfill
- ) {
- mincol = curwin->w_wcol;
- validate_cursor_col();
-
- if (curwin->w_wcol < mincol - curbuf->b_p_ts
- && curwin->w_wrow == curwin->w_winrow
- + curwin->w_height - 1 - p_so
- && (curwin->w_cursor.lnum != curwin->w_topline
- || curwin->w_topfill > 0
- )) {
- if (curwin->w_topfill > 0)
- --curwin->w_topfill;
- else if (hasFolding(curwin->w_topline, NULL, &old_topline))
- set_topline(curwin, old_topline + 1);
- else
- set_topline(curwin, curwin->w_topline + 1);
+ // Close folds where the cursor isn't, according to 'foldclose'
+ if (!char_avail()) {
+ foldCheckClose();
+ }
+
+ // If we inserted a character at the last position of the last line in the
+ // window, scroll the window one line up. This avoids an extra redraw. This
+ // is detected when the cursor column is smaller after inserting something.
+ // Don't do this when the topline changed already, it has already been
+ // adjusted (by insertchar() calling open_line())).
+ if (curbuf->b_mod_set
+ && curwin->w_p_wrap
+ && !s->did_backspace
+ && curwin->w_topline == s->old_topline
+ && curwin->w_topfill == s->old_topfill) {
+ s->mincol = curwin->w_wcol;
+ validate_cursor_col();
+
+ if (curwin->w_wcol < s->mincol - curbuf->b_p_ts
+ && curwin->w_wrow == curwin->w_winrow
+ + curwin->w_height - 1 - p_so
+ && (curwin->w_cursor.lnum != curwin->w_topline
+ || curwin->w_topfill > 0)) {
+ if (curwin->w_topfill > 0) {
+ --curwin->w_topfill;
+ } else if (hasFolding(curwin->w_topline, NULL, &s->old_topline)) {
+ set_topline(curwin, s->old_topline + 1);
+ } else {
+ set_topline(curwin, curwin->w_topline + 1);
}
}
+ }
- /* May need to adjust w_topline to show the cursor. */
- update_topline();
+ // May need to adjust w_topline to show the cursor.
+ update_topline();
- did_backspace = FALSE;
+ s->did_backspace = false;
- validate_cursor(); /* may set must_redraw */
+ validate_cursor(); // may set must_redraw
- /*
- * Redraw the display when no characters are waiting.
- * Also shows mode, ruler and positions cursor.
- */
- ins_redraw(TRUE);
+ // Redraw the display when no characters are waiting.
+ // Also shows mode, ruler and positions cursor.
+ ins_redraw(true);
- if (curwin->w_p_scb)
- do_check_scrollbind(TRUE);
+ if (curwin->w_p_scb) {
+ do_check_scrollbind(true);
+ }
- if (curwin->w_p_crb)
- do_check_cursorbind();
- update_curswant();
- old_topline = curwin->w_topline;
- old_topfill = curwin->w_topfill;
+ if (curwin->w_p_crb) {
+ do_check_cursorbind();
+ }
+ update_curswant();
+ s->old_topline = curwin->w_topline;
+ s->old_topfill = curwin->w_topfill;
+ s->lastc = s->c; // remember previous char for CTRL-D
- /*
- * Get a character for Insert mode. Ignore K_IGNORE.
- */
- lastc = c; /* remember previous char for CTRL-D */
+ // After using CTRL-G U the next cursor key will not break undo.
+ if (dont_sync_undo == MAYBE) {
+ dont_sync_undo = true;
+ } else {
+ dont_sync_undo = false;
+ }
- // After using CTRL-G U the next cursor key will not break undo.
- if (dont_sync_undo == MAYBE)
- dont_sync_undo = true;
- else
- dont_sync_undo = false;
+ return 1;
+}
- input_enable_events();
- do {
- c = safe_vgetc();
- } while (c == K_IGNORE);
- input_disable_events();
+static int insert_execute(VimState *state, int key)
+{
+ if (key == K_IGNORE) {
+ return -1; // get another key
+ }
+ InsertState *s = (InsertState *)state;
+ s->c = key;
- if (c == K_EVENT) {
- c = lastc;
- queue_process_events(loop.events);
- continue;
- }
+ // Don't want K_EVENT with cursorhold for the second key, e.g., after CTRL-V.
+ did_cursorhold = true;
- /* Don't want K_CURSORHOLD for the second key, e.g., after CTRL-V. */
- did_cursorhold = TRUE;
+ if (p_hkmap && KeyTyped) {
+ s->c = hkmap(s->c); // Hebrew mode mapping
+ }
- if (p_hkmap && KeyTyped)
- c = hkmap(c); /* Hebrew mode mapping */
- if (p_fkmap && KeyTyped)
- c = fkmap(c); /* Farsi mode mapping */
+ if (p_fkmap && KeyTyped) {
+ s->c = fkmap(s->c); // Farsi mode mapping
+ }
- /*
- * Special handling of keys while the popup menu is visible or wanted
- * and the cursor is still in the completed word. Only when there is
- * a match, skip this when no matches were found.
- */
- if (compl_started
- && pum_wanted()
- && curwin-&