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 /src | |
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`'
Diffstat (limited to 'src')
-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 |
14 files changed, 3525 insertions, 2903 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->w_cursor.col >= compl_col - && (compl_shown_match == NULL - || compl_shown_match != compl_shown_match->cp_next)) { - /* BS: Delete one character from "compl_leader". */ - if ((c == K_BS || c == Ctrl_H) - && curwin->w_cursor.col > compl_col - && (c = ins_compl_bs()) == NUL) - continue; - - /* When no match was selected or it was edited. */ - if (!compl_used_match) { - /* CTRL-L: Add one character from the current match to - * "compl_leader". Except when at the original match and - * there is nothing to add, CTRL-L works like CTRL-P then. */ - if (c == Ctrl_L - && (!CTRL_X_MODE_LINE_OR_EVAL(ctrl_x_mode) - || (int)STRLEN(compl_shown_match->cp_str) - > curwin->w_cursor.col - compl_col)) { - ins_compl_addfrommatch(); - continue; - } + // 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->w_cursor.col >= compl_col + && (compl_shown_match == NULL + || compl_shown_match != compl_shown_match->cp_next)) { + // BS: Delete one character from "compl_leader". + if ((s->c == K_BS || s->c == Ctrl_H) + && curwin->w_cursor.col > compl_col + && (s->c = ins_compl_bs()) == NUL) { + return 1; // continue + } - /* A non-white character that fits in with the current - * completion: Add to "compl_leader". */ - if (ins_compl_accept_char(c)) { - /* Trigger InsertCharPre. */ - char_u *str = do_insert_char_pre(c); - char_u *p; - - if (str != NULL) { - for (p = str; *p != NUL; mb_ptr_adv(p)) - ins_compl_addleader(PTR2CHAR(p)); - xfree(str); - } else - ins_compl_addleader(c); - continue; - } + // When no match was selected or it was edited. + if (!compl_used_match) { + // CTRL-L: Add one character from the current match to + // "compl_leader". Except when at the original match and + // there is nothing to add, CTRL-L works like CTRL-P then. + if (s->c == Ctrl_L + && (!CTRL_X_MODE_LINE_OR_EVAL(ctrl_x_mode) + || (int)STRLEN(compl_shown_match->cp_str) + > curwin->w_cursor.col - compl_col)) { + ins_compl_addfrommatch(); + return 1; // continue + } + + // A non-white character that fits in with the current + // completion: Add to "compl_leader". + if (ins_compl_accept_char(s->c)) { + // Trigger InsertCharPre. + char_u *str = do_insert_char_pre(s->c); + char_u *p; - /* Pressing CTRL-Y selects the current match. When - * compl_enter_selects is set the Enter key does the same. */ - if (c == Ctrl_Y || (compl_enter_selects - && (c == CAR || c == K_KENTER || c == NL))) { - ins_compl_delete(); - ins_compl_insert(); + if (str != NULL) { + for (p = str; *p != NUL; mb_ptr_adv(p)) { + ins_compl_addleader(PTR2CHAR(p)); + } + xfree(str); + } else { + ins_compl_addleader(s->c); } + return 1; // continue + } + + // Pressing CTRL-Y selects the current match. When + // compl_enter_selects is set the Enter key does the same. + if (s->c == Ctrl_Y + || (compl_enter_selects + && (s->c == CAR || s->c == K_KENTER || s->c == NL))) { + ins_compl_delete(); + ins_compl_insert(); } } + } - /* Prepare for or stop CTRL-X mode. This doesn't do completion, but - * it does fix up the text when finishing completion. */ - compl_get_longest = FALSE; - if (ins_compl_prep(c)) - continue; + // Prepare for or stop CTRL-X mode. This doesn't do completion, but it does + // fix up the text when finishing completion. + compl_get_longest = false; + if (ins_compl_prep(s->c)) { + return 1; // continue + } - /* CTRL-\ CTRL-N goes to Normal mode, - * CTRL-\ CTRL-G goes to mode selected with 'insertmode', - * CTRL-\ CTRL-O is like CTRL-O but without moving the cursor. */ - if (c == Ctrl_BSL) { - /* may need to redraw when no more chars available now */ - ins_redraw(FALSE); - ++no_mapping; - ++allow_keys; - c = plain_vgetc(); - --no_mapping; - --allow_keys; - if (c != Ctrl_N && c != Ctrl_G && c != Ctrl_O) { - /* it's something else */ - vungetc(c); - c = Ctrl_BSL; - } else if (c == Ctrl_G && p_im) - continue; - else { - if (c == Ctrl_O) { - ins_ctrl_o(); - ins_at_eol = FALSE; /* cursor keeps its column */ - nomove = TRUE; - } - count = 0; - goto doESCkey; + // CTRL-\ CTRL-N goes to Normal mode, + // CTRL-\ CTRL-G goes to mode selected with 'insertmode', + // CTRL-\ CTRL-O is like CTRL-O but without moving the cursor + if (s->c == Ctrl_BSL) { + // may need to redraw when no more chars available now + ins_redraw(false); + ++no_mapping; + ++allow_keys; + s->c = plain_vgetc(); + --no_mapping; + --allow_keys; + if (s->c != Ctrl_N && s->c != Ctrl_G && s->c != Ctrl_O) { + // it's something else + vungetc(s->c); + s->c = Ctrl_BSL; + } else if (s->c == Ctrl_G && p_im) { + return 1; // continue + } else { + if (s->c == Ctrl_O) { + ins_ctrl_o(); + ins_at_eol = false; // cursor keeps its column + s->nomove = true; } + s->count = 0; + return 0; } + } - c = do_digraph(c); + s->c = do_digraph(s->c); - if ((c == Ctrl_V || c == Ctrl_Q) && ctrl_x_mode == CTRL_X_CMDLINE) - goto docomplete; - if (c == Ctrl_V || c == Ctrl_Q) { - ins_ctrl_v(); - c = Ctrl_V; /* pretend CTRL-V is last typed character */ - continue; + if ((s->c == Ctrl_V || s->c == Ctrl_Q) && ctrl_x_mode == CTRL_X_CMDLINE) { + insert_do_complete(s); + return 1; + } + + if (s->c == Ctrl_V || s->c == Ctrl_Q) { + ins_ctrl_v(); + s->c = Ctrl_V; // pretend CTRL-V is last typed character + return 1; // continue + } + + if (cindent_on() + && ctrl_x_mode == 0) { + // A key name preceded by a bang means this key is not to be + // inserted. Skip ahead to the re-indenting below. + // A key name preceded by a star means that indenting has to be + // done before inserting the key. + s->line_is_white = inindent(0); + if (in_cinkeys(s->c, '!', s->line_is_white)) { + insert_do_cindent(s); + return 1; // continue } - if (cindent_on() - && ctrl_x_mode == 0 - ) { - /* A key name preceded by a bang means this key is not to be - * inserted. Skip ahead to the re-indenting below. - * A key name preceded by a star means that indenting has to be - * done before inserting the key. */ - line_is_white = inindent(0); - if (in_cinkeys(c, '!', line_is_white)) - goto force_cindent; - if (can_cindent && in_cinkeys(c, '*', line_is_white) - && stop_arrow() == OK) - do_c_expr_indent(); + if (can_cindent && in_cinkeys(s->c, '*', s->line_is_white) + && stop_arrow() == OK) { + do_c_expr_indent(); } + } - if (curwin->w_p_rl) - switch (c) { - case K_LEFT: c = K_RIGHT; break; - case K_S_LEFT: c = K_S_RIGHT; break; - case K_C_LEFT: c = K_C_RIGHT; break; - case K_RIGHT: c = K_LEFT; break; - case K_S_RIGHT: c = K_S_LEFT; break; - case K_C_RIGHT: c = K_C_LEFT; break; - } + if (curwin->w_p_rl) + switch (s->c) { + case K_LEFT: s->c = K_RIGHT; break; + case K_S_LEFT: s->c = K_S_RIGHT; break; + case K_C_LEFT: s->c = K_C_RIGHT; break; + case K_RIGHT: s->c = K_LEFT; break; + case K_S_RIGHT: s->c = K_S_LEFT; break; + case K_C_RIGHT: s->c = K_C_LEFT; break; + } - /* - * If 'keymodel' contains "startsel", may start selection. If it - * does, a CTRL-O and c will be stuffed, we need to get these - * characters. - */ - if (ins_start_select(c)) - continue; + // If 'keymodel' contains "startsel", may start selection. If it + // does, a CTRL-O and c will be stuffed, we need to get these + // characters. + if (ins_start_select(s->c)) { + return 1; // continue + } - /* - * The big switch to handle a character in insert mode. - */ - switch (c) { - case ESC: /* End input mode */ - if (echeck_abbr(ESC + ABBR_OFF)) - break; - /*FALLTHROUGH*/ - - case Ctrl_C: /* End input mode */ - if (c == Ctrl_C && cmdwin_type != 0) { - /* Close the cmdline window. */ - cmdwin_result = K_IGNORE; - got_int = FALSE; /* don't stop executing autocommands et al. */ - nomove = TRUE; - goto doESCkey; - } + return insert_handle_key(s); +} -#ifdef UNIX -do_intr: -#endif - // when 'insertmode' set, and not halfway through a mapping, don't leave - // Insert mode - if (goto_im()) { - if (got_int) { - (void)vgetc(); /* flush all buffers */ - got_int = false; - } else { - vim_beep(BO_IM); - } - break; - } -doESCkey: - /* - * This is the ONLY return from edit()! - */ - /* 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 (ins_esc(&count, cmdchar, nomove)) { - if (cmdchar != 'r' && cmdchar != 'v') - apply_autocmds(EVENT_INSERTLEAVE, NULL, NULL, - FALSE, curbuf); - did_cursorhold = FALSE; - return c == Ctrl_O; - } - continue; +static int insert_handle_key(InsertState *s) +{ + // The big switch to handle a character in insert mode. + // TODO(tarruda): This could look better if a lookup table is used. + // (similar to normal mode `nv_cmds[]`) + switch (s->c) { + case ESC: // End input mode + if (echeck_abbr(ESC + ABBR_OFF)) { + break; + } + // FALLTHROUGH - case Ctrl_Z: /* suspend when 'insertmode' set */ - if (!p_im) - goto normalchar; /* insert CTRL-Z as normal char */ - stuffReadbuff((char_u *)":st\r"); - c = Ctrl_O; - /*FALLTHROUGH*/ - - case Ctrl_O: /* execute one command */ - if (ctrl_x_mode == CTRL_X_OMNI) - goto docomplete; - if (echeck_abbr(Ctrl_O + ABBR_OFF)) - break; - ins_ctrl_o(); + case Ctrl_C: // End input mode + if (s->c == Ctrl_C && cmdwin_type != 0) { + // Close the cmdline window. */ + cmdwin_result = K_IGNORE; + got_int = false; // don't stop executing autocommands et al + s->nomove = true; + return 0; // exit insert mode + } - /* don't move the cursor left when 'virtualedit' has "onemore". */ - if (ve_flags & VE_ONEMORE) { - ins_at_eol = FALSE; - nomove = TRUE; + // when 'insertmode' set, and not halfway through a mapping, don't leave + // Insert mode + if (goto_im()) { + if (got_int) { + (void)vgetc(); // flush all buffers + got_int = false; + } else { + vim_beep(BO_IM); } - count = 0; - goto doESCkey; + break; + } + return 0; // exit insert mode - case K_INS: /* toggle insert/replace mode */ - case K_KINS: - ins_insert(replaceState); + case Ctrl_Z: // suspend when 'insertmode' set + if (!p_im) { + goto normalchar; // insert CTRL-Z as normal char + } + stuffReadbuff((char_u *)":st\r"); + s->c = Ctrl_O; + // FALLTHROUGH + + case Ctrl_O: // execute one command + if (ctrl_x_mode == CTRL_X_OMNI) { + insert_do_complete(s); break; + } - case K_SELECT: /* end of Select mode mapping - ignore */ + if (echeck_abbr(Ctrl_O + ABBR_OFF)) { break; + } + ins_ctrl_o(); - case K_HELP: /* Help key works like <ESC> <Help> */ - case K_F1: - case K_XF1: - stuffcharReadbuff(K_HELP); - if (p_im) - need_start_insertmode = TRUE; - goto doESCkey; - - - case K_ZERO: /* Insert the previously inserted text. */ - case NUL: - case Ctrl_A: - /* For ^@ the trailing ESC will end the insert, unless there is an - * error. */ - if (stuff_inserted(NUL, 1L, (c == Ctrl_A)) == FAIL - && c != Ctrl_A && !p_im) - goto doESCkey; /* quit insert mode */ - inserted_space = FALSE; - break; + // don't move the cursor left when 'virtualedit' has "onemore". + if (ve_flags & VE_ONEMORE) { + ins_at_eol = false; + s->nomove = true; + } - case Ctrl_R: /* insert the contents of a register */ - ins_reg(); - auto_format(FALSE, TRUE); - inserted_space = FALSE; - break; + s->count = 0; + return 0; // exit insert mode - case Ctrl_G: /* commands starting with CTRL-G */ - ins_ctrl_g(); - break; + case K_INS: // toggle insert/replace mode + case K_KINS: + ins_insert(s->replaceState); + break; - case Ctrl_HAT: /* switch input mode and/or langmap */ - ins_ctrl_hat(); - break; + case K_SELECT: // end of Select mode mapping - ignore + break; - case Ctrl__: /* switch between languages */ - if (!p_ari) - goto normalchar; - ins_ctrl_(); - break; - case Ctrl_D: /* Make indent one shiftwidth smaller. */ - if (ctrl_x_mode == CTRL_X_PATH_DEFINES) - goto docomplete; - /* FALLTHROUGH */ + case K_HELP: // Help key works like <ESC> <Help> + case K_F1: + case K_XF1: + stuffcharReadbuff(K_HELP); + if (p_im) { + need_start_insertmode = true; + } + return 0; // exit insert mode - case Ctrl_T: /* Make indent one shiftwidth greater. */ - if (c == Ctrl_T && ctrl_x_mode == CTRL_X_THESAURUS) { - if (has_compl_option(FALSE)) - goto docomplete; - break; - } - ins_shift(c, lastc); - auto_format(FALSE, TRUE); - inserted_space = FALSE; - break; - case K_DEL: /* delete character under the cursor */ - case K_KDEL: - ins_del(); - auto_format(FALSE, TRUE); - break; + case K_ZERO: // Insert the previously inserted text. + case NUL: + case Ctrl_A: + // For ^@ the trailing ESC will end the insert, unless there is an + // error. + if (stuff_inserted(NUL, 1L, (s->c == Ctrl_A)) == FAIL + && s->c != Ctrl_A && !p_im) { + return 0; // exit insert mode + } + s->inserted_space = false; + break; - case K_BS: /* delete character before the cursor */ - case Ctrl_H: - did_backspace = ins_bs(c, BACKSPACE_CHAR, &inserted_space); - auto_format(FALSE, TRUE); - break; + case Ctrl_R: // insert the contents of a register + ins_reg(); + auto_format(false, true); + s->inserted_space = false; + break; - case Ctrl_W: /* delete word before the cursor */ - did_backspace = ins_bs(c, BACKSPACE_WORD, &inserted_space); - auto_format(FALSE, TRUE); - break; + case Ctrl_G: // commands starting with CTRL-G + ins_ctrl_g(); + break; - case Ctrl_U: /* delete all inserted text in current line */ - /* CTRL-X CTRL-U completes with 'completefunc'. */ - if (ctrl_x_mode == CTRL_X_FUNCTION) - goto docomplete; - did_backspace = ins_bs(c, BACKSPACE_LINE, &inserted_space); - auto_format(FALSE, TRUE); - inserted_space = FALSE; - break; + case Ctrl_HAT: // switch input mode and/or langmap + ins_ctrl_hat(); + break; - case K_LEFTMOUSE: /* mouse keys */ - case K_LEFTMOUSE_NM: - case K_LEFTDRAG: - case K_LEFTRELEASE: - case K_LEFTRELEASE_NM: - case K_MIDDLEMOUSE: - case K_MIDDLEDRAG: - case K_MIDDLERELEASE: - case K_RIGHTMOUSE: - case K_RIGHTDRAG: - case K_RIGHTRELEASE: - case K_X1MOUSE: - case K_X1DRAG: - case K_X1RELEASE: - case K_X2MOUSE: - case K_X2DRAG: - case K_X2RELEASE: - ins_mouse(c); - break; + case Ctrl__: // switch between languages + if (!p_ari) { + goto normalchar; + } + ins_ctrl_(); + break; - case K_MOUSEDOWN: /* Default action for scroll wheel up: scroll up */ - ins_mousescroll(MSCR_DOWN); + case Ctrl_D: // Make indent one shiftwidth smaller. + if (ctrl_x_mode == CTRL_X_PATH_DEFINES) { + insert_do_complete(s); break; + } + // FALLTHROUGH - case K_MOUSEUP: /* Default action for scroll wheel down: scroll down */ - ins_mousescroll(MSCR_UP); + case Ctrl_T: // Make indent one shiftwidth greater. + if (s->c == Ctrl_T && ctrl_x_mode == CTRL_X_THESAURUS) { + if (has_compl_option(false)) { + insert_do_complete(s); + } break; + } + ins_shift(s->c, s->lastc); + auto_format(false, true); + s->inserted_space = false; + break; - case K_MOUSELEFT: /* Scroll wheel left */ - ins_mousescroll(MSCR_LEFT); - break; + case K_DEL: // delete character under the cursor + case K_KDEL: + ins_del(); + auto_format(false, true); + break; - case K_MOUSERIGHT: /* Scroll wheel right */ - ins_mousescroll(MSCR_RIGHT); - break; + case K_BS: // delete character before the cursor + case Ctrl_H: + s->did_backspace = ins_bs(s->c, BACKSPACE_CHAR, &s->inserted_space); + auto_format(false, true); + break; - case K_IGNORE: /* Something mapped to nothing */ - break; + case Ctrl_W: // delete word before the cursor + s->did_backspace = ins_bs(s->c, BACKSPACE_WORD, &s->inserted_space); + auto_format(false, true); + break; - case K_CURSORHOLD: /* Didn't type something for a while. */ - apply_autocmds(EVENT_CURSORHOLDI, NULL, NULL, FALSE, curbuf); - did_cursorhold = TRUE; - break; + case Ctrl_U: // delete all inserted text in current line + // CTRL-X CTRL-U completes with 'completefunc'. + if (ctrl_x_mode == CTRL_X_FUNCTION) { + insert_do_complete(s); + } else { + s->did_backspace = ins_bs(s->c, BACKSPACE_LINE, &s->inserted_space); + auto_format(false, true); + s->inserted_space = false; + } + break; - case K_HOME: /* <Home> */ - case K_KHOME: - case K_S_HOME: - case K_C_HOME: - ins_home(c); - break; + case K_LEFTMOUSE: // mouse keys + case K_LEFTMOUSE_NM: + case K_LEFTDRAG: + case K_LEFTRELEASE: + case K_LEFTRELEASE_NM: + case K_MIDDLEMOUSE: + case K_MIDDLEDRAG: + case K_MIDDLERELEASE: + case K_RIGHTMOUSE: + case K_RIGHTDRAG: + case K_RIGHTRELEASE: + case K_X1MOUSE: + case K_X1DRAG: + case K_X1RELEASE: + case K_X2MOUSE: + case K_X2DRAG: + case K_X2RELEASE: + ins_mouse(s->c); + break; - case K_END: /* <End> */ - case K_KEND: - case K_S_END: - case K_C_END: - ins_end(c); - break; + case K_MOUSEDOWN: // Default action for scroll wheel up: scroll up + ins_mousescroll(MSCR_DOWN); + break; - case K_LEFT: /* <Left> */ - if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)) - ins_s_left(); - else - ins_left(dont_sync_undo == false); - break; + case K_MOUSEUP: // Default action for scroll wheel down: scroll down + ins_mousescroll(MSCR_UP); + break; + + case K_MOUSELEFT: // Scroll wheel left + ins_mousescroll(MSCR_LEFT); + break; + + case K_MOUSERIGHT: // Scroll wheel right + ins_mousescroll(MSCR_RIGHT); + break; + + case K_IGNORE: // Something mapped to nothing + break; - case K_S_LEFT: /* <S-Left> */ - case K_C_LEFT: + case K_EVENT: // some event + queue_process_events(loop.events); + break; + + case K_HOME: // <Home> + case K_KHOME: + case K_S_HOME: + case K_C_HOME: + ins_home(s->c); + break; + + case K_END: // <End> + case K_KEND: + case K_S_END: + case K_C_END: + ins_end(s->c); + break; + + case K_LEFT: // <Left> + if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)) { ins_s_left(); - break; + } else { + ins_left(dont_sync_undo == false); + } + break; - case K_RIGHT: /* <Right> */ - if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)) - ins_s_right(); - else - ins_right(dont_sync_undo == false); - break; + case K_S_LEFT: // <S-Left> + case K_C_LEFT: + ins_s_left(); + break; - case K_S_RIGHT: /* <S-Right> */ - case K_C_RIGHT: + case K_RIGHT: // <Right> + if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)) { ins_s_right(); - break; + } else { + ins_right(dont_sync_undo == false); + } + break; - case K_UP: /* <Up> */ - if (pum_visible()) - goto docomplete; - if (mod_mask & MOD_MASK_SHIFT) - ins_pageup(); - else - ins_up(FALSE); - break; + case K_S_RIGHT: // <S-Right> + case K_C_RIGHT: + ins_s_right(); + break; - case K_S_UP: /* <S-Up> */ - case K_PAGEUP: - case K_KPAGEUP: - if (pum_visible()) - goto docomplete; + case K_UP: // <Up> + if (pum_visible()) { + insert_do_complete(s); + } else if (mod_mask & MOD_MASK_SHIFT) { ins_pageup(); - break; + } else { + ins_up(false); + } + break; - case K_DOWN: /* <Down> */ - if (pum_visible()) - goto docomplete; - if (mod_mask & MOD_MASK_SHIFT) - ins_pagedown(); - else - ins_down(FALSE); - break; + case K_S_UP: // <S-Up> + case K_PAGEUP: + case K_KPAGEUP: + if (pum_visible()) { + insert_do_complete(s); + } else { + ins_pageup(); + } + break; - case K_S_DOWN: /* <S-Down> */ - case K_PAGEDOWN: - case K_KPAGEDOWN: - if (pum_visible()) - goto docomplete; + case K_DOWN: // <Down> + if (pum_visible()) { + insert_do_complete(s); + } else if (mod_mask & MOD_MASK_SHIFT) { ins_pagedown(); - break; + } else { + ins_down(false); + } + break; + + case K_S_DOWN: // <S-Down> + case K_PAGEDOWN: + case K_KPAGEDOWN: + if (pum_visible()) { + insert_do_complete(s); + } else { + ins_pagedown(); + } + break; - case K_S_TAB: /* When not mapped, use like a normal TAB */ - c = TAB; - /* FALLTHROUGH */ + case K_S_TAB: // When not mapped, use like a normal TAB + s->c = TAB; + // FALLTHROUGH - case TAB: /* TAB or Complete patterns along path */ - if (ctrl_x_mode == CTRL_X_PATH_PATTERNS) - goto docomplete; - inserted_space = FALSE; - if (ins_tab()) - goto normalchar; /* insert TAB as a normal char */ - auto_format(FALSE, TRUE); + case TAB: // TAB or Complete patterns along path + if (ctrl_x_mode == CTRL_X_PATH_PATTERNS) { + insert_do_complete(s); break; + } + s->inserted_space = false; + if (ins_tab()) { + goto normalchar; // insert TAB as a normal char + } + auto_format(false, true); + break; - case K_KENTER: /* <Enter> */ - c = CAR; - /* FALLTHROUGH */ - case CAR: - case NL: - /* In a quickfix window a <CR> jumps to the error under the - * cursor. */ - if (bt_quickfix(curbuf) && c == CAR) { - if (curwin->w_llist_ref == NULL) /* quickfix window */ - do_cmdline_cmd(".cc"); - else /* location list window */ - do_cmdline_cmd(".ll"); - break; - } - if (cmdwin_type != 0) { - /* Execute the command in the cmdline window. */ - cmdwin_result = CAR; - goto doESCkey; + case K_KENTER: // <Enter> + s->c = CAR; + // FALLTHROUGH + case CAR: + case NL: + // In a quickfix window a <CR> jumps to the error under the + // cursor. + if (bt_quickfix(curbuf) && s->c == CAR) { + if (curwin->w_llist_ref == NULL) { // quickfix window + do_cmdline_cmd(".cc"); + } else { // location list window + do_cmdline_cmd(".ll"); } - if (ins_eol(c) && !p_im) - goto doESCkey; /* out of memory */ - auto_format(FALSE, FALSE); - inserted_space = FALSE; break; + } + if (cmdwin_type != 0) { + // Execute the command in the cmdline window. + cmdwin_result = CAR; + return 0; + } + if (ins_eol(s->c) && !p_im) { + return 0; // out of memory + } + auto_format(false, false); + s->inserted_space = false; + break; - case Ctrl_K: /* digraph or keyword completion */ - if (ctrl_x_mode == CTRL_X_DICTIONARY) { - if (has_compl_option(TRUE)) - goto docomplete; - break; + case Ctrl_K: // digraph or keyword completion + if (ctrl_x_mode == CTRL_X_DICTIONARY) { + if (has_compl_option(true)) { + insert_do_complete(s); } - c = ins_digraph(); - if (c == NUL) - break; - goto normalchar; + break; + } - case Ctrl_X: /* Enter CTRL-X mode */ - ins_ctrl_x(); + s->c = ins_digraph(); + if (s->c == NUL) { break; + } + goto normalchar; - case Ctrl_RSB: /* Tag name completion after ^X */ - if (ctrl_x_mode != CTRL_X_TAGS) - goto normalchar; - goto docomplete; + case Ctrl_X: // Enter CTRL-X mode + ins_ctrl_x(); + break; - case Ctrl_F: /* File name completion after ^X */ - if (ctrl_x_mode != CTRL_X_FILES) - goto normalchar; - goto docomplete; + case Ctrl_RSB: // Tag name completion after ^X + if (ctrl_x_mode != CTRL_X_TAGS) { + goto normalchar; + } else { + insert_do_complete(s); + } + break; - case 's': /* Spelling completion after ^X */ - case Ctrl_S: - if (ctrl_x_mode != CTRL_X_SPELL) - goto normalchar; - goto docomplete; - - case Ctrl_L: /* Whole line completion after ^X */ - if (ctrl_x_mode != CTRL_X_WHOLE_LINE) { - /* CTRL-L with 'insertmode' set: Leave Insert mode */ - if (p_im) { - if (echeck_abbr(Ctrl_L + ABBR_OFF)) - break; - goto doESCkey; + case Ctrl_F: // File name completion after ^X + if (ctrl_x_mode != CTRL_X_FILES) { + goto normalchar; + } else { + insert_do_complete(s); + } + break; + + case 's': // Spelling completion after ^X + case Ctrl_S: + if (ctrl_x_mode != CTRL_X_SPELL) { + goto normalchar; + } else { + insert_do_complete(s); + } + break; + + case Ctrl_L: // Whole line completion after ^X + if (ctrl_x_mode != CTRL_X_WHOLE_LINE) { + // CTRL-L with 'insertmode' set: Leave Insert mode + if (p_im) { + if (echeck_abbr(Ctrl_L + ABBR_OFF)) { + break; } - goto normalchar; + return 0; // exit insert mode } - /* FALLTHROUGH */ + goto normalchar; + } + // FALLTHROUGH - case Ctrl_P: /* Do previous/next pattern completion */ - case Ctrl_N: - /* if 'complete' is empty then plain ^P is no longer special, - * but it is under other ^X modes */ - if (*curbuf->b_p_cpt == NUL - && ctrl_x_mode != 0 - && !(compl_cont_status & CONT_LOCAL)) - goto normalchar; - -docomplete: - compl_busy = TRUE; - if (ins_complete(c) == FAIL) - compl_cont_status = 0; - compl_busy = FALSE; - break; + case Ctrl_P: // Do previous/next pattern completion + case Ctrl_N: + // if 'complete' is empty then plain ^P is no longer special, + // but it is under other ^X modes + if (*curbuf->b_p_cpt == NUL + && ctrl_x_mode != 0 + && !(compl_cont_status & CONT_LOCAL)) { + goto normalchar; + } - case Ctrl_Y: /* copy from previous line or scroll down */ - case Ctrl_E: /* copy from next line or scroll up */ - c = ins_ctrl_ey(c); - break; + insert_do_complete(s); + break; - default: -#ifdef UNIX - if (c == intr_char) /* special interrupt char */ - goto do_intr; -#endif + case Ctrl_Y: // copy from previous line or scroll down + case Ctrl_E: // copy from next line or scroll up + s->c = ins_ctrl_ey(s->c); + break; -normalchar: - /* - * Insert a normal character. - */ - if (!p_paste) { - /* Trigger InsertCharPre. */ - char_u *str = do_insert_char_pre(c); - char_u *p; + default: - if (str != NULL) { - if (*str != NUL && stop_arrow() != FAIL) { - /* Insert the new value of v:char literally. */ - for (p = str; *p != NUL; mb_ptr_adv(p)) { - c = PTR2CHAR(p); - if (c == CAR || c == K_KENTER || c == NL) - ins_eol(c); - else - ins_char(c); +normalchar: + // Insert a normal character. + if (!p_paste) { + // Trigger InsertCharPre. + char_u *str = do_insert_char_pre(s->c); + char_u *p; + + if (str != NULL) { + if (*str != NUL && stop_arrow() != FAIL) { + // Insert the new value of v:char literally. + for (p = str; *p != NUL; mb_ptr_adv(p)) { + s->c = PTR2CHAR(p); + if (s->c == CAR || s->c == K_KENTER || s->c == NL) { + ins_eol(s->c); + } else { + ins_char(s->c); } - AppendToRedobuffLit(str, -1); } - xfree(str); - c = NUL; + AppendToRedobuffLit(str, -1); } + xfree(str); + s->c = NUL; + } - /* If the new value is already inserted or an empty string - * then don't insert any character. */ - if (c == NUL) - break; + // If the new value is already inserted or an empty string + // then don't insert any character. + if (s->c == NUL) + break; + } + // Try to perform smart-indenting. + ins_try_si(s->c); + + if (s->c == ' ') { + s->inserted_space = true; + if (inindent(0)) { + can_cindent = false; } - /* Try to perform smart-indenting. */ - ins_try_si(c); - - if (c == ' ') { - inserted_space = TRUE; - if (inindent(0)) - can_cindent = FALSE; - if (Insstart_blank_vcol == MAXCOL - && curwin->w_cursor.lnum == Insstart.lnum) - Insstart_blank_vcol = get_nolist_virtcol(); + if (Insstart_blank_vcol == MAXCOL + && curwin->w_cursor.lnum == Insstart.lnum) { + Insstart_blank_vcol = get_nolist_virtcol(); } + } - /* Insert a normal character and check for abbreviations on a - * special character. Let CTRL-] expand abbreviations without - * inserting it. */ - if (vim_iswordc(c) || (!echeck_abbr( - /* Add ABBR_OFF for characters above 0x100, this is - * what check_abbr() expects. */ - (has_mbyte && c >= 0x100) ? (c + ABBR_OFF) : - c) && c != Ctrl_RSB)) { - insert_special(c, FALSE, FALSE); - revins_legal++; - revins_chars++; - } + // Insert a normal character and check for abbreviations on a + // special character. Let CTRL-] expand abbreviations without + // inserting it. + if (vim_iswordc(s->c) + || (!echeck_abbr( + // Add ABBR_OFF for characters above 0x100, this is + // what check_abbr() expects. + (has_mbyte && s->c >= 0x100) ? (s->c + ABBR_OFF) : s->c) + && s->c != Ctrl_RSB)) { + insert_special(s->c, false, false); + revins_legal++; + revins_chars++; + } - auto_format(FALSE, TRUE); + auto_format(false, true); - /* When inserting a character the cursor line must never be in a - * closed fold. */ - foldOpenCursor(); - break; - } /* end of switch (c) */ + // When inserting a character the cursor line must never be in a + // closed fold. + foldOpenCursor(); + break; + } // end of switch (s->c) - /* If typed something may trigger CursorHoldI again. */ - if (c != K_CURSORHOLD) - did_cursorhold = FALSE; + return 1; // continue +} - /* If the cursor was moved we didn't just insert a space */ - if (arrow_used) - inserted_space = FALSE; +static void insert_do_complete(InsertState *s) +{ + compl_busy = true; + if (ins_complete(s->c) == FAIL) { + compl_cont_status = 0; + } + compl_busy = false; +} - if (can_cindent && cindent_on() - && ctrl_x_mode == 0 - ) { -force_cindent: - /* - * Indent now if a key was typed that is in 'cinkeys'. - */ - if (in_cinkeys(c, ' ', line_is_white)) { - if (stop_arrow() == OK) - /* re-indent the current line */ - do_c_expr_indent(); - } +static void insert_do_cindent(InsertState *s) +{ + // Indent now if a key was typed that is in 'cinkeys'. + if (in_cinkeys(s->c, ' ', s->line_is_white)) { + if (stop_arrow() == OK) { + // re-indent the current line + do_c_expr_indent(); } + } +} - } /* for (;;) */ - /* NOTREACHED */ +/* + * 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; + } + + // 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; + } + + InsertState state, *s = &state; + memset(s, 0, sizeof(InsertState)); + s->state.execute = insert_execute; + s->state.check = insert_check; + s->cmdchar = cmdchar; + s->startln = startln; + s->count = count; + insert_enter(s); + return s->c == Ctrl_O; } /* @@ -7697,12 +7782,8 @@ static void ins_left(bool end_change) if (revins_scol != -1 && (int)curwin->w_cursor.col >= revins_scol) revins_legal++; revins_chars++; - } - /* - * if 'whichwrap' set for cursor in insert mode may go to - * previous line - */ - else if (vim_strchr(p_ww, '[') != NULL && curwin->w_cursor.lnum > 1) { + } else if (vim_strchr(p_ww, '[') != NULL && curwin->w_cursor.lnum > 1) { + // if 'whichwrap' set for cursor in insert mode may go to previous line. // always break undo when moving upwards/downwards, else undo may break start_arrow(&tpos); --(curwin->w_cursor.lnum); diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index a9262ca6ea..e160281145 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -6544,7 +6544,7 @@ do_exedit ( msg_scroll = 0; must_redraw = CLEAR; - main_loop(FALSE, TRUE); + normal_enter(false, true); RedrawingDisabled = rd; no_wait_return = nwr; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 50e9ce7c17..52292128d8 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -58,6 +58,7 @@ #include "nvim/screen.h" #include "nvim/search.h" #include "nvim/strings.h" +#include "nvim/state.h" #include "nvim/syntax.h" #include "nvim/tag.h" #include "nvim/window.h" @@ -91,6 +92,45 @@ struct cmdline_info { int input_fn; /* when TRUE Invoked for input() function */ }; +typedef struct command_line_state { + VimState state; + int firstc; + long count; + int indent; + int c; + int i; + int j; + int gotesc; // TRUE when <ESC> just typed + int do_abbr; // when TRUE check for abbr. + char_u *lookfor; // string to match + int hiscnt; // current history line in use + int histype; // history type to be used + pos_T old_cursor; + colnr_T old_curswant; + colnr_T old_leftcol; + linenr_T old_topline; + int old_topfill; + linenr_T old_botline; + int did_incsearch; + int incsearch_postponed; + int did_wild_list; // did wild_list() recently + int wim_index; // index in wim_flags[] + int res; + int save_msg_scroll; + int save_State; // remember State when called + int some_key_typed; // one of the keys was typed + // mouse drag and release events are ignored, unless they are + // preceded with a mouse down event + int ignore_drag_release; + int break_ctrl_c; + expand_T xpc; + long *b_im_ptr; + // Everything that may work recursively should save and restore the + // current command line in save_ccline. That includes update_screen(), a + // custom status line may invoke ":normal". + struct cmdline_info save_ccline; +} CommandLineState; + /* The current cmdline_info. It is initialized in getcmdline() and after that * used by other functions. When invoking getcmdline() recursively it needs * to be saved with save_cmdline() and restored with restore_cmdline(). @@ -120,1400 +160,1478 @@ static int hislen = 0; /* actual length of history tables */ static int cmd_hkmap = 0; /* Hebrew mapping during command line */ static int cmd_fkmap = 0; /* Farsi mapping during command line */ - -/* - * getcmdline() - accept a command line starting with firstc. - * - * firstc == ':' get ":" command line. - * firstc == '/' or '?' get search pattern - * firstc == '=' get expression - * firstc == '@' get text for input() function - * firstc == '>' get text for debug mode - * firstc == NUL get text for :insert command - * firstc == -1 like NUL, and break on CTRL-C - * - * The line is collected in ccline.cmdbuff, which is reallocated to fit the - * command line. - * - * Careful: getcmdline() can be called recursively! - * - * Return pointer to allocated string if there is a commandline, NULL - * otherwise. - */ -char_u * -getcmdline ( - int firstc, - long count, /* only used for incremental search */ - int indent /* indent for inside conditionals */ -) +static uint8_t *command_line_enter(int firstc, long count, int indent) { - int c; - int i; - int j; - int gotesc = FALSE; /* TRUE when <ESC> just typed */ - int do_abbr; /* when TRUE check for abbr. */ - char_u *lookfor = NULL; /* string to match */ - int hiscnt; /* current history line in use */ - int histype; /* history type to be used */ - pos_T old_cursor; - colnr_T old_curswant; - colnr_T old_leftcol; - linenr_T old_topline; - int old_topfill; - linenr_T old_botline; - int did_incsearch = FALSE; - int incsearch_postponed = FALSE; - int did_wild_list = FALSE; /* did wild_list() recently */ - int wim_index = 0; /* index in wim_flags[] */ - int res; - int save_msg_scroll = msg_scroll; - int save_State = State; /* remember State when called */ - int some_key_typed = FALSE; /* one of the keys was typed */ - /* mouse drag and release events are ignored, unless they are - * preceded with a mouse down event */ - int ignore_drag_release = TRUE; - int break_ctrl_c = FALSE; - expand_T xpc; - long *b_im_ptr = NULL; - /* Everything that may work recursively should save and restore the - * current command line in save_ccline. That includes update_screen(), a - * custom status line may invoke ":normal". */ - struct cmdline_info save_ccline; - - if (firstc == -1) { - firstc = NUL; - break_ctrl_c = TRUE; + CommandLineState state, *s = &state; + memset(s, 0, sizeof(CommandLineState)); + s->firstc = firstc; + s->count = count; + s->indent = indent; + s->save_msg_scroll = msg_scroll; + s->save_State = State; + s->ignore_drag_release = true; + + if (s->firstc == -1) { + s->firstc = NUL; + s->break_ctrl_c = true; } - /* start without Hebrew mapping for a command line */ - if (firstc == ':' || firstc == '=' || firstc == '>') + + // start without Hebrew mapping for a command line + if (s->firstc == ':' || s->firstc == '=' || s->firstc == '>') { cmd_hkmap = 0; + } - ccline.overstrike = FALSE; /* always start in insert mode */ - old_cursor = curwin->w_cursor; /* needs to be restored later */ - old_curswant = curwin->w_curswant; - old_leftcol = curwin->w_leftcol; - old_topline = curwin->w_topline; - old_topfill = curwin->w_topfill; - old_botline = curwin->w_botline; + ccline.overstrike = false; // always start in insert mode + s->old_cursor = curwin->w_cursor; // needs to be restored later + s->old_curswant = curwin->w_curswant; + s->old_leftcol = curwin->w_leftcol; + s->old_topline = curwin->w_topline; + s->old_topfill = curwin->w_topfill; + s->old_botline = curwin->w_botline; - /* - * set some variables for redrawcmd() - */ - ccline.cmdfirstc = (firstc == '@' ? 0 : firstc); - ccline.cmdindent = (firstc > 0 ? indent : 0); + // set some variables for redrawcmd() + ccline.cmdfirstc = (s->firstc == '@' ? 0 : s->firstc); + ccline.cmdindent = (s->firstc > 0 ? s->indent : 0); - /* alloc initial ccline.cmdbuff */ - alloc_cmdbuff(exmode_active ? 250 : indent + 1); + // alloc initial ccline.cmdbuff + alloc_cmdbuff(exmode_active ? 250 : s->indent + 1); ccline.cmdlen = ccline.cmdpos = 0; ccline.cmdbuff[0] = NUL; - /* autoindent for :insert and :append */ - if (firstc <= 0) { - memset(ccline.cmdbuff, ' ', indent); - ccline.cmdbuff[indent] = NUL; - ccline.cmdpos = indent; - ccline.cmdspos = indent; - ccline.cmdlen = indent; + // autoindent for :insert and :append + if (s->firstc <= 0) { + memset(ccline.cmdbuff, ' ', s->indent); + ccline.cmdbuff[s->indent] = NUL; + ccline.cmdpos = s->indent; + ccline.cmdspos = s->indent; + ccline.cmdlen = s->indent; } - ExpandInit(&xpc); - ccline.xpc = &xpc; + ExpandInit(&s->xpc); + ccline.xpc = &s->xpc; if (curwin->w_p_rl && *curwin->w_p_rlc == 's' - && (firstc == '/' || firstc == '?')) - cmdmsg_rl = TRUE; - else - cmdmsg_rl = FALSE; + && (s->firstc == '/' || s->firstc == '?')) { + cmdmsg_rl = true; + } else { + cmdmsg_rl = false; + } - redir_off = TRUE; /* don't redirect the typed command */ + redir_off = true; // don't redirect the typed command if (!cmd_silent) { - i = msg_scrolled; - msg_scrolled = 0; /* avoid wait_return message */ - gotocmdline(TRUE); - msg_scrolled += i; - redrawcmdprompt(); /* draw prompt or indent */ + s->i = msg_scrolled; + msg_scrolled = 0; // avoid wait_return message + gotocmdline(true); + msg_scrolled += s->i; + redrawcmdprompt(); // draw prompt or indent set_cmdspos(); } - xpc.xp_context = EXPAND_NOTHING; - xpc.xp_backslash = XP_BS_NONE; + s->xpc.xp_context = EXPAND_NOTHING; + s->xpc.xp_backslash = XP_BS_NONE; #ifndef BACKSLASH_IN_FILENAME - xpc.xp_shell = FALSE; + s->xpc.xp_shell = false; #endif if (ccline.input_fn) { - xpc.xp_context = ccline.xp_context; - xpc.xp_pattern = ccline.cmdbuff; - xpc.xp_arg = ccline.xp_arg; + s->xpc.xp_context = ccline.xp_context; + s->xpc.xp_pattern = ccline.cmdbuff; + s->xpc.xp_arg = ccline.xp_arg; } - /* - * Avoid scrolling when called by a recursive do_cmdline(), e.g. when - * doing ":@0" when register 0 doesn't contain a CR. - */ - msg_scroll = FALSE; + // Avoid scrolling when called by a recursive do_cmdline(), e.g. when + // doing ":@0" when register 0 doesn't contain a CR. + msg_scroll = false; State = CMDLINE; - if (firstc == '/' || firstc == '?' || firstc == '@') { - /* Use ":lmap" mappings for search pattern and input(). */ - if (curbuf->b_p_imsearch == B_IMODE_USE_INSERT) - b_im_ptr = &curbuf->b_p_iminsert; - else - b_im_ptr = &curbuf->b_p_imsearch; - if (*b_im_ptr == B_IMODE_LMAP) + if (s->firstc == '/' || s->firstc == '?' || s->firstc == '@') { + // Use ":lmap" mappings for search pattern and input(). + if (curbuf->b_p_imsearch == B_IMODE_USE_INSERT) { + s->b_im_ptr = &curbuf->b_p_iminsert; + } else { + s->b_im_ptr = &curbuf->b_p_imsearch; + } + + if (*s->b_im_ptr == B_IMODE_LMAP) { State |= LANGMAP; + } } setmouse(); - ui_cursor_shape(); /* may show different cursor shape */ + ui_cursor_shape(); // may show different cursor shape init_history(); - hiscnt = hislen; /* set hiscnt to impossible history value */ - histype = hist_char2type(firstc); - - do_digraph(-1); /* init digraph typeahead */ + s->hiscnt = hislen; // set hiscnt to impossible history value + s->histype = hist_char2type(s->firstc); + do_digraph(-1); // init digraph typeahead // If something above caused an error, reset the flags, we do want to type // and execute commands. Display may be messed up a bit. if (did_emsg) { redrawcmd(); } - did_emsg = FALSE; - got_int = FALSE; - /* - * Collect the command string, handling editing keys. - */ - for (;; ) { - redir_off = TRUE; /* Don't redirect the typed command. - Repeated, because a ":redir" inside - completion may switch it on. */ - quit_more = FALSE; /* reset after CTRL-D which had a more-prompt */ + did_emsg = false; + got_int = false; + s->state.check = command_line_check; + s->state.execute = command_line_execute; + state_enter(&s->state); - cursorcmd(); /* set the cursor on the right spot */ + cmdmsg_rl = false; - /* Get a character. Ignore K_IGNORE, it should not do anything, such - * as stop completion. */ - input_enable_events(); - do { - c = safe_vgetc(); - } while (c == K_IGNORE || c == K_PASTE); - input_disable_events(); + cmd_fkmap = 0; - if (c == K_EVENT) { - queue_process_events(loop.events); - continue; + ExpandCleanup(&s->xpc); + ccline.xpc = NULL; + + if (s->did_incsearch) { + curwin->w_cursor = s->old_cursor; + curwin->w_curswant = s->old_curswant; + curwin->w_leftcol = s->old_leftcol; + curwin->w_topline = s->old_topline; + curwin->w_topfill = s->old_topfill; + curwin->w_botline = s->old_botline; + highlight_match = false; + validate_cursor(); // needed for TAB + redraw_later(SOME_VALID); + } + + if (ccline.cmdbuff != NULL) { + // Put line in history buffer (":" and "=" only when it was typed). + if (ccline.cmdlen && s->firstc != NUL + && (s->some_key_typed || s->histype == HIST_SEARCH)) { + add_to_history(s->histype, ccline.cmdbuff, true, + s->histype == HIST_SEARCH ? s->firstc : NUL); + if (s->firstc == ':') { + xfree(new_last_cmdline); + new_last_cmdline = vim_strsave(ccline.cmdbuff); + } } - if (KeyTyped) { - some_key_typed = TRUE; - if (cmd_hkmap) - c = hkmap(c); - if (cmd_fkmap) - c = cmdl_fkmap(c); - if (cmdmsg_rl && !KeyStuffed) { - /* Invert horizontal movements and operations. Only when - * typed by the user directly, not when the result of a - * mapping. */ - switch (c) { - case K_RIGHT: c = K_LEFT; break; - case K_S_RIGHT: c = K_S_LEFT; break; - case K_C_RIGHT: c = K_C_LEFT; break; - case K_LEFT: c = K_RIGHT; break; - case K_S_LEFT: c = K_S_RIGHT; break; - case K_C_LEFT: c = K_C_RIGHT; break; - } + if (s->gotesc) { // abandon command line + xfree(ccline.cmdbuff); + ccline.cmdbuff = NULL; + if (msg_scrolled == 0) { + compute_cmdrow(); } + MSG(""); + redraw_cmdline = true; } + } - /* - * Ignore got_int when CTRL-C was typed here. - * Don't ignore it in :global, we really need to break then, e.g., for - * ":g/pat/normal /pat" (without the <CR>). - * Don't ignore it for the input() function. - */ - if ((c == Ctrl_C -#ifdef UNIX - || c == intr_char -#endif - ) - && firstc != '@' - && !break_ctrl_c - && !global_busy) - got_int = FALSE; - - /* free old command line when finished moving around in the history - * list */ - if (lookfor != NULL - && c != K_S_DOWN && c != K_S_UP - && c != K_DOWN && c != K_UP - && c != K_PAGEDOWN && c != K_PAGEUP - && c != K_KPAGEDOWN && c != K_KPAGEUP - && c != K_LEFT && c != K_RIGHT - && (xpc.xp_numfiles > 0 || (c != Ctrl_P && c != Ctrl_N))) { - xfree(lookfor); - lookfor = NULL; + // If the screen was shifted up, redraw the whole screen (later). + // If the line is too long, clear it, so ruler and shown command do + // not get printed in the middle of it. + msg_check(); + msg_scroll = s->save_msg_scroll; + redir_off = false; + + // When the command line was typed, no need for a wait-return prompt. + if (s->some_key_typed) { + need_wait_return = false; + } + + State = s->save_State; + setmouse(); + ui_cursor_shape(); // may show different cursor shape + + { + char_u *p = ccline.cmdbuff; + + // Make ccline empty, getcmdline() may try to use it. + ccline.cmdbuff = NULL; + return p; + } +} + +static int command_line_check(VimState *state) +{ + redir_off = true; // Don't redirect the typed command. + // Repeated, because a ":redir" inside + // completion may switch it on. + quit_more = false; // reset after CTRL-D which had a more-prompt + + cursorcmd(); // set the cursor on the right spot + return 1; +} + +static int command_line_execute(VimState *state, int key) +{ + if (key == K_IGNORE || key == K_PASTE) { + return -1; // get another key + } + + CommandLineState *s = (CommandLineState *)state; + s->c = key; + + if (s->c == K_EVENT) { + queue_process_events(loop.events); + return 1; + } + + if (KeyTyped) { + s->some_key_typed = true; + if (cmd_hkmap) { + s->c = hkmap(s->c); } - /* - * When there are matching completions to select <S-Tab> works like - * CTRL-P (unless 'wc' is <S-Tab>). - */ - if (c != p_wc && c == K_S_TAB && xpc.xp_numfiles > 0) - c = Ctrl_P; - - /* Special translations for 'wildmenu' */ - if (did_wild_list && p_wmnu) { - if (c == K_LEFT) - c = Ctrl_P; - else if (c == K_RIGHT) - c = Ctrl_N; + if (cmd_fkmap) { + s->c = cmdl_fkmap(s->c); } - /* Hitting CR after "emenu Name.": complete submenu */ - if (xpc.xp_context == EXPAND_MENUNAMES && p_wmnu - && ccline.cmdpos > 1 - && ccline.cmdbuff[ccline.cmdpos - 1] == '.' - && ccline.cmdbuff[ccline.cmdpos - 2] != '\\' - && (c == '\n' || c == '\r' || c == K_KENTER)) - c = K_DOWN; - - /* free expanded names when finished walking through matches */ - if (xpc.xp_numfiles != -1 - && !(c == p_wc && KeyTyped) && c != p_wcm - && c != Ctrl_N && c != Ctrl_P && c != Ctrl_A - && c != Ctrl_L) { - (void)ExpandOne(&xpc, NULL, NULL, 0, WILD_FREE); - did_wild_list = FALSE; - if (!p_wmnu || (c != K_UP && c != K_DOWN)) - xpc.xp_context = EXPAND_NOTHING; - wim_index = 0; - if (p_wmnu && wild_menu_showing != 0) { - int skt = KeyTyped; - int old_RedrawingDisabled = RedrawingDisabled; - - if (ccline.input_fn) - RedrawingDisabled = 0; - - if (wild_menu_showing == WM_SCROLLED) { - /* Entered command line, move it up */ - cmdline_row--; - redrawcmd(); - } else if (save_p_ls != -1) { - /* restore 'laststatus' and 'winminheight' */ - p_ls = save_p_ls; - p_wmh = save_p_wmh; - last_status(FALSE); - save_cmdline(&save_ccline); - update_screen(VALID); /* redraw the screen NOW */ - restore_cmdline(&save_ccline); - redrawcmd(); - save_p_ls = -1; - } else { - win_redraw_last_status(topframe); - redraw_statuslines(); - } - KeyTyped = skt; - wild_menu_showing = 0; - if (ccline.input_fn) - RedrawingDisabled = old_RedrawingDisabled; + + if (cmdmsg_rl && !KeyStuffed) { + // Invert horizontal movements and operations. Only when + // typed by the user directly, not when the result of a + // mapping. + switch (s->c) { + case K_RIGHT: s->c = K_LEFT; break; + case K_S_RIGHT: s->c = K_S_LEFT; break; + case K_C_RIGHT: s->c = K_C_LEFT; break; + case K_LEFT: s->c = K_RIGHT; break; + case K_S_LEFT: s->c = K_S_RIGHT; break; + case K_C_LEFT: s->c = K_C_RIGHT; break; } } + } - /* Special translations for 'wildmenu' */ - if (xpc.xp_context == EXPAND_MENUNAMES && p_wmnu) { - /* Hitting <Down> after "emenu Name.": complete submenu */ - if (c == K_DOWN && ccline.cmdpos > 0 - && ccline.cmdbuff[ccline.cmdpos - 1] == '.') - c = p_wc; - else if (c == K_UP) { - /* Hitting <Up>: Remove one submenu name in front of the - * cursor */ - int found = FALSE; - - j = (int)(xpc.xp_pattern - ccline.cmdbuff); - i = 0; - while (--j > 0) { - /* check for start of menu name */ - if (ccline.cmdbuff[j] == ' ' - && ccline.cmdbuff[j - 1] != '\\') { - i = j + 1; + // Ignore got_int when CTRL-C was typed here. + // Don't ignore it in :global, we really need to break then, e.g., for + // ":g/pat/normal /pat" (without the <CR>). + // Don't ignore it for the input() function. + if ((s->c == Ctrl_C) + && s->firstc != '@' + && !s->break_ctrl_c + && !global_busy) { + got_int = false; + } + + // free old command line when finished moving around in the history + // list + if (s->lookfor != NULL + && s->c != K_S_DOWN && s->c != K_S_UP + && s->c != K_DOWN && s->c != K_UP + && s->c != K_PAGEDOWN && s->c != K_PAGEUP + && s->c != K_KPAGEDOWN && s->c != K_KPAGEUP + && s->c != K_LEFT && s->c != K_RIGHT + && (s->xpc.xp_numfiles > 0 || (s->c != Ctrl_P && s->c != Ctrl_N))) { + xfree(s->lookfor); + s->lookfor = NULL; + } + + // When there are matching completions to select <S-Tab> works like + // CTRL-P (unless 'wc' is <S-Tab>). + if (s->c != p_wc && s->c == K_S_TAB && s->xpc.xp_numfiles > 0) { + s->c = Ctrl_P; + } + + // Special translations for 'wildmenu' + if (s->did_wild_list && p_wmnu) { + if (s->c == K_LEFT) { + s->c = Ctrl_P; + } else if (s->c == K_RIGHT) { + s->c = Ctrl_N; + } + } + + // Hitting CR after "emenu Name.": complete submenu + if (s->xpc.xp_context == EXPAND_MENUNAMES && p_wmnu + && ccline.cmdpos > 1 + && ccline.cmdbuff[ccline.cmdpos - 1] == '.' + && ccline.cmdbuff[ccline.cmdpos - 2] != '\\' + && (s->c == '\n' || s->c == '\r' || s->c == K_KENTER)) { + s->c = K_DOWN; + } + + // free expanded names when finished walking through matches + if (s->xpc.xp_numfiles != -1 + && !(s->c == p_wc && KeyTyped) && s->c != p_wcm + && s->c != Ctrl_N && s->c != Ctrl_P && s->c != Ctrl_A + && s->c != Ctrl_L) { + (void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE); + s->did_wild_list = false; + if (!p_wmnu || (s->c != K_UP && s->c != K_DOWN)) { + s->xpc.xp_context = EXPAND_NOTHING; + } + s->wim_index = 0; + if (p_wmnu && wild_menu_showing != 0) { + int skt = KeyTyped; + int old_RedrawingDisabled = RedrawingDisabled; + + if (ccline.input_fn) { + RedrawingDisabled = 0; + } + + if (wild_menu_showing == WM_SCROLLED) { + // Entered command line, move it up + cmdline_row--; + redrawcmd(); + } else if (save_p_ls != -1) { + // restore 'laststatus' and 'winminheight' + p_ls = save_p_ls; + p_wmh = save_p_wmh; + last_status(false); + save_cmdline(&s->save_ccline); + update_screen(VALID); // redraw the screen NOW + restore_cmdline(&s->save_ccline); + redrawcmd(); + save_p_ls = -1; + } else { + win_redraw_last_status(topframe); + redraw_statuslines(); + } + KeyTyped = skt; + wild_menu_showing = 0; + if (ccline.input_fn) { + RedrawingDisabled = old_RedrawingDisabled; + } + } + } + + // Special translations for 'wildmenu' + if (s->xpc.xp_context == EXPAND_MENUNAMES && p_wmnu) { + // Hitting <Down> after "emenu Name.": complete submenu + if (s->c == K_DOWN && ccline.cmdpos > 0 + && ccline.cmdbuff[ccline.cmdpos - 1] == '.') { + s->c = p_wc; + } else if (s->c == K_UP) { + // Hitting <Up>: Remove one submenu name in front of the + // cursor + int found = false; + + s->j = (int)(s->xpc.xp_pattern - ccline.cmdbuff); + s->i = 0; + while (--s->j > 0) { + // check for start of menu name + if (ccline.cmdbuff[s->j] == ' ' + && ccline.cmdbuff[s->j - 1] != '\\') { + s->i = s->j + 1; + break; + } + + // check for start of submenu name + if (ccline.cmdbuff[s->j] == '.' + && ccline.cmdbuff[s->j - 1] != '\\') { + if (found) { + s->i = s->j + 1; break; - } - /* check for start of submenu name */ - if (ccline.cmdbuff[j] == '.' - && ccline.cmdbuff[j - 1] != '\\') { - if (found) { - i = j + 1; - break; - } else - found = TRUE; + } else { + found = true; } } - if (i > 0) - cmdline_del(i); - c = p_wc; - xpc.xp_context = EXPAND_NOTHING; } + if (s->i > 0) { + cmdline_del(s->i); + } + s->c = p_wc; + s->xpc.xp_context = EXPAND_NOTHING; } - if ((xpc.xp_context == EXPAND_FILES - || xpc.xp_context == EXPAND_DIRECTORIES - || xpc.xp_context == EXPAND_SHELLCMD) && p_wmnu) { - char_u upseg[5]; - - upseg[0] = PATHSEP; - upseg[1] = '.'; - upseg[2] = '.'; - upseg[3] = PATHSEP; - upseg[4] = NUL; - - if (c == K_DOWN - && ccline.cmdpos > 0 - && ccline.cmdbuff[ccline.cmdpos - 1] == PATHSEP - && (ccline.cmdpos < 3 - || ccline.cmdbuff[ccline.cmdpos - 2] != '.' - || ccline.cmdbuff[ccline.cmdpos - 3] != '.')) { - /* go down a directory */ - c = p_wc; - } else if (STRNCMP(xpc.xp_pattern, upseg + 1, 3) == 0 && c == K_DOWN) { - /* If in a direct ancestor, strip off one ../ to go down */ - int found = FALSE; - - j = ccline.cmdpos; - i = (int)(xpc.xp_pattern - ccline.cmdbuff); - while (--j > i) { - if (has_mbyte) - j -= (*mb_head_off)(ccline.cmdbuff, ccline.cmdbuff + j); - if (vim_ispathsep(ccline.cmdbuff[j])) { - found = TRUE; - break; - } + } + if ((s->xpc.xp_context == EXPAND_FILES + || s->xpc.xp_context == EXPAND_DIRECTORIES + || s->xpc.xp_context == EXPAND_SHELLCMD) && p_wmnu) { + char_u upseg[5]; + + upseg[0] = PATHSEP; + upseg[1] = '.'; + upseg[2] = '.'; + upseg[3] = PATHSEP; + upseg[4] = NUL; + + if (s->c == K_DOWN + && ccline.cmdpos > 0 + && ccline.cmdbuff[ccline.cmdpos - 1] == PATHSEP + && (ccline.cmdpos < 3 + || ccline.cmdbuff[ccline.cmdpos - 2] != '.' + || ccline.cmdbuff[ccline.cmdpos - 3] != '.')) { + // go down a directory + s->c = p_wc; + } else if (STRNCMP(s->xpc.xp_pattern, upseg + 1, 3) == 0 + && s->c == K_DOWN) { + // If in a direct ancestor, strip off one ../ to go down + int found = false; + + s->j = ccline.cmdpos; + s->i = (int)(s->xpc.xp_pattern - ccline.cmdbuff); + while (--s->j > s->i) { + if (has_mbyte) { + s->j -= (*mb_head_off)(ccline.cmdbuff, ccline.cmdbuff + s->j); } - if (found - && ccline.cmdbuff[j - 1] == '.' - && ccline.cmdbuff[j - 2] == '.' - && (vim_ispathsep(ccline.cmdbuff[j - 3]) || j == i + 2)) { - cmdline_del(j - 2); - c = p_wc; + if (vim_ispathsep(ccline.cmdbuff[s->j])) { + found = true; + break; } - } else if (c == K_UP) { - /* go up a directory */ - int found = FALSE; - - j = ccline.cmdpos - 1; - i = (int)(xpc.xp_pattern - ccline.cmdbuff); - while (--j > i) { - if (has_mbyte) - j -= (*mb_head_off)(ccline.cmdbuff, ccline.cmdbuff + j); - if (vim_ispathsep(ccline.cmdbuff[j]) + } + if (found + && ccline.cmdbuff[s->j - 1] == '.' + && ccline.cmdbuff[s->j - 2] == '.' + && (vim_ispathsep(ccline.cmdbuff[s->j - 3]) || s->j == s->i + 2)) { + cmdline_del(s->j - 2); + s->c = p_wc; + } + } else if (s->c == K_UP) { + // go up a directory + int found = false; + + s->j = ccline.cmdpos - 1; + s->i = (int)(s->xpc.xp_pattern - ccline.cmdbuff); + while (--s->j > s->i) { + if (has_mbyte) { + s->j -= (*mb_head_off)(ccline.cmdbuff, ccline.cmdbuff + s->j); + } + if (vim_ispathsep(ccline.cmdbuff[s->j]) #ifdef BACKSLASH_IN_FILENAME - && vim_strchr(" *?[{`$%#", ccline.cmdbuff[j + 1]) - == NULL + && vim_strchr(" *?[{`$%#", ccline.cmdbuff[j + 1]) + == NULL #endif - ) { - if (found) { - i = j + 1; - break; - } else - found = TRUE; + ) { + if (found) { + s->i = s->j + 1; + break; + } else { + found = true; } } + } - if (!found) - j = i; - else if (STRNCMP(ccline.cmdbuff + j, upseg, 4) == 0) - j += 4; - else if (STRNCMP(ccline.cmdbuff + j, upseg + 1, 3) == 0 - && j == i) - j += 3; - else - j = 0; - if (j > 0) { - /* TODO this is only for DOS/UNIX systems - need to put in - * machine-specific stuff here and in upseg init */ - cmdline_del(j); - put_on_cmdline(upseg + 1, 3, FALSE); - } else if (ccline.cmdpos > i) - cmdline_del(i); - - /* Now complete in the new directory. Set KeyTyped in case the - * Up key came from a mapping. */ - c = p_wc; - KeyTyped = TRUE; + if (!found) { + s->j = s->i; + } else if (STRNCMP(ccline.cmdbuff + s->j, upseg, 4) == 0) { + s->j += 4; + } else if (STRNCMP(ccline.cmdbuff + s->j, upseg + 1, 3) == 0 + && s->j == s->i) { + s->j += 3; + } else { + s->j = 0; } + + if (s->j > 0) { + // TODO(tarruda): this is only for DOS/UNIX systems - need to put in + // machine-specific stuff here and in upseg init + cmdline_del(s->j); + put_on_cmdline(upseg + 1, 3, false); + } else if (ccline.cmdpos > s->i) { + cmdline_del(s->i); + } + + // Now complete in the new directory. Set KeyTyped in case the + // Up key came from a mapping. + s->c = p_wc; + KeyTyped = true; } + } + // CTRL-\ CTRL-N goes to Normal mode, CTRL-\ CTRL-G goes to Insert + // mode when 'insertmode' is set, CTRL-\ e prompts for an expression. + if (s->c == Ctrl_BSL) { + ++no_mapping; + ++allow_keys; + s->c = plain_vgetc(); + --no_mapping; + --allow_keys; + // CTRL-\ e doesn't work when obtaining an expression, unless it + // is in a mapping. + if (s->c != Ctrl_N && s->c != Ctrl_G && (s->c != 'e' + || (ccline.cmdfirstc == '=' && + KeyTyped))) { + vungetc(s->c); + s->c = Ctrl_BSL; + } else if (s->c == 'e') { + char_u *p = NULL; + int len; - /* CTRL-\ CTRL-N goes to Normal mode, CTRL-\ CTRL-G goes to Insert - * mode when 'insertmode' is set, CTRL-\ e prompts for an expression. */ - if (c == Ctrl_BSL) { - ++no_mapping; - ++allow_keys; - c = plain_vgetc(); - --no_mapping; - --allow_keys; - /* CTRL-\ e doesn't work when obtaining an expression, unless it - * is in a mapping. */ - if (c != Ctrl_N && c != Ctrl_G && (c != 'e' - || (ccline.cmdfirstc == '=' && - KeyTyped))) { - vungetc(c); - c = Ctrl_BSL; - } else if (c == 'e') { - char_u *p = NULL; - int len; - - /* - * Replace the command line with the result of an expression. - * Need to save and restore the current command line, to be - * able to enter a new one... - */ - if (ccline.cmdpos == ccline.cmdlen) - new_cmdpos = 99999; /* keep it at the end */ - else - new_cmdpos = ccline.cmdpos; - - save_cmdline(&save_ccline); - c = get_expr_register(); - restore_cmdline(&save_ccline); - if (c == '=') { - /* Need to save and restore ccline. And set "textlock" - * to avoid nasty things like going to another buffer when - * evaluating an expression. */ - save_cmdline(&save_ccline); - ++textlock; - p = get_expr_line(); - --textlock; - restore_cmdline(&save_ccline); - - if (p != NULL) { - len = (int)STRLEN(p); - realloc_cmdbuff(len + 1); - ccline.cmdlen = len; - STRCPY(ccline.cmdbuff, p); - xfree(p); - - /* Restore the cursor or use the position set with - * set_cmdline_pos(). */ - if (new_cmdpos > ccline.cmdlen) - ccline.cmdpos = ccline.cmdlen; - else - ccline.cmdpos = new_cmdpos; - - KeyTyped = FALSE; /* Don't do p_wc completion. */ - redrawcmd(); - goto cmdline_changed; + // Replace the command line with the result of an expression. + // Need to save and restore the current command line, to be + // able to enter a new one... + if (ccline.cmdpos == ccline.cmdlen) { + new_cmdpos = 99999; // keep it at the end + } else { + new_cmdpos = ccline.cmdpos; + } + + save_cmdline(&s->save_ccline); + s->c = get_expr_register(); + restore_cmdline(&s->save_ccline); + if (s->c == '=') { + // Need to save and restore ccline. And set "textlock" + // to avoid nasty things like going to another buffer when + // evaluating an expression. + save_cmdline(&s->save_ccline); + ++textlock; + p = get_expr_line(); + --textlock; + restore_cmdline(&s->save_ccline); + + if (p != NULL) { + len = (int)STRLEN(p); + realloc_cmdbuff(len + 1); + ccline.cmdlen = len; + STRCPY(ccline.cmdbuff, p); + xfree(p); + + // Restore the cursor or use the position set with + // set_cmdline_pos(). + if (new_cmdpos > ccline.cmdlen) { + ccline.cmdpos = ccline.cmdlen; + } else { + ccline.cmdpos = new_cmdpos; } + + KeyTyped = false; // Don't do p_wc completion. + redrawcmd(); + return command_line_changed(s); } - beep_flush(); - got_int = FALSE; /* don't abandon the command line */ - did_emsg = FALSE; - emsg_on_display = FALSE; - redrawcmd(); - goto cmdline_not_changed; - } else { - if (c == Ctrl_G && p_im && restart_edit == 0) - restart_edit = 'a'; - gotesc = TRUE; /* will free ccline.cmdbuff after putting it - in history */ - goto returncmd; /* back to Normal mode */ } + beep_flush(); + got_int = false; // don't abandon the command line + did_emsg = false; + emsg_on_display = false; + redrawcmd(); + return command_line_not_changed(s); + } else { + if (s->c == Ctrl_G && p_im && restart_edit == 0) { + restart_edit = 'a'; + } + s->gotesc = true; // will free ccline.cmdbuff after putting it + // in history + return 0; // back to Normal mode } + } - if (c == cedit_key || c == K_CMDWIN) { - if (ex_normal_busy == 0 && got_int == FALSE) { - /* - * Open a window to edit the command line (and history). - */ - c = ex_window(); - some_key_typed = TRUE; + if (s->c == cedit_key || s->c == K_CMDWIN) { + if (ex_normal_busy == 0 && got_int == false) { + // Open a window to edit the command line (and history). + s->c = ex_window(); + s->some_key_typed = true; + } + } else { + s->c = do_digraph(s->c); + } + + if (s->c == '\n' + || s->c == '\r' + || s->c == K_KENTER + || (s->c == ESC + && (!KeyTyped || vim_strchr(p_cpo, CPO_ESC) != NULL))) { + // In Ex mode a backslash escapes a newline. + if (exmode_active + && s->c != ESC + && ccline.cmdpos == ccline.cmdlen + && ccline.cmdpos > 0 + && ccline.cmdbuff[ccline.cmdpos - 1] == '\\') { + if (s->c == K_KENTER) { + s->c = '\n'; } - } else - c = do_digraph(c); - - if (c == '\n' || c == '\r' || c == K_KENTER || (c == ESC - && (!KeyTyped || - vim_strchr(p_cpo, - CPO_ESC) != - NULL))) { - /* In Ex mode a backslash escapes a newline. */ - if (exmode_active - && c != ESC - && ccline.cmdpos == ccline.cmdlen - && ccline.cmdpos > 0 - && ccline.cmdbuff[ccline.cmdpos - 1] == '\\') { - if (c == K_KENTER) - c = '\n'; - } else { - gotesc = FALSE; /* Might have typed ESC previously, don't - truncate the cmdline now. */ - if (ccheck_abbr(c + ABBR_OFF)) - goto cmdline_changed; - if (!cmd_silent) { - ui_cursor_goto(msg_row, 0); - ui_flush(); - } - break; + } else { + s->gotesc = false; // Might have typed ESC previously, don't + // truncate the cmdline now. + if (ccheck_abbr(s->c + ABBR_OFF)) { + return command_line_changed(s); } + + if (!cmd_silent) { + ui_cursor_goto(msg_row, 0); + ui_flush(); + } + return 0; } + } - /* - * Completion for 'wildchar' or 'wildcharm' key. - * - hitting <ESC> twice means: abandon command line. - * - wildcard expansion is only done when the 'wildchar' key is really - * typed, not when it comes from a macro - */ - if ((c == p_wc && !gotesc && KeyTyped) || c == p_wcm) { - if (xpc.xp_numfiles > 0) { /* typed p_wc at least twice */ - /* if 'wildmode' contains "list" may still need to list */ - if (xpc.xp_numfiles > 1 - && !did_wild_list - && (wim_flags[wim_index] & WIM_LIST)) { - (void)showmatches(&xpc, FALSE); - redrawcmd(); - did_wild_list = TRUE; - } - if (wim_flags[wim_index] & WIM_LONGEST) - res = nextwild(&xpc, WILD_LONGEST, WILD_NO_BEEP, - firstc != '@'); - else if (wim_flags[wim_index] & WIM_FULL) - res = nextwild(&xpc, WILD_NEXT, WILD_NO_BEEP, - firstc != '@'); - else - res = OK; /* don't insert 'wildchar' now */ - } else { /* typed p_wc first time */ - wim_index = 0; - j = ccline.cmdpos; - /* if 'wildmode' first contains "longest", get longest - * common part */ - if (wim_flags[0] & WIM_LONGEST) - res = nextwild(&xpc, WILD_LONGEST, WILD_NO_BEEP, - firstc != '@'); - else - res = nextwild(&xpc, WILD_EXPAND_KEEP, WILD_NO_BEEP, - firstc != '@'); - - /* if interrupted while completing, behave like it failed */ - if (got_int) { - (void)vpeekc(); /* remove <C-C> from input stream */ - got_int = FALSE; /* don't abandon the command line */ - (void)ExpandOne(&xpc, NULL, NULL, 0, WILD_FREE); - xpc.xp_context = EXPAND_NOTHING; - goto cmdline_changed; + // Completion for 'wildchar' or 'wildcharm' key. + // - hitting <ESC> twice means: abandon command line. + // - wildcard expansion is only done when the 'wildchar' key is really + // typed, not when it comes from a macro + if ((s->c == p_wc && !s->gotesc && KeyTyped) || s->c == p_wcm) { + if (s->xpc.xp_numfiles > 0) { // typed p_wc at least twice + // if 'wildmode' contains "list" may still need to list + if (s->xpc.xp_numfiles > 1 + && !s->did_wild_list + && (wim_flags[s->wim_index] & WIM_LIST)) { + (void)showmatches(&s->xpc, false); + redrawcmd(); + s->did_wild_list = true; + } + + if (wim_flags[s->wim_index] & WIM_LONGEST) { + s->res = nextwild(&s->xpc, WILD_LONGEST, WILD_NO_BEEP, + s->firstc != '@'); + } else if (wim_flags[s->wim_index] & WIM_FULL) { + s->res = nextwild(&s->xpc, WILD_NEXT, WILD_NO_BEEP, + s->firstc != '@'); + } else { + s->res = OK; // don't insert 'wildchar' now + } + } else { // typed p_wc first time + s->wim_index = 0; + s->j = ccline.cmdpos; + + // if 'wildmode' first contains "longest", get longest + // common part + if (wim_flags[0] & WIM_LONGEST) { + s->res = nextwild(&s->xpc, WILD_LONGEST, WILD_NO_BEEP, + s->firstc != '@'); + } else { + s->res = nextwild(&s->xpc, WILD_EXPAND_KEEP, WILD_NO_BEEP, + s->firstc != '@'); + } + + // if interrupted while completing, behave like it failed + if (got_int) { + (void)vpeekc(); // remove <C-C> from input stream + got_int = false; // don't abandon the command line + (void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE); + s->xpc.xp_context = EXPAND_NOTHING; + return command_line_changed(s); + } + + // when more than one match, and 'wildmode' first contains + // "list", or no change and 'wildmode' contains "longest,list", + // list all matches + if (s->res == OK && s->xpc.xp_numfiles > 1) { + // a "longest" that didn't do anything is skipped (but not + // "list:longest") + if (wim_flags[0] == WIM_LONGEST && ccline.cmdpos == s->j) { + s->wim_index = 1; } + if ((wim_flags[s->wim_index] & WIM_LIST) + || (p_wmnu && (wim_flags[s->wim_index] & WIM_FULL) != 0)) { + if (!(wim_flags[0] & WIM_LONGEST)) { + int p_wmnu_save = p_wmnu; + p_wmnu = 0; + // remove match + nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@'); + p_wmnu = p_wmnu_save; + } - /* when more than one match, and 'wildmode' first contains - * "list", or no change and 'wildmode' contains "longest,list", - * list all matches */ - if (res == OK && xpc.xp_numfiles > 1) { - /* a "longest" that didn't do anything is skipped (but not - * "list:longest") */ - if (wim_flags[0] == WIM_LONGEST && ccline.cmdpos == j) - wim_index = 1; - if ((wim_flags[wim_index] & WIM_LIST) - || (p_wmnu && (wim_flags[wim_index] & WIM_FULL) != 0) - ) { - if (!(wim_flags[0] & WIM_LONGEST)) { - int p_wmnu_save = p_wmnu; - p_wmnu = 0; - /* remove match */ - nextwild(&xpc, WILD_PREV, 0, firstc != '@'); - p_wmnu = p_wmnu_save; - } - (void)showmatches(&xpc, p_wmnu - && ((wim_flags[wim_index] & WIM_LIST) == 0)); - redrawcmd(); - did_wild_list = TRUE; - if (wim_flags[wim_index] & WIM_LONGEST) - nextwild(&xpc, WILD_LONGEST, WILD_NO_BEEP, - firstc != '@'); - else if (wim_flags[wim_index] & WIM_FULL) - nextwild(&xpc, WILD_NEXT, WILD_NO_BEEP, - firstc != '@'); - } else { - vim_beep(BO_WILD); + (void)showmatches(&s->xpc, p_wmnu + && ((wim_flags[s->wim_index] & WIM_LIST) == 0)); + redrawcmd(); + s->did_wild_list = true; + + if (wim_flags[s->wim_index] & WIM_LONGEST) { + nextwild(&s->xpc, WILD_LONGEST, WILD_NO_BEEP, + s->firstc != '@'); + } else if (wim_flags[s->wim_index] & WIM_FULL) { + nextwild(&s->xpc, WILD_NEXT, WILD_NO_BEEP, + s->firstc != '@'); } - } else if (xpc.xp_numfiles == -1) { - xpc.xp_context = EXPAND_NOTHING; + } else { + vim_beep(BO_WILD); } + } else if (s->xpc.xp_numfiles == -1) { + s->xpc.xp_context = EXPAND_NOTHING; } - if (wim_index < 3) - ++wim_index; - if (c == ESC) - gotesc = TRUE; - if (res == OK) - goto cmdline_changed; } - gotesc = FALSE; + if (s->wim_index < 3) { + ++s->wim_index; + } - /* <S-Tab> goes to last match, in a clumsy way */ - if (c == K_S_TAB && KeyTyped) { - if (nextwild(&xpc, WILD_EXPAND_KEEP, 0, firstc != '@') == OK - && nextwild(&xpc, WILD_PREV, 0, firstc != '@') == OK - && nextwild(&xpc, WILD_PREV, 0, firstc != '@') == OK) - goto cmdline_changed; + if (s->c == ESC) { + s->gotesc = true; } - if (c == NUL || c == K_ZERO) /* NUL is stored as NL */ - c = NL; + if (s->res == OK) { + return command_line_changed(s); + } + } - do_abbr = TRUE; /* default: check for abbreviation */ + s->gotesc = false; - /* - * Big switch for a typed command line character. - */ - switch (c) { - case K_BS: - case Ctrl_H: - case K_DEL: - case K_KDEL: - case Ctrl_W: - if (cmd_fkmap && c == K_BS) - c = K_DEL; - if (c == K_KDEL) - c = K_DEL; + // <S-Tab> goes to last match, in a clumsy way + if (s->c == K_S_TAB && KeyTyped) { + if (nextwild(&s->xpc, WILD_EXPAND_KEEP, 0, s->firstc != '@') == OK + && nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@') == OK + && nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@') == OK) { + return command_line_changed(s); + } + } - /* - * delete current character is the same as backspace on next - * character, except at end of line - */ - if (c == K_DEL && ccline.cmdpos != ccline.cmdlen) - ++ccline.cmdpos; - if (has_mbyte && c == K_DEL) - ccline.cmdpos += mb_off_next(ccline.cmdbuff, - ccline.cmdbuff + ccline.cmdpos); - if (ccline.cmdpos > 0) { - char_u *p; - - j = ccline.cmdpos; - p = ccline.cmdbuff + j; - if (has_mbyte) { - p = mb_prevptr(ccline.cmdbuff, p); - if (c == Ctrl_W) { - while (p > ccline.cmdbuff && ascii_isspace(*p)) - p = mb_prevptr(ccline.cmdbuff, p); - i = mb_get_class(p); - while (p > ccline.cmdbuff && mb_get_class(p) == i) - p = mb_prevptr(ccline.cmdbuff, p); - if (mb_get_class(p) != i) - p += (*mb_ptr2len)(p); + if (s->c == NUL || s->c == K_ZERO) { + // NUL is stored as NL + s->c = NL; + } + + s->do_abbr = true; // default: check for abbreviation + return command_line_handle_key(s); +} + +static int command_line_handle_key(CommandLineState *s) +{ + // Big switch for a typed command line character. + switch (s->c) { + case K_BS: + case Ctrl_H: + case K_DEL: + case K_KDEL: + case Ctrl_W: + if (cmd_fkmap && s->c == K_BS) { + s->c = K_DEL; + } + + if (s->c == K_KDEL) { + s->c = K_DEL; + } + + // delete current character is the same as backspace on next + // character, except at end of line + if (s->c == K_DEL && ccline.cmdpos != ccline.cmdlen) { + ++ccline.cmdpos; + } + + if (has_mbyte && s->c == K_DEL) { + ccline.cmdpos += mb_off_next(ccline.cmdbuff, + ccline.cmdbuff + ccline.cmdpos); + } + + if (ccline.cmdpos > 0) { + char_u *p; + + s->j = ccline.cmdpos; + p = ccline.cmdbuff + s->j; + if (has_mbyte) { + p = mb_prevptr(ccline.cmdbuff, p); + + if (s->c == Ctrl_W) { + while (p > ccline.cmdbuff && ascii_isspace(*p)) { + p = mb_prevptr(ccline.cmdbuff, p); } - } else if (c == Ctrl_W) { - while (p > ccline.cmdbuff && ascii_isspace(p[-1])) - --p; - i = vim_iswordc(p[-1]); - while (p > ccline.cmdbuff && !ascii_isspace(p[-1]) - && vim_iswordc(p[-1]) == i) - --p; - } else + + s->i = mb_get_class(p); + while (p > ccline.cmdbuff && mb_get_class(p) == s->i) + p = mb_prevptr(ccline.cmdbuff, p); + + if (mb_get_class(p) != s->i) { + p += (*mb_ptr2len)(p); + } + } + } else if (s->c == Ctrl_W) { + while (p > ccline.cmdbuff && ascii_isspace(p[-1])) { --p; - ccline.cmdpos = (int)(p - ccline.cmdbuff); - ccline.cmdlen -= j - ccline.cmdpos; - i = ccline.cmdpos; - while (i < ccline.cmdlen) - ccline.cmdbuff[i++] = ccline.cmdbuff[j++]; - - /* Truncate at the end, required for multi-byte chars. */ - ccline.cmdbuff[ccline.cmdlen] = NUL; - redrawcmd(); - } else if (ccline.cmdlen == 0 && c != Ctrl_W - && ccline.cmdprompt == NULL && indent == 0) { - /* In ex and debug mode it doesn't make sense to return. */ - if (exmode_active - || ccline.cmdfirstc == '>' - ) - goto cmdline_not_changed; - - xfree(ccline.cmdbuff); /* no commandline to return */ - ccline.cmdbuff = NULL; - if (!cmd_silent) { - if (cmdmsg_rl) - msg_col = Columns; - else - msg_col = 0; - msg_putchar(' '); /* delete ':' */ } - redraw_cmdline = TRUE; - goto returncmd; /* back to cmd mode */ - } - goto cmdline_changed; - case K_INS: - case K_KINS: - /* if Farsi mode set, we are in reverse insert mode - - Do not change the mode */ - if (cmd_fkmap) - beep_flush(); - else - ccline.overstrike = !ccline.overstrike; - - ui_cursor_shape(); /* may show different cursor shape */ - goto cmdline_not_changed; - - case Ctrl_HAT: - if (map_to_exists_mode((char_u *)"", LANGMAP, FALSE)) { - /* ":lmap" mappings exists, toggle use of mappings. */ - State ^= LANGMAP; - if (b_im_ptr != NULL) { - if (State & LANGMAP) - *b_im_ptr = B_IMODE_LMAP; - else - *b_im_ptr = B_IMODE_NONE; - } + s->i = vim_iswordc(p[-1]); + while (p > ccline.cmdbuff && !ascii_isspace(p[-1]) + && vim_iswordc(p[-1]) == s->i) + --p; + } else { + --p; } - if (b_im_ptr != NULL) { - if (b_im_ptr == &curbuf->b_p_iminsert) - set_iminsert_global(); - else - set_imsearch_global(); + + ccline.cmdpos = (int)(p - ccline.cmdbuff); + ccline.cmdlen -= s->j - ccline.cmdpos; + s->i = ccline.cmdpos; + + while (s->i < ccline.cmdlen) { + ccline.cmdbuff[s->i++] = ccline.cmdbuff[s->j++]; } - ui_cursor_shape(); /* may show different cursor shape */ - /* Show/unshow value of 'keymap' in status lines later. */ - status_redraw_curbuf(); - goto cmdline_not_changed; - - /* case '@': only in very old vi */ - case Ctrl_U: - /* delete all characters left of the cursor */ - j = ccline.cmdpos; - ccline.cmdlen -= j; - i = ccline.cmdpos = 0; - while (i < ccline.cmdlen) - ccline.cmdbuff[i++] = ccline.cmdbuff[j++]; - /* Truncate at the end, required for multi-byte chars. */ + + // Truncate at the end, required for multi-byte chars. ccline.cmdbuff[ccline.cmdlen] = NUL; redrawcmd(); - goto cmdline_changed; - - - case ESC: /* get here if p_wc != ESC or when ESC typed twice */ - case Ctrl_C: - /* In exmode it doesn't make sense to return. Except when - * ":normal" runs out of characters. */ - if (exmode_active - && (ex_normal_busy == 0 || typebuf.tb_len > 0) - ) - goto cmdline_not_changed; - - gotesc = TRUE; /* will free ccline.cmdbuff after - putting it in history */ - goto returncmd; /* back to cmd mode */ - - case Ctrl_R: /* insert register */ - putcmdline('"', TRUE); - ++no_mapping; - i = c = plain_vgetc(); /* CTRL-R <char> */ - if (i == Ctrl_O) - i = Ctrl_R; /* CTRL-R CTRL-O == CTRL-R CTRL-R */ - if (i == Ctrl_R) - c = plain_vgetc(); /* CTRL-R CTRL-R <char> */ - --no_mapping; - /* - * Insert the result of an expression. - * Need to save the current command line, to be able to enter - * a new one... - */ - new_cmdpos = -1; - if (c == '=') { - if (ccline.cmdfirstc == '=') { /* can't do this recursively */ - beep_flush(); - c = ESC; + } else if (ccline.cmdlen == 0 && s->c != Ctrl_W + && ccline.cmdprompt == NULL && s->indent == 0) { + // In ex and debug mode it doesn't make sense to return. + if (exmode_active || ccline.cmdfirstc == '>') { + return command_line_not_changed(s); + } + + xfree(ccline.cmdbuff); // no commandline to return + ccline.cmdbuff = NULL; + if (!cmd_silent) { + if (cmdmsg_rl) { + msg_col = Columns; } else { - save_cmdline(&save_ccline); - c = get_expr_register(); - restore_cmdline(&save_ccline); + msg_col = 0; } + msg_putchar(' '); // delete ':' } - if (c != ESC) { /* use ESC to cancel inserting register */ - cmdline_paste(c, i == Ctrl_R, FALSE); - - /* When there was a serious error abort getting the - * command line. */ - if (aborting()) { - gotesc = TRUE; /* will free ccline.cmdbuff after - putting it in history */ - goto returncmd; /* back to cmd mode */ - } - KeyTyped = FALSE; /* Don't do p_wc completion. */ - if (new_cmdpos >= 0) { - /* set_cmdline_pos() was used */ - if (new_cmdpos > ccline.cmdlen) - ccline.cmdpos = ccline.cmdlen; - else - ccline.cmdpos = new_cmdpos; + redraw_cmdline = true; + return 0; // back to cmd mode + } + return command_line_changed(s); + + case K_INS: + case K_KINS: + // if Farsi mode set, we are in reverse insert mode - + // Do not change the mode + if (cmd_fkmap) { + beep_flush(); + } else { + ccline.overstrike = !ccline.overstrike; + } + + ui_cursor_shape(); // may show different cursor shape + return command_line_not_changed(s); + + case Ctrl_HAT: + if (map_to_exists_mode((char_u *)"", LANGMAP, false)) { + // ":lmap" mappings exists, toggle use of mappings. + State ^= LANGMAP; + if (s->b_im_ptr != NULL) { + if (State & LANGMAP) { + *s->b_im_ptr = B_IMODE_LMAP; + } else { + *s->b_im_ptr = B_IMODE_NONE; } } - redrawcmd(); - goto cmdline_changed; + } - case Ctrl_D: - if (showmatches(&xpc, FALSE) == EXPAND_NOTHING) - break; /* Use ^D as normal char instead */ + if (s->b_im_ptr != NULL) { + if (s->b_im_ptr == &curbuf->b_p_iminsert) { + set_iminsert_global(); + } else { + set_imsearch_global(); + } + } + ui_cursor_shape(); // may show different cursor shape + // Show/unshow value of 'keymap' in status lines later. + status_redraw_curbuf(); + return command_line_not_changed(s); + + // case '@': only in very old vi + case Ctrl_U: + // delete all characters left of the cursor + s->j = ccline.cmdpos; + ccline.cmdlen -= s->j; + s->i = ccline.cmdpos = 0; + while (s->i < ccline.cmdlen) { + ccline.cmdbuff[s->i++] = ccline.cmdbuff[s->j++]; + } - redrawcmd(); - continue; /* don't do incremental search now */ + // Truncate at the end, required for multi-byte chars. + ccline.cmdbuff[ccline.cmdlen] = NUL; + redrawcmd(); + return command_line_changed(s); - case K_RIGHT: - case K_S_RIGHT: - case K_C_RIGHT: - do { - if (ccline.cmdpos >= ccline.cmdlen) - break; - i = cmdline_charsize(ccline.cmdpos); - if (KeyTyped && ccline.cmdspos + i >= Columns * Rows) - break; - ccline.cmdspos += i; - if (has_mbyte) - ccline.cmdpos += (*mb_ptr2len)(ccline.cmdbuff - + ccline.cmdpos); - else - ++ccline.cmdpos; - } while ((c == K_S_RIGHT || c == K_C_RIGHT - || (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL))) - && ccline.cmdbuff[ccline.cmdpos] != ' '); - if (has_mbyte) - set_cmdspos_cursor(); - goto cmdline_not_changed; - case K_LEFT: - case K_S_LEFT: - case K_C_LEFT: - if (ccline.cmdpos == 0) - goto cmdline_not_changed; - do { - --ccline.cmdpos; - if (has_mbyte) /* move to first byte of char */ - ccline.cmdpos -= (*mb_head_off)(ccline.cmdbuff, - ccline.cmdbuff + ccline.cmdpos); - ccline.cmdspos -= cmdline_charsize(ccline.cmdpos); - } while (ccline.cmdpos > 0 - && (c == K_S_LEFT || c == K_C_LEFT - || (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL))) - && ccline.cmdbuff[ccline.cmdpos - 1] != ' '); - if (has_mbyte) - set_cmdspos_cursor(); - goto cmdline_not_changed; + case ESC: // get here if p_wc != ESC or when ESC typed twice + case Ctrl_C: + // In exmode it doesn't make sense to return. Except when + // ":normal" runs out of characters. + if (exmode_active && (ex_normal_busy == 0 || typebuf.tb_len > 0)) { + return command_line_not_changed(s); + } - case K_IGNORE: - /* Ignore mouse event or ex_window() result. */ - goto cmdline_not_changed; + s->gotesc = true; // will free ccline.cmdbuff after + // putting it in history + return 0; // back to cmd mode + case Ctrl_R: // insert register + putcmdline('"', true); + ++no_mapping; + s->i = s->c = plain_vgetc(); // CTRL-R <char> + if (s->i == Ctrl_O) { + s->i = Ctrl_R; // CTRL-R CTRL-O == CTRL-R CTRL-R + } - case K_MIDDLEDRAG: - case K_MIDDLERELEASE: - goto cmdline_not_changed; /* Ignore mouse */ + if (s->i == Ctrl_R) { + s->c = plain_vgetc(); // CTRL-R CTRL-R <char> + } + --no_mapping; + // Insert the result of an expression. + // Need to save the current command line, to be able to enter + // a new one... + new_cmdpos = -1; + if (s->c == '=') { + if (ccline.cmdfirstc == '=') { // can't do this recursively + beep_flush(); + s->c = ESC; + } else { + save_cmdline(&s->save_ccline); + s->c = get_expr_register(); + restore_cmdline(&s->save_ccline); + } + } - case K_MIDDLEMOUSE: - if (!mouse_has(MOUSE_COMMAND)) - goto cmdline_not_changed; /* Ignore mouse */ - cmdline_paste(0, TRUE, TRUE); - redrawcmd(); - goto cmdline_changed; - - - case K_LEFTDRAG: - case K_LEFTRELEASE: - case K_RIGHTDRAG: - case K_RIGHTRELEASE: - /* Ignore drag and release events when the button-down wasn't - * seen before. */ - if (ignore_drag_release) - goto cmdline_not_changed; - /* FALLTHROUGH */ - case K_LEFTMOUSE: - case K_RIGHTMOUSE: - if (c == K_LEFTRELEASE || c == K_RIGHTRELEASE) - ignore_drag_release = TRUE; - else - ignore_drag_release = FALSE; - if (!mouse_has(MOUSE_COMMAND)) - goto cmdline_not_changed; /* Ignore mouse */ - - set_cmdspos(); - for (ccline.cmdpos = 0; ccline.cmdpos < ccline.cmdlen; - ++ccline.cmdpos) { - i = cmdline_charsize(ccline.cmdpos); - if (mouse_row <= cmdline_row + ccline.cmdspos / Columns - && mouse_col < ccline.cmdspos % Columns + i) - break; - if (has_mbyte) { - /* Count ">" for double-wide char that doesn't fit. */ - correct_cmdspos(ccline.cmdpos, i); - ccline.cmdpos += (*mb_ptr2len)(ccline.cmdbuff - + ccline.cmdpos) - 1; + if (s->c != ESC) { // use ESC to cancel inserting register + cmdline_paste(s->c, s->i == Ctrl_R, false); + + // When there was a serious error abort getting the + // command line. + if (aborting()) { + s->gotesc = true; // will free ccline.cmdbuff after + // putting it in history + return 0; // back to cmd mode + } + KeyTyped = false; // Don't do p_wc completion. + if (new_cmdpos >= 0) { + // set_cmdline_pos() was used + if (new_cmdpos > ccline.cmdlen) { + ccline.cmdpos = ccline.cmdlen; + } else { + ccline.cmdpos = new_cmdpos; } - ccline.cmdspos += i; } - goto cmdline_not_changed; - - /* Mouse scroll wheel: ignored here */ - case K_MOUSEDOWN: - case K_MOUSEUP: - case K_MOUSELEFT: - case K_MOUSERIGHT: - /* Alternate buttons ignored here */ - case K_X1MOUSE: - case K_X1DRAG: - case K_X1RELEASE: - case K_X2MOUSE: - case K_X2DRAG: - case K_X2RELEASE: - goto cmdline_not_changed; - - - - case K_SELECT: /* end of Select mode mapping - ignore */ - goto cmdline_not_changed; - - case Ctrl_B: /* begin of command line */ - case K_HOME: - case K_KHOME: - case K_S_HOME: - case K_C_HOME: - ccline.cmdpos = 0; - set_cmdspos(); - goto cmdline_not_changed; - - case Ctrl_E: /* end of command line */ - case K_END: - case K_KEND: - case K_S_END: - case K_C_END: - ccline.cmdpos = ccline.cmdlen; - set_cmdspos_cursor(); - goto cmdline_not_changed; + } + redrawcmd(); + return command_line_changed(s); - case Ctrl_A: /* all matches */ - if (nextwild(&xpc, WILD_ALL, 0, firstc != '@') == FAIL) + case Ctrl_D: + if (showmatches(&s->xpc, false) == EXPAND_NOTHING) { + break; // Use ^D as normal char instead + } + + redrawcmd(); + return 1; // don't do incremental search now + + case K_RIGHT: + case K_S_RIGHT: + case K_C_RIGHT: + do { + if (ccline.cmdpos >= ccline.cmdlen) { break; - goto cmdline_changed; - - case Ctrl_L: - if (p_is && !cmd_silent && (firstc == '/' || firstc == '?')) { - /* Add a character from under the cursor for 'incsearch' */ - if (did_incsearch - && !equalpos(curwin->w_cursor, old_cursor)) { - c = gchar_cursor(); - /* If 'ignorecase' and 'smartcase' are set and the - * command line has no uppercase characters, convert - * the character to lowercase */ - if (p_ic && p_scs && !pat_has_uppercase(ccline.cmdbuff)) - c = vim_tolower(c); - if (c != NUL) { - if (c == firstc || vim_strchr((char_u *)( - p_magic ? "\\^$.*[" : "\\^$"), c) - != NULL) { - /* put a backslash before special characters */ - stuffcharReadbuff(c); - c = '\\'; - } - break; - } - } - goto cmdline_not_changed; } - /* completion: longest common part */ - if (nextwild(&xpc, WILD_LONGEST, 0, firstc != '@') == FAIL) + s->i = cmdline_charsize(ccline.cmdpos); + if (KeyTyped && ccline.cmdspos + s->i >= Columns * Rows) { break; - goto cmdline_changed; + } - case Ctrl_N: /* next match */ - case Ctrl_P: /* previous match */ - if (xpc.xp_numfiles > 0) { - if (nextwild(&xpc, (c == Ctrl_P) ? WILD_PREV : WILD_NEXT, - 0, firstc != '@') == FAIL) - break; - goto cmdline_changed; + ccline.cmdspos += s->i; + if (has_mbyte) { + ccline.cmdpos += (*mb_ptr2len)(ccline.cmdbuff + + ccline.cmdpos); + } else { + ++ccline.cmdpos; } + } while ((s->c == K_S_RIGHT || s->c == K_C_RIGHT + || (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL))) + && ccline.cmdbuff[ccline.cmdpos] != ' '); + if (has_mbyte) { + set_cmdspos_cursor(); + } + return command_line_not_changed(s); - case K_UP: - case K_DOWN: - case K_S_UP: - case K_S_DOWN: - case K_PAGEUP: - case K_KPAGEUP: - case K_PAGEDOWN: - case K_KPAGEDOWN: - if (hislen == 0 || firstc == NUL) /* no history */ - goto cmdline_not_changed; - - i = hiscnt; - - /* save current command string so it can be restored later */ - if (lookfor == NULL) { - lookfor = vim_strsave(ccline.cmdbuff); - lookfor[ccline.cmdpos] = NUL; + case K_LEFT: + case K_S_LEFT: + case K_C_LEFT: + if (ccline.cmdpos == 0) { + return command_line_not_changed(s); + } + do { + --ccline.cmdpos; + if (has_mbyte) { // move to first byte of char + ccline.cmdpos -= (*mb_head_off)(ccline.cmdbuff, + ccline.cmdbuff + ccline.cmdpos); } + ccline.cmdspos -= cmdline_charsize(ccline.cmdpos); + } while (ccline.cmdpos > 0 + && (s->c == K_S_LEFT || s->c == K_C_LEFT + || (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL))) + && ccline.cmdbuff[ccline.cmdpos - 1] != ' '); - j = (int)STRLEN(lookfor); - for (;; ) { - /* one step backwards */ - if (c == K_UP|| c == K_S_UP || c == Ctrl_P - || c == K_PAGEUP || c == K_KPAGEUP) { - if (hiscnt == hislen) /* first time */ - hiscnt = hisidx[histype]; - else if (hiscnt == 0 && hisidx[histype] != hislen - 1) - hiscnt = hislen - 1; - else if (hiscnt != hisidx[histype] + 1) - --hiscnt; - else { /* at top of list */ - hiscnt = i; - break; - } - } else { /* one step forwards */ - /* on last entry, clear the line */ - if (hiscnt == hisidx[histype]) { - hiscnt = hislen; - break; - } + if (has_mbyte) { + set_cmdspos_cursor(); + } - /* not on a history line, nothing to do */ - if (hiscnt == hislen) - break; - if (hiscnt == hislen - 1) /* wrap around */ - hiscnt = 0; - else - ++hiscnt; + return command_line_not_changed(s); + + case K_IGNORE: + // Ignore mouse event or ex_window() result. + return command_line_not_changed(s); + + + case K_MIDDLEDRAG: + case K_MIDDLERELEASE: + return command_line_not_changed(s); // Ignore mouse + + case K_MIDDLEMOUSE: + if (!mouse_has(MOUSE_COMMAND)) { + return command_line_not_changed(s); // Ignore mouse + } + cmdline_paste(0, true, true); + redrawcmd(); + return command_line_changed(s); + + + case K_LEFTDRAG: + case K_LEFTRELEASE: + case K_RIGHTDRAG: + case K_RIGHTRELEASE: + // Ignore drag and release events when the button-down wasn't + // seen before. + if (s->ignore_drag_release) { + return command_line_not_changed(s); + } + // FALLTHROUGH + case K_LEFTMOUSE: + case K_RIGHTMOUSE: + if (s->c == K_LEFTRELEASE || s->c == K_RIGHTRELEASE) { + s->ignore_drag_release = true; + } else { + s->ignore_drag_release = false; + } + + if (!mouse_has(MOUSE_COMMAND)) { + return command_line_not_changed(s); // Ignore mouse + } + + set_cmdspos(); + for (ccline.cmdpos = 0; ccline.cmdpos < ccline.cmdlen; + ++ccline.cmdpos) { + s->i = cmdline_charsize(ccline.cmdpos); + if (mouse_row <= cmdline_row + ccline.cmdspos / Columns + && mouse_col < ccline.cmdspos % Columns + s->i) { + break; + } + + if (has_mbyte) { + // Count ">" for double-wide char that doesn't fit. + correct_cmdspos(ccline.cmdpos, s->i); + ccline.cmdpos += (*mb_ptr2len)(ccline.cmdbuff + + ccline.cmdpos) - 1; + } + ccline.cmdspos += s->i; + } + return command_line_not_changed(s); + + // Mouse scroll wheel: ignored here + case K_MOUSEDOWN: + case K_MOUSEUP: + case K_MOUSELEFT: + case K_MOUSERIGHT: + // Alternate buttons ignored here + case K_X1MOUSE: + case K_X1DRAG: + case K_X1RELEASE: + case K_X2MOUSE: + case K_X2DRAG: + case K_X2RELEASE: + return command_line_not_changed(s); + + + + case K_SELECT: // end of Select mode mapping - ignore + return command_line_not_changed(s); + + case Ctrl_B: // begin of command line + case K_HOME: + case K_KHOME: + case K_S_HOME: + case K_C_HOME: + ccline.cmdpos = 0; + set_cmdspos(); + return command_line_not_changed(s); + + case Ctrl_E: // end of command line + case K_END: + case K_KEND: + case K_S_END: + case K_C_END: + ccline.cmdpos = ccline.cmdlen; + set_cmdspos_cursor(); + return command_line_not_changed(s); + + case Ctrl_A: // all matches + if (nextwild(&s->xpc, WILD_ALL, 0, s->firstc != '@') == FAIL) + break; + return command_line_changed(s); + + case Ctrl_L: + if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) { + // Add a character from under the cursor for 'incsearch' + if (s->did_incsearch && !equalpos(curwin->w_cursor, s->old_cursor)) { + s->c = gchar_cursor(); + // If 'ignorecase' and 'smartcase' are set and the + // command line has no uppercase characters, convert + // the character to lowercase + if (p_ic && p_scs && !pat_has_uppercase(ccline.cmdbuff)) { + s->c = vim_tolower(s->c); } - if (hiscnt < 0 || history[histype][hiscnt].hisstr == NULL) { - hiscnt = i; + + if (s->c != NUL) { + if (s->c == s->firstc + || vim_strchr((char_u *)(p_magic ? "\\^$.*[" : "\\^$"), s->c) + != NULL) { + // put a backslash before special characters + stuffcharReadbuff(s->c); + s->c = '\\'; + } break; } - if ((c != K_UP && c != K_DOWN) - || hiscnt == i - || STRNCMP(history[histype][hiscnt].hisstr, - lookfor, (size_t)j) == 0) - break; } + return command_line_not_changed(s); + } - if (hiscnt != i) { /* jumped to other entry */ - char_u *p; - int len; - int old_firstc; + // completion: longest common part + if (nextwild(&s->xpc, WILD_LONGEST, 0, s->firstc != '@') == FAIL) { + break; + } + return command_line_changed(s); - xfree(ccline.cmdbuff); - xpc.xp_context = EXPAND_NOTHING; - if (hiscnt == hislen) - p = lookfor; /* back to the old one */ - else - p = history[histype][hiscnt].hisstr; - - if (histype == HIST_SEARCH - && p != lookfor - && (old_firstc = p[STRLEN(p) + 1]) != firstc) { - /* Correct for the separator character used when - * adding the history entry vs the one used now. - * First loop: count length. - * Second loop: copy the characters. */ - for (i = 0; i <= 1; ++i) { - len = 0; - for (j = 0; p[j] != NUL; ++j) { - /* Replace old sep with new sep, unless it is - * escaped. */ - if (p[j] == old_firstc - && (j == 0 || p[j - 1] != '\\')) { - if (i > 0) - ccline.cmdbuff[len] = firstc; - } else { - /* Escape new sep, unless it is already - * escaped. */ - if (p[j] == firstc - && (j == 0 || p[j - 1] != '\\')) { - if (i > 0) - ccline.cmdbuff[len] = '\\'; - ++len; - } - if (i > 0) - ccline.cmdbuff[len] = p[j]; - } - ++len; - } - if (i == 0) { - alloc_cmdbuff(len); - } - } - ccline.cmdbuff[len] = NUL; + case Ctrl_N: // next match + case Ctrl_P: // previous match + if (s->xpc.xp_numfiles > 0) { + if (nextwild(&s->xpc, (s->c == Ctrl_P) ? WILD_PREV : WILD_NEXT, + 0, s->firstc != '@') == FAIL) { + break; + } + return command_line_changed(s); + } + + case K_UP: + case K_DOWN: + case K_S_UP: + case K_S_DOWN: + case K_PAGEUP: + case K_KPAGEUP: + case K_PAGEDOWN: + case K_KPAGEDOWN: + if (hislen == 0 || s->firstc == NUL) { + // no history + return command_line_not_changed(s); + } + + s->i = s->hiscnt; + + // save current command string so it can be restored later + if (s->lookfor == NULL) { + s->lookfor = vim_strsave(ccline.cmdbuff); + s->lookfor[ccline.cmdpos] = NUL; + } + + s->j = (int)STRLEN(s->lookfor); + for (;; ) { + // one step backwards + if (s->c == K_UP|| s->c == K_S_UP || s->c == Ctrl_P + || s->c == K_PAGEUP || s->c == K_KPAGEUP) { + if (s->hiscnt == hislen) { + // first time + s->hiscnt = hisidx[s->histype]; + } else if (s->hiscnt == 0 && hisidx[s->histype] != hislen - 1) { + s->hiscnt = hislen - 1; + } else if (s->hiscnt != hisidx[s->histype] + 1) { + --s->hiscnt; } else { - alloc_cmdbuff((int)STRLEN(p)); - STRCPY(ccline.cmdbuff, p); + // at top of list + s->hiscnt = s->i; + break; + } + } else { // one step forwards + // on last entry, clear the line + if (s->hiscnt == hisidx[s->histype]) { + s->hiscnt = hislen; + break; } - ccline.cmdpos = ccline.cmdlen = (int)STRLEN(ccline.cmdbuff); - redrawcmd(); - goto cmdline_changed; - } - beep_flush(); - goto cmdline_not_changed; - - case Ctrl_V: - case Ctrl_Q: - ignore_drag_release = TRUE; - putcmdline('^', TRUE); - c = get_literal(); /* get next (two) character(s) */ - do_abbr = FALSE; /* don't do abbreviation now */ - /* may need to remove ^ when composing char was typed */ - if (enc_utf8 && utf_iscomposing(c) && !cmd_silent) { - draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos); - msg_putchar(' '); - cursorcmd(); + // not on a history line, nothing to do + if (s->hiscnt == hislen) { + break; + } + + if (s->hiscnt == hislen - 1) { + // wrap around + s->hiscnt = 0; + } else { + ++s->hiscnt; + } } - break; - case Ctrl_K: - ignore_drag_release = TRUE; - putcmdline('?', TRUE); - c = get_digraph(TRUE); - if (c != NUL) + if (s->hiscnt < 0 || history[s->histype][s->hiscnt].hisstr == NULL) { + s->hiscnt = s->i; break; + } - redrawcmd(); - goto cmdline_not_changed; - - case Ctrl__: /* CTRL-_: switch language mode */ - if (!p_ari) + if ((s->c != K_UP && s->c != K_DOWN) + || s->hiscnt == s->i + || STRNCMP(history[s->histype][s->hiscnt].hisstr, + s->lookfor, (size_t)s->j) == 0) { break; - if (p_altkeymap) { - cmd_fkmap = !cmd_fkmap; - if (cmd_fkmap) /* in Farsi always in Insert mode */ - ccline.overstrike = FALSE; - } else /* Hebrew is default */ - cmd_hkmap = !cmd_hkmap; - goto cmdline_not_changed; - - default: -#ifdef UNIX - if (c == intr_char) { - gotesc = TRUE; /* will free ccline.cmdbuff after - putting it in history */ - goto returncmd; /* back to Normal mode */ } -#endif - /* - * Normal character with no special meaning. Just set mod_mask - * to 0x0 so that typing Shift-Space in the GUI doesn't enter - * the string <S-Space>. This should only happen after ^V. - */ - if (!IS_SPECIAL(c)) - mod_mask = 0x0; - break; } - /* - * End of switch on command line character. - * We come here if we have a normal character. - */ - if (do_abbr && (IS_SPECIAL(c) || !vim_iswordc(c)) && (ccheck_abbr( - /* Add ABBR_OFF for characters above 0x100, this is - * what check_abbr() expects. */ - (has_mbyte && - c >= - 0x100) ? (c + - ABBR_OFF) : - c) || c == - Ctrl_RSB)) - goto cmdline_changed; + if (s->hiscnt != s->i) { + // jumped to other entry + char_u *p; + int len; + int old_firstc; - /* - * put the character in the command line - */ - if (IS_SPECIAL(c) || mod_mask != 0) - put_on_cmdline(get_special_key_name(c, mod_mask), -1, TRUE); - else { - if (has_mbyte) { - j = (*mb_char2bytes)(c, IObuff); - IObuff[j] = NUL; /* exclude composing chars */ - put_on_cmdline(IObuff, j, TRUE); + xfree(ccline.cmdbuff); + s->xpc.xp_context = EXPAND_NOTHING; + if (s->hiscnt == hislen) { + p = s->lookfor; // back to the old one } else { - IObuff[0] = c; - put_on_cmdline(IObuff, 1, TRUE); + p = history[s->histype][s->hiscnt].hisstr; } - } - goto cmdline_changed; - /* - * This part implements incremental searches for "/" and "?" - * Jump to cmdline_not_changed when a character has been read but the command - * line did not change. Then we only search and redraw if something changed in - * the past. - * Jump to cmdline_changed when the command line did change. - * (Sorry for the goto's, I know it is ugly). - */ -cmdline_not_changed: - if (!incsearch_postponed) - continue; + if (s->histype == HIST_SEARCH + && p != s->lookfor + && (old_firstc = p[STRLEN(p) + 1]) != s->firstc) { + // Correct for the separator character used when + // adding the history entry vs the one used now. + // First loop: count length. + // Second loop: copy the characters. + for (s->i = 0; s->i <= 1; ++s->i) { + len = 0; + for (s->j = 0; p[s->j] != NUL; ++s->j) { + // Replace old sep with new sep, unless it is + // escaped. + if (p[s->j] == old_firstc + && (s->j == 0 || p[s->j - 1] != '\\')) { + if (s->i > 0) { + ccline.cmdbuff[len] = s->firstc; + } + } else { + // Escape new sep, unless it is already + // escaped. + if (p[s->j] == s->firstc + && (s->j == 0 || p[s->j - 1] != '\\')) { + if (s->i > 0) { + ccline.cmdbuff[len] = '\\'; + } + ++len; + } -cmdline_changed: - /* - * 'incsearch' highlighting. - */ - if (p_is && !cmd_silent && (firstc == '/' || firstc == '?')) { - pos_T end_pos; - proftime_T tm; + if (s->i > 0) { + ccline.cmdbuff[len] = p[s->j]; + } + } + ++len; + } - /* if there is a character waiting, search and redraw later */ - if (char_avail()) { - incsearch_postponed = TRUE; - continue; + if (s->i == 0) { + alloc_cmdbuff(len); + } + } + ccline.cmdbuff[len] = NUL; + } else { + alloc_cmdbuff((int)STRLEN(p)); + STRCPY(ccline.cmdbuff, p); } - incsearch_postponed = FALSE; - curwin->w_cursor = old_cursor; /* start at old position */ - /* If there is no command line, don't do anything */ - if (ccline.cmdlen == 0) - i = 0; - else { - ui_busy_start(); - ui_flush(); - ++emsg_off; /* So it doesn't beep if bad expr */ - /* Set the time limit to half a second. */ - tm = profile_setlimit(500L); - i = do_search(NULL, firstc, ccline.cmdbuff, count, - SEARCH_KEEP + SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK, - &tm - ); - --emsg_off; - /* if interrupted while searching, behave like it failed */ - if (got_int) { - (void)vpeekc(); /* remove <C-C> from input stream */ - got_int = FALSE; /* don't abandon the command line */ - i = 0; - } else if (char_avail()) - /* cancelled searching because a char was typed */ - incsearch_postponed = TRUE; - ui_busy_stop(); - } - if (i != 0) - highlight_match = TRUE; /* highlight position */ - else - highlight_match = FALSE; /* remove highlight */ - - /* first restore the old curwin values, so the screen is - * positioned in the same way as the actual search command */ - curwin->w_leftcol = old_leftcol; - curwin->w_topline = old_topline; - curwin->w_topfill = old_topfill; - curwin->w_botline = old_botline; - changed_cline_bef_curs(); - update_topline(); - - if (i != 0) { - pos_T save_pos = curwin->w_cursor; - - /* - * First move cursor to end of match, then to the start. This - * moves the whole match onto the screen when 'nowrap' is set. - */ - curwin->w_cursor.lnum += search_match_lines; - curwin->w_cursor.col = search_match_endcol; - if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { - curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - coladvance((colnr_T)MAXCOL); - } - validate_cursor(); - end_pos = curwin->w_cursor; - curwin->w_cursor = save_pos; - } else - end_pos = curwin->w_cursor; /* shutup gcc 4 */ + ccline.cmdpos = ccline.cmdlen = (int)STRLEN(ccline.cmdbuff); + redrawcmd(); + return command_line_changed(s); + } + beep_flush(); + return command_line_not_changed(s); + + case Ctrl_V: + case Ctrl_Q: + s->ignore_drag_release = true; + putcmdline('^', true); + s->c = get_literal(); // get next (two) character(s) + s->do_abbr = false; // don't do abbreviation now + // may need to remove ^ when composing char was typed + if (enc_utf8 && utf_iscomposing(s->c) && !cmd_silent) { + draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos); + msg_putchar(' '); + cursorcmd(); + } + break; - validate_cursor(); - /* May redraw the status line to show the cursor position. */ - if (p_ru && curwin->w_status_height > 0) - curwin->w_redr_status = TRUE; + case Ctrl_K: + s->ignore_drag_release = true; + putcmdline('?', true); + s->c = get_digraph(true); - save_cmdline(&save_ccline); - update_screen(SOME_VALID); - restore_cmdline(&save_ccline); + if (s->c != NUL) { + break; + } - /* Leave it at the end to make CTRL-R CTRL-W work. */ - if (i != 0) - curwin->w_cursor = end_pos; + redrawcmd(); + return command_line_not_changed(s); - msg_starthere(); - redrawcmdline(); - did_incsearch = TRUE; + case Ctrl__: // CTRL-_: switch language mode + if (!p_ari) { + break; } - - if (cmdmsg_rl - || (p_arshape && !p_tbidi && enc_utf8) - ) - /* Always redraw the whole command line to fix shaping and - * right-left typing. Not efficient, but it works. - * Do it only when there are no characters left to read - * to avoid useless intermediate redraws. */ - if (vpeekc() == NUL) - redrawcmd(); + if (p_altkeymap) { + cmd_fkmap = !cmd_fkmap; + if (cmd_fkmap) { + // in Farsi always in Insert mode + ccline.overstrike = false; + } + } else { + // Hebrew is default + cmd_hkmap = !cmd_hkmap; + } + return command_line_not_changed(s); + + default: + // Normal character with no special meaning. Just set mod_mask + // to 0x0 so that typing Shift-Space in the GUI doesn't enter + // the string <S-Space>. This should only happen after ^V. + if (!IS_SPECIAL(s->c)) { + mod_mask = 0x0; + } + break; } -returncmd: - - cmdmsg_rl = FALSE; + // End of switch on command line character. + // We come here if we have a normal character. + if (s->do_abbr && (IS_SPECIAL(s->c) || !vim_iswordc(s->c)) + // Add ABBR_OFF for characters above 0x100, this is + // what check_abbr() expects. + && (ccheck_abbr((has_mbyte && s->c >= 0x100) ? + (s->c + ABBR_OFF) : s->c) + || s->c == Ctrl_RSB)) { + return command_line_changed(s); + } - cmd_fkmap = 0; + // put the character in the command line + if (IS_SPECIAL(s->c) || mod_mask != 0) { + put_on_cmdline(get_special_key_name(s->c, mod_mask), -1, true); + } else { + if (has_mbyte) { + s->j = (*mb_char2bytes)(s->c, IObuff); + IObuff[s->j] = NUL; // exclude composing chars + put_on_cmdline(IObuff, s->j, true); + } else { + IObuff[0] = s->c; + put_on_cmdline(IObuff, 1, true); + } + } + return command_line_changed(s); +} - ExpandCleanup(&xpc); - ccline.xpc = NULL; - if (did_incsearch) { - curwin->w_cursor = old_cursor; - curwin->w_curswant = old_curswant; - curwin->w_leftcol = old_leftcol; - curwin->w_topline = old_topline; - curwin->w_topfill = old_topfill; - curwin->w_botline = old_botline; - highlight_match = FALSE; - validate_cursor(); /* needed for TAB */ - redraw_later(SOME_VALID); +static int command_line_not_changed(CommandLineState *s) +{ + // This part implements incremental searches for "/" and "?" Jump to + // cmdline_not_changed when a character has been read but the command line + // did not change. Then we only search and redraw if something changed in + // the past. Jump to cmdline_changed when the command line did change. + // (Sorry for the goto's, I know it is ugly). + if (!s->incsearch_postponed) { + return 1; } + return command_line_changed(s); +} - if (ccline.cmdbuff != NULL) { - /* - * Put line in history buffer (":" and "=" only when it was typed). - */ - if (ccline.cmdlen && firstc != NUL - && (some_key_typed || histype == HIST_SEARCH)) { - add_to_history(histype, ccline.cmdbuff, TRUE, - histype == HIST_SEARCH ? firstc : NUL); - if (firstc == ':') { - xfree(new_last_cmdline); - new_last_cmdline = vim_strsave(ccline.cmdbuff); +static int command_line_changed(CommandLineState *s) +{ + // 'incsearch' highlighting. + if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) { + pos_T end_pos; + proftime_T tm; + + // if there is a character waiting, search and redraw later + if (char_avail()) { + s->incsearch_postponed = true; + return 1; + } + s->incsearch_postponed = false; + curwin->w_cursor = s->old_cursor; // start at old position + + // If there is no command line, don't do anything + if (ccline.cmdlen == 0) { + s->i = 0; + } else { + ui_busy_start(); + ui_flush(); + ++emsg_off; // So it doesn't beep if bad expr + // Set the time limit to half a second. + tm = profile_setlimit(500L); + s->i = do_search(NULL, s->firstc, ccline.cmdbuff, s->count, + SEARCH_KEEP + SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK, + &tm); + --emsg_off; + // if interrupted while searching, behave like it failed + if (got_int) { + (void)vpeekc(); // remove <C-C> from input stream + got_int = false; // don't abandon the command line + s->i = 0; + } else if (char_avail()) { + // cancelled searching because a char was typed + s->incsearch_postponed = true; } + ui_busy_stop(); } - if (gotesc) { /* abandon command line */ - xfree(ccline.cmdbuff); - ccline.cmdbuff = NULL; - if (msg_scrolled == 0) - compute_cmdrow(); - MSG(""); - redraw_cmdline = TRUE; + if (s->i != 0) { + highlight_match = true; // highlight position + } else { + highlight_match = false; // remove highlight } - } - /* - * If the screen was shifted up, redraw the whole screen (later). - * If the line is too long, clear it, so ruler and shown command do - * not get printed in the middle of it. - */ - msg_check(); - msg_scroll = save_msg_scroll; - redir_off = FALSE; + // first restore the old curwin values, so the screen is + // positioned in the same way as the actual search command + curwin->w_leftcol = s->old_leftcol; + curwin->w_topline = s->old_topline; + curwin->w_topfill = s->old_topfill; + curwin->w_botline = s->old_botline; + changed_cline_bef_curs(); + update_topline(); + + if (s->i != 0) { + pos_T save_pos = curwin->w_cursor; + + // First move cursor to end of match, then to the start. This + // moves the whole match onto the screen when 'nowrap' is set. + curwin->w_cursor.lnum += search_match_lines; + curwin->w_cursor.col = search_match_endcol; + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + coladvance((colnr_T)MAXCOL); + } + validate_cursor(); + end_pos = curwin->w_cursor; + curwin->w_cursor = save_pos; + } else { + end_pos = curwin->w_cursor; // shutup gcc 4 + } - /* When the command line was typed, no need for a wait-return prompt. */ - if (some_key_typed) - need_wait_return = FALSE; + validate_cursor(); + // May redraw the status line to show the cursor position. + if (p_ru && curwin->w_status_height > 0) { + curwin->w_redr_status = true; + } - State = save_State; - setmouse(); - ui_cursor_shape(); /* may show different cursor shape */ + save_cmdline(&s->save_ccline); + update_screen(SOME_VALID); + restore_cmdline(&s->save_ccline); - { - char_u *p = ccline.cmdbuff; + // Leave it at the end to make CTRL-R CTRL-W work. + if (s->i != 0) { + curwin->w_cursor = end_pos; + } - /* Make ccline empty, getcmdline() may try to use it. */ - ccline.cmdbuff = NULL; - return p; + msg_starthere(); + redrawcmdline(); + s->did_incsearch = true; } + + if (cmdmsg_rl || (p_arshape && !p_tbidi && enc_utf8)) { + // Always redraw the whole command line to fix shaping and + // right-left typing. Not efficient, but it works. + // Do it only when there are no characters left to read + // to avoid useless intermediate redraws. + if (vpeekc() == NUL) { + redrawcmd(); + } + } + + return 1; +} + +/* + * getcmdline() - accept a command line starting with firstc. + * + * firstc == ':' get ":" command line. + * firstc == '/' or '?' get search pattern + * firstc == '=' get expression + * firstc == '@' get text for input() function + * firstc == '>' get text for debug mode + * firstc == NUL get text for :insert command + * firstc == -1 like NUL, and break on CTRL-C + * + * The line is collected in ccline.cmdbuff, which is reallocated to fit the + * command line. + * + * Careful: getcmdline() can be called recursively! + * + * Return pointer to allocated string if there is a commandline, NULL + * otherwise. + */ +char_u * +getcmdline ( + int firstc, + long count, // only used for incremental search + int indent // indent for inside conditionals +) +{ + return command_line_enter(firstc, count, indent); } /* @@ -4943,7 +5061,7 @@ static int ex_window(void) * Call the main loop until <CR> or CTRL-C is typed. */ cmdwin_result = 0; - main_loop(TRUE, FALSE); + normal_enter(true, false); RedrawingDisabled = i; diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 990d0fb8e2..b77a030158 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1131,7 +1131,7 @@ static void gotchars(char_u *chars, int len) * - While reading a script file. * - When no_u_sync is non-zero. */ -static void may_sync_undo(void) +void may_sync_undo(void) { if ((!(State & (INSERT + CMDLINE)) || arrow_used) && scriptin[curscript] == NULL) diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index bf4f5e8c4d..9d656276ec 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -283,7 +283,6 @@ static struct key_name_entry { {K_ZERO, (char_u *)"Nul"}, {K_SNR, (char_u *)"SNR"}, {K_PLUG, (char_u *)"Plug"}, - {K_CURSORHOLD, (char_u *)"CursorHold"}, {K_PASTE, (char_u *)"Paste"}, {0, NULL} }; diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h index 119bff943a..d2d96c6149 100644 --- a/src/nvim/keymap.h +++ b/src/nvim/keymap.h @@ -242,7 +242,6 @@ enum key_extra { , KE_X2RELEASE , KE_DROP /* DnD data is available */ - , KE_CURSORHOLD /* CursorHold event */ , KE_NOP /* doesn't do something */ , KE_FOCUSGAINED /* focus gained */ , KE_FOCUSLOST /* focus lost */ @@ -437,7 +436,6 @@ enum key_extra { #define K_FOCUSGAINED TERMCAP2KEY(KS_EXTRA, KE_FOCUSGAINED) #define K_FOCUSLOST TERMCAP2KEY(KS_EXTRA, KE_FOCUSLOST) -#define K_CURSORHOLD TERMCAP2KEY(KS_EXTRA, KE_CURSORHOLD) #define K_EVENT TERMCAP2KEY(KS_EXTRA, KE_EVENT) #define K_PASTE TERMCAP2KEY(KS_EXTRA, KE_PASTE) diff --git a/src/nvim/main.c b/src/nvim/main.c index 60a242fae3..eb2a1567e7 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -54,6 +54,7 @@ #include "nvim/profile.h" #include "nvim/quickfix.h" #include "nvim/screen.h" +#include "nvim/state.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui.h" @@ -528,228 +529,16 @@ int main(int argc, char **argv) } TIME_MSG("before starting main loop"); + ILOG("Starting Neovim main loop."); /* * Call the main command loop. This never returns. */ - main_loop(FALSE, FALSE); + normal_enter(false, false); return 0; } -/* - * Main loop: Execute Normal mode commands until exiting Vim. - * Also used to handle commands in the command-line window, until the window - * is closed. - * Also used to handle ":visual" command after ":global": execute Normal mode - * commands, return when entering Ex mode. "noexmode" is TRUE then. - */ -void -main_loop ( - int cmdwin, /* TRUE when working in the command-line window */ - int noexmode /* TRUE when return on entering Ex mode */ -) -{ - oparg_T oa; /* operator arguments */ - int previous_got_int = FALSE; /* "got_int" was TRUE */ - linenr_T conceal_old_cursor_line = 0; - linenr_T conceal_new_cursor_line = 0; - int conceal_update_lines = FALSE; - - ILOG("Starting Neovim main loop."); - - clear_oparg(&oa); - while (!cmdwin - || cmdwin_result == 0 - ) { - if (stuff_empty()) { - did_check_timestamps = FALSE; - if (need_check_timestamps) - check_timestamps(FALSE); - if (need_wait_return) /* if wait_return still needed ... */ - wait_return(FALSE); /* ... call it now */ - if (need_start_insertmode && goto_im() - && !VIsual_active - ) { - need_start_insertmode = FALSE; - stuffReadbuff((char_u *)"i"); /* start insert mode next */ - /* skip the fileinfo message now, because it would be shown - * after insert mode finishes! */ - need_fileinfo = FALSE; - } - } - - /* 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 (noexmode && global_busy && !exmode_active && 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) - (void)vgetc(); /* flush all buffers */ - got_int = FALSE; - } - previous_got_int = TRUE; - } else - previous_got_int = FALSE; - - 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()) { - /* 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 (curwin->w_p_cole > 0) { - conceal_old_cursor_line = last_cursormoved.lnum; - conceal_new_cursor_line = curwin->w_cursor.lnum; - conceal_update_lines = TRUE; - } - last_cursormoved = curwin->w_cursor; - } - - /* 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); - last_changedtick_buf = curbuf; - last_changedtick = curbuf->b_changedtick; - } - - /* 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; - } - /* 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(); - if (fdo_flags & FDO_ALL) - foldOpenCursor(); - } - - /* - * 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(); - redraw_statuslines(); - if (need_maketitle) - maketitle(); - /* display message after redraw */ - if (keep_msg != NULL) { - char_u *p; - - // 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. - p = keep_msg; - msg_attr(p, keep_msg_attr); - xfree(p); - } - if (need_fileinfo) { /* show file info after redraw */ - fileinfo(FALSE, TRUE, FALSE); - need_fileinfo = FALSE; - } - - 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 (conceal_update_lines - && (conceal_old_cursor_line != conceal_new_cursor_line - || conceal_cursor_line(curwin) - || need_cursor_line_redraw)) { - if (conceal_old_cursor_line != conceal_new_cursor_line - && conceal_old_cursor_line - <= curbuf->b_ml.ml_line_count) - update_single_line(curwin, conceal_old_cursor_line); - update_single_line(curwin, conceal_new_cursor_line); - curwin->w_valid &= ~VALID_CROW; - } - setcursor(); - - 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; - } - } - - /* - * Update w_curswant if w_set_curswant has been set. - * Postponed until here to avoid computing w_virtcol too often. - */ - update_curswant(); - - /* - * 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 = (!cmdwin && !noexmode); - /* - * If we're invoked as ex, do a round of ex commands. - * Otherwise, get and execute a normal mode command. - */ - if (exmode_active) { - if (noexmode) /* End of ":global/path/visual" commands */ - return; - do_exmode(exmode_active == EXMODE_VIM); - } else - normal_cmd(&oa, TRUE); - } -} - - /* Exit properly */ void getout(int exitval) { @@ -2075,3 +1864,5 @@ static void check_swap_exists_action(void) getout(1); handle_swap_exists(NULL); } + + 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; +} diff --git a/src/nvim/normal.h b/src/nvim/normal.h index b71487c30c..01259de6cd 100644 --- a/src/nvim/normal.h +++ b/src/nvim/normal.h @@ -36,8 +36,8 @@ typedef struct oparg_S { bool block_mode; /* current operator is Visual block mode */ colnr_T start_vcol; /* start col for block mode operator */ colnr_T end_vcol; /* end col for block mode operator */ - long prev_opcount; /* ca.opcount saved for K_CURSORHOLD */ - long prev_count0; /* ca.count0 saved for K_CURSORHOLD */ + long prev_opcount; // ca.opcount saved for K_EVENT + long prev_count0; // ca.count0 saved for K_EVENT } oparg_T; /* diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index e2cff2f9c0..df803609ae 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -73,10 +73,25 @@ void input_stop(void) stream_close(&read_stream, NULL); } +static void cursorhold_event(void **argv) +{ + event_T event = State & INSERT ? EVENT_CURSORHOLDI : EVENT_CURSORHOLD; + apply_autocmds(event, NULL, NULL, false, curbuf); + did_cursorhold = true; +} + +static void create_cursorhold_event(void) +{ + // If the queue had any items, this function should not have been + // called(inbuf_poll would return kInputAvail) + assert(queue_empty(loop.events)); + queue_put(loop.events, cursorhold_event, 0); +} + // Low level input function int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt) { - if (rbuffer_size(input_buffer)) { + if (maxlen && rbuffer_size(input_buffer)) { return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen); } @@ -87,16 +102,12 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt) } } else { if ((result = inbuf_poll((int)p_ut)) == kInputNone) { - if (trigger_cursorhold() && maxlen >= 3 - && !typebuf_changed(tb_change_cnt)) { - buf[0] = K_SPECIAL; - buf[1] = KS_EXTRA; - buf[2] = KE_CURSORHOLD; - return 3; + if (trigger_cursorhold() && !typebuf_changed(tb_change_cnt)) { + create_cursorhold_event(); + } else { + before_blocking(); + result = inbuf_poll(-1); } - - before_blocking(); - result = inbuf_poll(-1); } } @@ -105,14 +116,14 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt) return 0; } - if (rbuffer_size(input_buffer)) { + if (maxlen && rbuffer_size(input_buffer)) { // Safe to convert rbuffer_read to int, it will never overflow since we use // relatively small buffers. return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen); } // If there are events, return the keys directly - if (pending_events()) { + if (maxlen && pending_events()) { return push_event_key(buf, maxlen); } @@ -313,6 +324,11 @@ void input_done(void) input_eof = true; } +bool input_available(void) +{ + return rbuffer_size(input_buffer) != 0; +} + // This is a replacement for the old `WaitForChar` function in os_unix.c static InbufPollResult inbuf_poll(int ms) { diff --git a/src/nvim/state.c b/src/nvim/state.c new file mode 100644 index 0000000000..b2f3f0bebe --- /dev/null +++ b/src/nvim/state.c @@ -0,0 +1,62 @@ +#include <assert.h> + +#include "nvim/lib/kvec.h" + +#include "nvim/state.h" +#include "nvim/vim.h" +#include "nvim/getchar.h" +#include "nvim/ui.h" +#include "nvim/os/input.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "state.c.generated.h" +#endif + + +void state_enter(VimState *s) +{ + for (;;) { + int check_result = s->check ? s->check(s) : 1; + + if (!check_result) { + break; + } else if (check_result == -1) { + continue; + } + + int key; + +getkey: + if (char_avail() || using_script() || input_available()) { + // Don't block for events if there's a character already available for + // processing. Characters can come from mappings, scripts and other + // sources, so this scenario is very common. + key = safe_vgetc(); + } else if (!queue_empty(loop.events)) { + // Event was made available after the last queue_process_events call + key = K_EVENT; + } else { + input_enable_events(); + // Flush screen updates before blocking + ui_flush(); + // Call `os_inchar` directly to block for events or user input without + // consuming anything from `input_buffer`(os/input.c) or calling the + // mapping engine. If an event was put into the queue, we send K_EVENT + // directly. + (void)os_inchar(NULL, 0, -1, 0); + input_disable_events(); + key = !queue_empty(loop.events) ? K_EVENT : safe_vgetc(); + } + + if (key == K_EVENT) { + may_sync_undo(); + } + + int execute_result = s->execute(s, key); + if (!execute_result) { + break; + } else if (execute_result == -1) { + goto getkey; + } + } +} diff --git a/src/nvim/state.h b/src/nvim/state.h new file mode 100644 index 0000000000..8027514148 --- /dev/null +++ b/src/nvim/state.h @@ -0,0 +1,20 @@ +#ifndef NVIM_STATE_H +#define NVIM_STATE_H + +#include <stddef.h> + +typedef struct vim_state VimState; + +typedef int(*state_check_callback)(VimState *state); +typedef int(*state_execute_callback)(VimState *state, int key); + +struct vim_state { + state_check_callback check; + state_execute_callback execute; +}; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "state.h.generated.h" +#endif + +#endif // NVIM_STATE_H diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 82b9599051..119ee2c5b3 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -63,6 +63,7 @@ #include "nvim/map.h" #include "nvim/misc1.h" #include "nvim/move.h" +#include "nvim/state.h" #include "nvim/ex_docmd.h" #include "nvim/ex_cmds.h" #include "nvim/window.h" @@ -73,6 +74,16 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/private/handle.h" +typedef struct terminal_state { + VimState state; + Terminal *term; + int save_state; // saved value of State + int save_rd; // saved value of RedrawingDisabled + bool save_mapped_ctrl_c; // saved value of mapped_ctrl_c; + bool close; + bool got_bs; // if the last input was <C-\> +} TerminalState; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "terminal.c.generated.h" #endif @@ -341,105 +352,106 @@ void terminal_resize(Terminal *term, uint16_t width, uint16_t height) void terminal_enter(void) { buf_T *buf = curbuf; - Terminal *term = buf->terminal; - assert(term && "should only be called when curbuf has a terminal"); + TerminalState state, *s = &state; + memset(s, 0, sizeof(TerminalState)); + s->term = buf->terminal; + assert(s->term && "should only be called when curbuf has a terminal"); // Ensure the terminal is properly sized. - terminal_resize(term, 0, 0); + terminal_resize(s->term, 0, 0); checkpcmark(); setpcmark(); - int save_state = State; - int save_rd = RedrawingDisabled; + s->save_state = State; + s->save_rd = RedrawingDisabled; State = TERM_FOCUS; RedrawingDisabled = false; - bool save_mapped_ctrl_c = mapped_ctrl_c; + s->save_mapped_ctrl_c = mapped_ctrl_c; mapped_ctrl_c = true; // go to the bottom when the terminal is focused - adjust_topline(term, buf, false); + adjust_topline(s->term, buf, false); // erase the unfocused cursor - invalidate_terminal(term, term->cursor.row, term->cursor.row + 1); + invalidate_terminal(s->term, s->term->cursor.row, s->term->cursor.row + 1); showmode(); ui_busy_start(); redraw(false); - int c; - bool close = false; - - bool got_bs = false; // True if the last input was <C-\> - - while (curbuf->handle == term->buf_handle) { - input_enable_events(); - c = safe_vgetc(); - input_disable_events(); - - switch (c) { - case K_LEFTMOUSE: - case K_LEFTDRAG: - case K_LEFTRELEASE: - case K_MIDDLEMOUSE: - case K_MIDDLEDRAG: - case K_MIDDLERELEASE: - case K_RIGHTMOUSE: - case K_RIGHTDRAG: - case K_RIGHTRELEASE: - case K_MOUSEDOWN: - case K_MOUSEUP: - if (send_mouse_event(term, c)) { - goto end; - } - break; - - case K_EVENT: - // We cannot let an event free the terminal yet. It is still needed. - term->refcount++; - queue_process_events(loop.events); - term->refcount--; - if (term->buf_handle == 0) { - close = true; - goto end; - } - break; - case Ctrl_N: - if (got_bs) { - goto end; - } - // FALLTHROUGH - - default: - if (c == Ctrl_BSL && !got_bs) { - got_bs = true; - break; - } - if (term->closed) { - close = true; - goto end; - } - - got_bs = false; - terminal_send_key(term, c); - } - } + s->state.execute = terminal_execute; + state_enter(&s->state); -end: restart_edit = 0; - State = save_state; - RedrawingDisabled = save_rd; + State = s->save_state; + RedrawingDisabled = s->save_rd; // draw the unfocused cursor - invalidate_terminal(term, term->cursor.row, term->cursor.row + 1); - mapped_ctrl_c = save_mapped_ctrl_c; + invalidate_terminal(s->term, s->term->cursor.row, s->term->cursor.row + 1); + mapped_ctrl_c = s->save_mapped_ctrl_c; unshowmode(true); - redraw(buf != curbuf); + redraw(curbuf->handle != s->term->buf_handle); ui_busy_stop(); - if (close) { - bool wipe = term->buf_handle != 0; - term->opts.close_cb(term->opts.data); + if (s->close) { + bool wipe = s->term->buf_handle != 0; + s->term->opts.close_cb(s->term->opts.data); if (wipe) { do_cmdline_cmd("bwipeout!"); } } } +static int terminal_execute(VimState *state, int key) +{ + TerminalState *s = (TerminalState *)state; + + switch (key) { + case K_LEFTMOUSE: + case K_LEFTDRAG: + case K_LEFTRELEASE: + case K_MIDDLEMOUSE: + case K_MIDDLEDRAG: + case K_MIDDLERELEASE: + case K_RIGHTMOUSE: + case K_RIGHTDRAG: + case K_RIGHTRELEASE: + case K_MOUSEDOWN: + case K_MOUSEUP: + if (send_mouse_event(s->term, key)) { + return 0; + } + break; + + case K_EVENT: + // We cannot let an event free the terminal yet. It is still needed. + s->term->refcount++; + queue_process_events(loop.events); + s->term->refcount--; + if (s->term->buf_handle == 0) { + s->close = true; + return 0; + } + break; + + case Ctrl_N: + if (s->got_bs) { + return 0; + } + // FALLTHROUGH + + default: + if (key == Ctrl_BSL && !s->got_bs) { + s->got_bs = true; + break; + } + if (s->term->closed) { + s->close = true; + return 0; + } + + s->got_bs = false; + terminal_send_key(s->term, key); + } + + return curbuf->handle == s->term->buf_handle; +} + void terminal_destroy(Terminal *term) { buf_T *buf = handle_get_buffer(term->buf_handle); |