aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/tutor/en/vim-01-beginner.tutor3
-rw-r--r--src/nvim/README.md190
-rw-r--r--src/nvim/edit.c1719
-rw-r--r--src/nvim/ex_docmd.c2
-rw-r--r--src/nvim/ex_getln.c2564
-rw-r--r--src/nvim/getchar.c2
-rw-r--r--src/nvim/keymap.c1
-rw-r--r--src/nvim/keymap.h2
-rw-r--r--src/nvim/main.c219
-rw-r--r--src/nvim/normal.c1443
-rw-r--r--src/nvim/normal.h4
-rw-r--r--src/nvim/os/input.c40
-rw-r--r--src/nvim/state.c62
-rw-r--r--src/nvim/state.h20
-rw-r--r--src/nvim/terminal.c160
-rw-r--r--test/functional/api/vim_spec.lua18
-rw-r--r--test/functional/terminal/tui_spec.lua47
17 files changed, 3575 insertions, 2921 deletions
diff --git a/runtime/tutor/en/vim-01-beginner.tutor b/runtime/tutor/en/vim-01-beginner.tutor
index bda4e3537e..06160a4918 100644
--- a/runtime/tutor/en/vim-01-beginner.tutor
+++ b/runtime/tutor/en/vim-01-beginner.tutor
@@ -883,8 +883,7 @@ Vim has many more features than Vi, but most of them are disabled by
default. To start using more features you have to create a "vimrc" file.
1. Start editing the "vimrc" file. This depends on your system:
- `:e ~/.vimrc`{vim} for Unix-like systems
- `:e $VIM/_vimrc`{vim} for Microsoft Windows
+ `:e ~/config/nvim/init.vim`{vim} for Unix-like systems
2. Now read the example "vimrc" file contents:
`:r $VIMRUNTIME/vimrc_example.vim`{vim}
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);
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 8d4e183653..eb4804f141 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -247,33 +247,21 @@ describe('vim_* functions', function()
~ |
{1:very fail} |
]])
+ helpers.wait()
-- shows up to &cmdheight lines
- nvim_async('err_write', 'more fail\n')
- nvim_async('err_write', 'too fail\n')
+ nvim_async('err_write', 'more fail\ntoo fail\n')
screen:expect([[
~ |
~ |
~ |
~ |
~ |
- {1:very fail} |
- {1:more fail} |
- {2:Press ENTER or type command to continue}^ |
- ]])
-
- -- shows the rest after return
- feed('<cr>')
- screen:expect([[
- ~ |
- ~ |
- ~ |
- {1:very fail} |
{1:more fail} |
- {2:Press ENTER or type command to continue} |
{1:too fail} |
{2:Press ENTER or type command to continue}^ |
]])
+ feed('<cr>') -- exit the press ENTER screen
end)
end)
diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua
index 5ec087645f..d38bedcd4a 100644
--- a/test/functional/terminal/tui_spec.lua
+++ b/test/functional/terminal/tui_spec.lua
@@ -12,7 +12,9 @@ describe('tui', function()
before_each(function()
helpers.clear()
screen = thelpers.screen_setup(0, '["'..helpers.nvim_prog..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile"]')
- screen.timeout = 30000 -- pasting can be really slow in the TUI
+ -- right now pasting can be really slow in the TUI, especially in ASAN.
+ -- this will be fixed later but for now we require a high timeout.
+ screen.timeout = 60000
screen:expect([[
{1: } |
~ |
@@ -51,6 +53,49 @@ describe('tui', function()
]])
end)
+ it('interprets leading esc byte as the alt modifier', function()
+ local keys = 'dfghjkl'
+ for c in keys:gmatch('.') do
+ execute('nnoremap <a-'..c..'> ialt-'..c..'<cr><esc>')
+ feed('\x1b'..c)
+ end
+ screen:expect([[
+ alt-j |
+ alt-k |
+ alt-l |
+ {1: } |
+ [No Name] [+] |
+ |
+ -- TERMINAL -- |
+ ]])
+ feed('gg')
+ screen:expect([[
+ {1:a}lt-d |
+ alt-f |
+ alt-g |
+ alt-h |
+ [No Name] [+] |
+ |
+ -- TERMINAL -- |
+ ]])
+ end)
+
+ it('accepts ascii control sequences', function()
+ feed('i')
+ feed('\x16\x07') -- ctrl+g
+ feed('\x16\x16') -- ctrl+v
+ feed('\x16\x0d') -- ctrl+m
+ screen:expect([[
+ {3:^G^V^M}{1: } |
+ ~ |
+ ~ |
+ ~ |
+ [No Name] [+] |
+ -- INSERT -- |
+ -- TERMINAL -- |
+ ]], {[1] = {reverse = true}, [2] = {background = 11}, [3] = {foreground = 4}})
+ end)
+
it('automatically sends <Paste> for bracketed paste sequences', function()
feed('i\x1b[200~')
screen:expect([[