diff options
| author | Thiago de Arruda <tpadilha84@gmail.com> | 2015-10-26 11:24:51 -0300 |
|---|---|---|
| committer | Thiago de Arruda <tpadilha84@gmail.com> | 2015-10-26 11:24:51 -0300 |
| commit | 29d64a901df6eff436b100374b2a240eea1906d9 (patch) | |
| tree | 613b820fdc3d1d48a68146a4b516d05898798513 | |
| parent | 424b00ea610b90ed4041711f27d71d0e359038a7 (diff) | |
| parent | 1726c7d999e68b4ed8aee234b7dfa339ed0784b2 (diff) | |
| download | rneovim-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.md | 190 | ||||
| -rw-r--r-- | src/nvim/edit.c | 1719 | ||||
| -rw-r--r-- | src/nvim/ex_docmd.c | 2 | ||||
| -rw-r--r-- | src/nvim/ex_getln.c | 2564 | ||||
| -rw-r--r-- | src/nvim/getchar.c | 2 | ||||
| -rw-r--r-- | src/nvim/keymap.c | 1 | ||||
| -rw-r--r-- | src/nvim/keymap.h | 2 | ||||
| -rw-r--r-- | src/nvim/main.c | 219 | ||||
| -rw-r--r-- | src/nvim/normal.c | 1443 | ||||
| -rw-r--r-- | src/nvim/normal.h | 4 | ||||
| -rw-r--r-- | src/nvim/os/input.c | 40 | ||||
| -rw-r--r-- | src/nvim/state.c | 62 | ||||
| -rw-r--r-- | src/nvim/state.h | 20 | ||||
| -rw-r--r-- | src/nvim/terminal.c | 160 | ||||
| -rw-r--r-- | test/functional/api/vim_spec.lua | 18 | ||||
| -rw-r--r-- | test/functional/terminal/tui_spec.lua | 47 |
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-& |