From e596234fc2fa056ef6ac9eb868bdfcdceba4af47 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Fri, 2 Oct 2015 10:59:06 -0300 Subject: [PATCH 01/23] test: Add more TUI tests and increase timeout --- test/functional/terminal/tui_spec.lua | 47 ++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) 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 ialt-'..c..'') + 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 for bracketed paste sequences', function() feed('i\x1b[200~') screen:expect([[ From dae006a942213f1c37ecfd20ac79d2f7fc462696 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Sun, 4 Oct 2015 09:36:34 -0300 Subject: [PATCH 02/23] main: Extract `normal_check` from `main_loop` The new function contains logic that must be executed after handling input in normal mode and also before the first main loop iteration. Also rename `main_loop` to `normal_enter` and move it to normal.c --- src/nvim/ex_docmd.c | 2 +- src/nvim/ex_getln.c | 2 +- src/nvim/main.c | 218 +------------------------------------- src/nvim/normal.c | 251 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 257 insertions(+), 216 deletions(-) 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..7b9fd1e61e 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -4943,7 +4943,7 @@ static int ex_window(void) * Call the main loop until or CTRL-C is typed. */ cmdwin_result = 0; - main_loop(TRUE, FALSE); + normal_enter(true, false); RedrawingDisabled = i; diff --git a/src/nvim/main.c b/src/nvim/main.c index 60a242fae3..06fd116b86 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -528,228 +528,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 +1863,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..fe1eb5b4a0 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -65,6 +65,19 @@ #include "nvim/os/time.h" #include "nvim/os/input.h" +typedef struct normal_state { + linenr_T conceal_old_cursor_line; + linenr_T conceal_new_cursor_line; + bool conceal_update_lines; + 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 +} NormalState; + /* * The Visual area is remembered for reselection. */ @@ -79,6 +92,12 @@ 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)); +} + /* * nv_*(): functions called to handle Normal and Visual mode commands. * n_*(): functions called to handle Normal mode commands. @@ -418,6 +437,34 @@ static int find_command(int cmdchar) return idx; } +// 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; + + for (;;) { + int check_result = normal_check(&state); + if (!check_result) { + break; + } else if (check_result == -1) { + continue; + } + normal_cmd(&state.oa, true); + } +} + /* * Execute a command in Normal mode. */ @@ -1064,6 +1111,209 @@ normal_end: opcount = ca.opcount; } +// 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(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; + } + } + + // 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; + } + + 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) { + s->conceal_old_cursor_line = last_cursormoved.lnum; + s->conceal_new_cursor_line = curwin->w_cursor.lnum; + s->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) { + // 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 (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 (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); + } + + update_single_line(curwin, s->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; + } + } + + // 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; + } + + return !s->cmdwin || cmdwin_result == 0; +} + /* * Set v:count and v:count1 according to "cap". * Set v:prevcount only when "set_prevcount" is true. @@ -7376,3 +7626,4 @@ static int mouse_model_popup(void) { return p_mousem[0] == 'p'; } + From 82bb8c887c5625a77969c70a38ce94fce803301e Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Sun, 4 Oct 2015 09:38:26 -0300 Subject: [PATCH 03/23] normal: Extract most `normal_cmd` logic into two functions The new functions are `normal_prepare` and `normal_execute` which contain code executed before and after input is received in normal mode. --- src/nvim/normal.c | 78 ++++++++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/src/nvim/normal.c b/src/nvim/normal.c index fe1eb5b4a0..d2d734a677 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -69,6 +69,7 @@ typedef struct normal_state { linenr_T conceal_old_cursor_line; linenr_T conceal_new_cursor_line; 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 @@ -76,6 +77,7 @@ typedef struct normal_state { bool toplevel; // top-level normal mode oparg_T oa; // operator arguments cmdarg_T ca; // command arguments + int mapped_len; } NormalState; /* @@ -465,26 +467,11 @@ void normal_enter(bool cmdwin, bool noexmode) } } -/* - * Execute a command in Normal mode. - */ -void -normal_cmd ( - oparg_T *oap, - bool toplevel /* true when called from main() */ -) +static void normal_prepare(NormalState *s) { 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; - + oparg_T *oap = &s->oa; memset(&ca, 0, sizeof(ca)); /* also resets ca.retval */ ca.oap = oap; @@ -509,7 +496,7 @@ normal_cmd ( * count. */ if (!finish_op && !oap->regname) { ca.opcount = 0; - set_prevcount = true; + s->set_prevcount = true; } /* Restore counts from before receiving K_CURSORHOLD. This means after @@ -522,27 +509,31 @@ normal_cmd ( oap->prev_count0 = 0; } - mapped_len = typebuf_maplen(); + s->mapped_len = typebuf_maplen(); State = NORMAL_BUSY; /* 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); + if (s->toplevel && readbuf1_empty()) + set_vcount_ca(&ca, &s->set_prevcount); + s->ca = ca; +} - /* - * Get the command character from the user. - */ - input_enable_events(); - c = safe_vgetc(); - input_disable_events(); - - if (c == K_EVENT) { - queue_process_events(loop.events); - return; - } +static int normal_execute(NormalState *s, int c) +{ + cmdarg_T ca = s->ca; + 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 = s->mapped_len; + static int old_mapped_len = 0; + int idx; + bool set_prevcount = s->set_prevcount; + bool toplevel = s->toplevel; + oparg_T *oap = &s->oa; LANGMAP_ADJUST(c, true); @@ -1109,6 +1100,7 @@ normal_end: /* Save count before an operator for next time. */ opcount = ca.opcount; + return 1; } // Function executed before each iteration of normal mode. @@ -7627,3 +7619,25 @@ 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); + + input_enable_events(); + int c = safe_vgetc(); + input_disable_events(); + + if (c == K_EVENT) { + queue_process_events(loop.events); + goto end; + } + + (void)normal_execute(&s, c); + +end: + *oap = s.oa; +} From d8055f8eab7b1c3fb90f69e9795e9e2a88d0cd68 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Sun, 4 Oct 2015 10:05:54 -0300 Subject: [PATCH 04/23] normal: Fix code style in `normal_prepare` and `normal_execute` This was done separately to make it easier to follow the changes in the previous commit. --- src/nvim/normal.c | 686 ++++++++++++++++++++++------------------------ 1 file changed, 327 insertions(+), 359 deletions(-) diff --git a/src/nvim/normal.c b/src/nvim/normal.c index d2d734a677..11ef093ee7 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -469,137 +469,127 @@ void normal_enter(bool cmdwin, bool noexmode) static void normal_prepare(NormalState *s) { - cmdarg_T ca; /* command arguments */ - int c; - oparg_T *oap = &s->oa; - memset(&ca, 0, sizeof(ca)); /* also resets ca.retval */ - ca.oap = oap; + 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". */ - ca.opcount = opcount; + // 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). - */ - c = finish_op; - finish_op = (oap->op_type != OP_NOP); + // 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 */ + ui_cursor_shape(); // may show different cursor shape } - /* When not finishing an operator and no register name typed, reset the - * count. */ - if (!finish_op && !oap->regname) { - ca.opcount = 0; + // 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; } - /* 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; + // 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 (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; - /* 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(&ca, &s->set_prevcount); - s->ca = ca; + // 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); + } } static int normal_execute(NormalState *s, int c) { - cmdarg_T ca = s->ca; 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 = s->mapped_len; static int old_mapped_len = 0; int idx; - bool set_prevcount = s->set_prevcount; - bool toplevel = s->toplevel; - oparg_T *oap = &s->oa; LANGMAP_ADJUST(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) + // 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)) + } else if (old_mapped_len || (VIsual_active && s->mapped_len == 0 + && typebuf_maplen() > 0)) { old_mapped_len = typebuf_maplen(); + } - if (c == NUL) + if (c == NUL) { c = K_ZERO; + } - /* - * In Select mode, typed text replaces the selection. - */ - if (VIsual_active - && VIsual_select + // 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. */ + // 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) + if (restart_edit != 0) { c = 'd'; - else + } else { c = 'c'; - msg_nowait = true; /* don't delay going to insert mode */ - old_mapped_len = 0; /* do go to Insert mode */ + } + msg_nowait = true; // don't delay going to insert mode + old_mapped_len = 0; // do go to Insert mode } need_flushbuf = add_to_showcmd(c); 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'))) { + // 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') || (s->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); + s->ca.count0 /= 10; + del_from_showcmd(4); // delete the digit and ~@% + } else { + s->ca.count0 = s->ca.count0 * 10 + (c - '0'); + } + + if (s->ca.count0 < 0) { + // got too large! + s->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 (s->toplevel && readbuf1_empty()) { + set_vcount_ca(&s->ca, &s->set_prevcount); + } + if (ctrl_w) { ++no_mapping; - ++allow_keys; /* no mapping for nchar, but keys */ + ++allow_keys; // no mapping for nchar, but keys } - ++no_zero_mapping; /* don't map zero here */ + + ++no_zero_mapping; // don't map zero here c = plain_vgetc(); LANGMAP_ADJUST(c, true); --no_zero_mapping; @@ -610,91 +600,86 @@ getcount: need_flushbuf |= add_to_showcmd(c); } - /* - * If we got CTRL-W there may be a/another count - */ - if (c == Ctrl_W && !ctrl_w && oap->op_type == OP_NOP) { + // If we got CTRL-W there may be a/another count + if (c == Ctrl_W && !ctrl_w && s->oa.op_type == OP_NOP) { ctrl_w = true; - ca.opcount = ca.count0; /* remember first count */ - ca.count0 = 0; + s->ca.opcount = s->ca.count0; // remember first count + s->ca.count0 = 0; ++no_mapping; - ++allow_keys; /* no mapping for nchar, but keys */ - c = plain_vgetc(); /* get next character */ + ++allow_keys; // no mapping for nchar, but keys + c = plain_vgetc(); // get next character LANGMAP_ADJUST(c, true); --no_mapping; --allow_keys; need_flushbuf |= add_to_showcmd(c); - goto getcount; /* jump back */ + goto getcount; // jump back } } 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; + // Save the count values so that ca.opcount and ca.count0 are exactly + // the same when coming back here after handling K_CURSORHOLD. + 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; + } } - /* - * 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); + // 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); - /* - * 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); + // 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); + } - /* - * Find the command character in the table of commands. - * For CTRL-W we already got nchar when looking for a count. - */ + // 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); + s->ca.nchar = c; + s->ca.cmdchar = Ctrl_W; + } else { + s->ca.cmdchar = c; + } + + idx = find_command(s->ca.cmdchar); + if (idx < 0) { - /* Not a known command: beep. */ - clearopbeep(oap); + // Not a known command: beep. + clearopbeep(&s->oa); 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); + clearopbeep(&s->oa); text_locked_msg(); goto normal_end; } - 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 ((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 */ + // when 'keymodel' contains "stopsel" may stop Select/Visual mode if (km_stopsel && (nv_cmds[idx].cmd_flags & NV_STS) && !(mod_mask & MOD_MASK_SHIFT)) { @@ -702,14 +687,14 @@ getcount: redraw_curbuf_later(INVERTED); } - /* Keys that work different when 'keymodel' contains "startsel" */ + // 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); + unshift_special(&s->ca); + idx = find_command(s->ca.cmdchar); if (idx < 0) { - /* Just in case */ - clearopbeep(oap); + // Just in case + clearopbeep(&s->oa); goto normal_end; } } else if ((nv_cmds[idx].cmd_flags & NV_SSS) @@ -721,97 +706,94 @@ getcount: 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; + // 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 (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; } - idx = find_command(ca.cmdchar); + idx = find_command(s->ca.cmdchar); } - /* - * Get an additional character if we need one. - */ + // 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) + && s->oa.op_type == OP_NOP) || (nv_cmds[idx].cmd_flags & NV_NCH_ALW) == NV_NCH_ALW - || (ca.cmdchar == 'q' - && oap->op_type == OP_NOP + || (s->ca.cmdchar == 'q' + && s->oa.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 */ + || ((s->ca.cmdchar == 'a' || s->ca.cmdchar == 'i') + && (s->oa.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 ++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(). */ + ++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 */ + 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); + 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 (ca.cmdchar == 'r') /* get it in replace mode */ + if (s->ca.cmdchar == 'r') { + // get it in replace mode repl = true; - cp = &ca.nchar; + } + cp = &s->ca.nchar; } lang = (repl || (nv_cmds[idx].cmd_flags & NV_LANG)); - /* - * Get a second or third character. - */ + // Get a second or third character. if (cp != NULL) { if (repl) { - State = REPLACE; /* pretend Replace mode */ - ui_cursor_shape(); /* show different cursor shape */ + 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". */ + // Allow mappings defined with ":lmap". --no_mapping; --allow_keys; - if (repl) + if (repl) { State = LREPLACE; - else + } else { State = LANGMAP; + } langmap_active = true; } *cp = plain_vgetc(); if (langmap_active) { - /* Undo the decrement done above */ + // Undo the decrement done above ++no_mapping; ++allow_keys; State = NORMAL_BUSY; @@ -820,69 +802,69 @@ getcount: 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) + // Typing CTRL-K gets a digraph. + if (*cp == Ctrl_K && ((nv_cmds[idx].cmd_flags & NV_LANG) + || cp == &s->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... */ + // Guessing how to update showcmd here... del_from_showcmd(3); need_flushbuf |= add_to_showcmd(*cp); } } - /* adjust chars > 127, except after "tTfFr" commands */ + // adjust chars > 127, except after "tTfFr" commands LANGMAP_ADJUST(*cp, !lang); - /* adjust Hebrew mapped char */ - if (p_hkmap && lang && KeyTyped) + // adjust Hebrew mapped char + if (p_hkmap && lang && KeyTyped) { *cp = hkmap(*cp); - /* adjust Farsi mapped char */ - if (p_fkmap && lang && KeyTyped) + } + // adjust Farsi mapped char + if (p_fkmap && lang && KeyTyped) { *cp = fkmap(*cp); + } } - /* - * 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) { + // 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; + 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" and then - * something different from CTRL-N. Can't be avoided. */ + // There is a busy wait here when typing "f" 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) + if (c != Ctrl_N && c != Ctrl_G) { vungetc(c); - else { - ca.cmdchar = Ctrl_BSL; - ca.nchar = c; - idx = find_command(ca.cmdchar); + } else { + s->ca.cmdchar = Ctrl_BSL; + s->ca.nchar = c; + idx = find_command(s->ca.cmdchar); assert(idx >= 0); } } } - /* 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. */ + // 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)) { @@ -890,10 +872,11 @@ getcount: 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; + } else if (s->ca.ncharC1 == 0) { + s->ca.ncharC1 = c; + } else { + s->ca.ncharC2 = c; + } } no_mapping++; } @@ -901,40 +884,41 @@ getcount: --allow_keys; } - /* - * 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) + // 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) { ui_flush(); - if (ca.cmdchar != K_IGNORE) + } + if (s->ca.cmdchar != K_IGNORE) { 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; } - 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 */ + 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) { start_selection(); - unshift_special(&ca); - idx = find_command(ca.cmdchar); + unshift_special(&s->ca); + idx = find_command(s->ca.cmdchar); } else if ((nv_cmds[idx].cmd_flags & NV_SSS) && (mod_mask & MOD_MASK_SHIFT)) { start_selection(); @@ -942,164 +926,148 @@ getcount: } } - /* - * 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[idx].cmd_arg; + (nv_cmds[idx].cmd_func)(&s->ca); - /* - * If we didn't start or finish an operator, reset oap->regname, unless we - * need it later. - */ + // If we didn't start or finish an operator, reset oap->regname, unless we + // need it later. if (!finish_op - && !oap->op_type + && !s->oa.op_type && (idx < 0 || !(nv_cmds[idx].cmd_flags & NV_KEEPREG))) { - clearop(oap); + clearop(&s->oa); set_reg_var(get_default_register_name()); } - /* Get the length of mapped chars again after typing a count, second - * character or "z333". */ - if (old_mapped_len > 0) + // Get the length of mapped chars again after typing a count, second + // character or "z333". + if (old_mapped_len > 0) { old_mapped_len = typebuf_maplen(); + } - /* - * If an operation is pending, handle it... - */ - do_pending_operator(&ca, old_col, false); + // If an operation is pending, handle it... + do_pending_operator(&s->ca, 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 ( ((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) + // 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))) + && s->oa.regname == 0 + && !(s->ca.retval & CA_COMMAND_BUSY) && stuff_empty() && typebuf_typed() && emsg_silent == 0 && !did_wait_return - && oap->op_type == OP_NOP) { + && s->oa.op_type == OP_NOP) { int save_State = State; - /* Draw the cursor with the right shape here */ - if (restart_edit != 0) + // 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 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 */ + // 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 */ + // 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 */ + 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; } - /* - * Finish up after executing a Normal mode command. - */ + // Finish up after executing a Normal mode command. normal_end: msg_nowait = false; - /* Reset finish_op, in case it was set */ + // 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 */ + // Redraw the cursor with another shape, if we were in Operator-pending + // mode or did a replace command. + if (c || s->ca.cmdchar == 'r') { + ui_cursor_shape(); // may show different cursor shape } - if (oap->op_type == OP_NOP && oap->regname == 0 - && ca.cmdchar != K_CURSORHOLD - ) + if (s->oa.op_type == OP_NOP && s->oa.regname == 0 + && s->ca.cmdchar != K_CURSORHOLD) { clear_showcmd(); + } - checkpcmark(); /* check if we moved since setting pcmark */ - xfree(ca.searchbuf); + checkpcmark(); // check if we moved since setting pcmark + xfree(s->ca.searchbuf); - if (has_mbyte) + if (has_mbyte) { mb_adjust_cursor(); + } - if (curwin->w_p_scb && toplevel) { - validate_cursor(); /* may need to update w_leftcol */ + if (curwin->w_p_scb && s->toplevel) { + validate_cursor(); // may need to update w_leftcol do_check_scrollbind(true); } - if (curwin->w_p_crb && toplevel) { - validate_cursor(); /* may need to update w_leftcol */ + 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 ( oap->op_type == OP_NOP + // 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 && old_mapped_len == 0) || restart_VIsual_select == 1) - && !(ca.retval & CA_COMMAND_BUSY) + && !(s->ca.retval & CA_COMMAND_BUSY) && stuff_empty() - && oap->regname == 0) { + && s->oa.regname == 0) { if (restart_VIsual_select == 1) { VIsual_select = true; showmode(); restart_VIsual_select = 0; } - if (restart_edit != 0 - && !VIsual_active && old_mapped_len == 0 - ) + if (restart_edit != 0 && !VIsual_active && old_mapped_len == 0) { (void)edit(restart_edit, false, 1L); + } } - if (restart_VIsual_select == 2) + if (restart_VIsual_select == 2) { restart_VIsual_select = 1; + } - /* Save count before an operator for next time. */ - opcount = ca.opcount; + // Save count before an operator for next time + opcount = s->ca.opcount; return 1; } From e5165bae1139221ef752bccd582c7bd7474e6747 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Mon, 5 Oct 2015 10:13:18 -0300 Subject: [PATCH 05/23] input: Remove CURSORHOLD key Refactor input.c, normal.c and edit.c to use the K_EVENT special key to trigger the CURSORHOLD event. In normal and edit mode, K_EVENT is treated as K_CURSORHOLD, which enables better handling of arbitrary actions in those states(eg: In normal mode the previous operator counts will be restored). Also fix a test in vim_spec.lua. The test had a wrong assumption: cmdheight is only used to determine when the press enter screen will be shown, not to limit how many lines or control pagination. --- src/nvim/edit.c | 23 ++++++++----------- src/nvim/keymap.c | 1 - src/nvim/keymap.h | 2 -- src/nvim/normal.c | 38 ++++++++++---------------------- src/nvim/normal.h | 4 ++-- src/nvim/os/input.c | 29 ++++++++++++++++-------- test/functional/api/vim_spec.lua | 18 +++------------ 7 files changed, 46 insertions(+), 69 deletions(-) diff --git a/src/nvim/edit.c b/src/nvim/edit.c index b2f9601f82..67dbf8483b 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -626,14 +626,9 @@ edit ( } while (c == K_IGNORE); input_disable_events(); - if (c == K_EVENT) { - c = lastc; - queue_process_events(loop.events); - continue; - } - - /* Don't want K_CURSORHOLD for the second key, e.g., after CTRL-V. */ - did_cursorhold = TRUE; + // Don't want K_EVENT with cursorhold for the second key, e.g., after + // CTRL-V. + did_cursorhold = true; if (p_hkmap && KeyTyped) c = hkmap(c); /* Hebrew mode mapping */ @@ -974,9 +969,8 @@ doESCkey: case K_IGNORE: /* Something mapped to nothing */ break; - case K_CURSORHOLD: /* Didn't type something for a while. */ - apply_autocmds(EVENT_CURSORHOLDI, NULL, NULL, FALSE, curbuf); - did_cursorhold = TRUE; + case K_EVENT: // some event + queue_process_events(loop.events); break; case K_HOME: /* */ @@ -1223,9 +1217,10 @@ normalchar: break; } /* end of switch (c) */ - /* If typed something may trigger CursorHoldI again. */ - if (c != K_CURSORHOLD) - did_cursorhold = FALSE; + // If typed something may trigger CursorHoldI again. + if (c != K_EVENT) { + did_cursorhold = false; + } /* If the cursor was moved we didn't just insert a space */ if (arrow_used) 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/normal.c b/src/nvim/normal.c index 11ef093ee7..740c30d323 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -336,7 +336,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[]. */ @@ -492,8 +492,8 @@ static void normal_prepare(NormalState *s) s->set_prevcount = true; } - // Restore counts from before receiving K_CURSORHOLD. This means after - // typing "3", handling K_CURSORHOLD and then typing "2" we get "32", not + // 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; @@ -616,9 +616,9 @@ getcount: } } - if (c == K_CURSORHOLD) { + if (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_CURSORHOLD. + // 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) { @@ -891,7 +891,7 @@ getcount: if (need_flushbuf) { ui_flush(); } - if (s->ca.cmdchar != K_IGNORE) { + if (s->ca.cmdchar != K_IGNORE && s->ca.cmdchar != K_EVENT) { did_cursorhold = false; } @@ -1022,7 +1022,7 @@ normal_end: } if (s->oa.op_type == OP_NOP && s->oa.regname == 0 - && s->ca.cmdchar != K_CURSORHOLD) { + && s->ca.cmdchar != K_EVENT) { clear_showcmd(); } @@ -3147,7 +3147,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 }; @@ -7567,16 +7567,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 } /* @@ -7594,18 +7589,9 @@ void normal_cmd(oparg_T *oap, bool toplevel) s.toplevel = toplevel; s.oa = *oap; normal_prepare(&s); - input_enable_events(); int c = safe_vgetc(); input_disable_events(); - - if (c == K_EVENT) { - queue_process_events(loop.events); - goto end; - } - (void)normal_execute(&s, c); - -end: *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..9061a5ce2e 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -73,6 +73,21 @@ 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) { @@ -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); } } 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('') - 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('') -- exit the press ENTER screen end) end) From 32594a33a3d815b0da6634cb41692e412276cb91 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Mon, 5 Oct 2015 10:48:03 -0300 Subject: [PATCH 06/23] main: Call `normal_execute` from `normal_enter` `normal_prepare` is now called by `normal_check` before returning 1(to continue). Also remove `input_{enable,disable}_events` calls from `normal_cmd`, which only exists now as a compatibility function to run normal commands with keys inserted into the typeahead buffer(We don't want to process events in these cases anyway). --- src/nvim/normal.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 740c30d323..1f2d6f9caf 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -463,7 +463,13 @@ void normal_enter(bool cmdwin, bool noexmode) } else if (check_result == -1) { continue; } - normal_cmd(&state.oa, true); + + input_enable_events(); + int c = safe_vgetc(); + input_disable_events(); + if (!normal_execute(&state, c)) { + break; + } } } @@ -1271,7 +1277,13 @@ static int normal_check(NormalState *s) return -1; } - return !s->cmdwin || cmdwin_result == 0; + if (s->cmdwin && cmdwin_result != 0) { + // command-line window and cmdwin_result is set + return 0; + } + + normal_prepare(s); + return 1; } /* @@ -7589,9 +7601,6 @@ void normal_cmd(oparg_T *oap, bool toplevel) s.toplevel = toplevel; s.oa = *oap; normal_prepare(&s); - input_enable_events(); - int c = safe_vgetc(); - input_disable_events(); - (void)normal_execute(&s, c); + (void)normal_execute(&s, safe_vgetc()); *oap = s.oa; } From 350ffc63dbd26e17c389b35d4b8b36e4ef362137 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Mon, 5 Oct 2015 12:02:13 -0300 Subject: [PATCH 07/23] main: Refactor normal_enter to call `os_inchar` directly This makes it impossible for K_EVENT to interfere with mappings, but it also disables processing of events while in the middle of a mapping (Though this will be fixed later as this refactoring progresses). `may_sync_undo` is now called when K_EVENT is received. This is necessary to correctly update undo entry lists before executing some action. --- src/nvim/getchar.c | 2 +- src/nvim/normal.c | 32 ++++++++++++++++++++++++++++---- src/nvim/os/input.c | 11 ++++++++--- 3 files changed, 37 insertions(+), 8 deletions(-) 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/normal.c b/src/nvim/normal.c index 1f2d6f9caf..8f08fe2eed 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -464,10 +464,34 @@ void normal_enter(bool cmdwin, bool noexmode) continue; } - input_enable_events(); - int c = safe_vgetc(); - input_disable_events(); - if (!normal_execute(&state, c)) { + int key; + + 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(); + } + + if (!normal_execute(&state, key)) { break; } } diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 9061a5ce2e..df803609ae 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -91,7 +91,7 @@ static void create_cursorhold_event(void) // 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); } @@ -116,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); } @@ -324,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) { From 8d93621c6368fb6e3e3f7138bff50e08585f347b Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Mon, 5 Oct 2015 14:17:15 -0300 Subject: [PATCH 08/23] main: Start modeling Nvim as pushdown automaton From a very high level point of view, Vim/Nvim can be described as state machines following these instructions in a loop: - Read user input - Peform some action. The action is determined by the current state and can switch states. - Possibly display some feedback to the user. This is not immediately visible because these instructions spread across dozens of nested loops and function calls, making it very hard to modify the state machine(to support more event types, for example). So far, the approach Nvim has taken to allow more events is this: - At the very core function that blocks for input, poll for arbitrary events. - If the event received from the OS is user input, just return it normally to the callers. - If the event is not direct result of user input(possibly a vimscript function call coming from a msgpack-rpc socket or a job control callback), return a special key code(`K_EVENT`) that is handled by callers where it is safer to perform arbitrary actions. One problem with this approach is that the `K_EVENT` signal is being sent across multiple states that may be unaware of it. This was partially fixed with the `input_enable_events`/`input_disable_events` functions, which were added as a mechanism that the upper layers can use to tell the core input functions that it is ready to accept `K_EVENT`. Another problem is that the mapping engine is implemented in getchar.c which is called from every state, but the mapping engine is not aware of `K_EVENT` so events can break mappings. While it is theoretically possible to modify getchar.c to make it aware of `K_EVENT`, this commit fixes the problem with a different approach: Model Nvim as a pushdown automaton(https://en.wikipedia.org/wiki/Pushdown_automaton). This design has many advantages which include: - Decoupling the event loop from the states reponsible for handling events. - Better control of state transition with less dependency on global variable hacks(eg: 'restart_edit' global variable). - Easier removal of global variables and function splitting. That is because many variables are for state-specific information, and probably ended up being global to simplify communication between functions, which we fix by storing state-specific information in specialized structures. The final goal is to let Nvim have a single top-level event loop represented by the following pseudo-code: ``` while not quitting let event = read_event current_state(event) update_screen() ``` This closely mirrors the state machine description above and makes it easier to understand, extend and debug the program. Note that while the pseudo code suggests an explicit stack of states that doesn't rely on return addresses(as suggested by the principles of automata-based programming: https://en.wikipedia.org/wiki/Automata-based_programming), for now we'll use the call stack as a structure to manage state transitioning as it would be very difficult to refactor Nvim to use an explicit stack of states, and the benefits would be small. While this change may seem like an endless amount of work, it is possible to do it incrementally as was shown in the previous commits. The general procedure is: 1- Find a blocking `vgetc()`(or derivatives) call. This call represents an implicit state of the program. 2- Split the code before and after the `vgetc()` call into functions that match the signature of `state_check_callback` and `state_execute_callback. Only `state_execute_callback` is required. 3- Create a `VimState` "subclass" and a initializer function that sets the function pointers and performs any other required initialization steps. If the state has no local variables, just use `VimState` without subclassing. 4- Instead of calling the original function containing the `vgetc()`, initialize a stack-allocated `VimState` subclass, then call `state_enter` to begin processing events in the state. 5- The check/execute callbacks can return 1 to continue normally, 0 to break the loop or -1 to skip to the next iteration. These callbacks contain code that execute before and after the old `vgetc()` call. The functions created in step 2 may contain other `vgetc()` calls. These represent implicit sub-states of the current state, but it is fine to remove them later in smaller steps since we didn't break compatibility with existing code. --- src/nvim/main.c | 1 + src/nvim/normal.c | 53 ++++++++-------------------------------- src/nvim/state.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++ src/nvim/state.h | 20 +++++++++++++++ 4 files changed, 93 insertions(+), 43 deletions(-) create mode 100644 src/nvim/state.c create mode 100644 src/nvim/state.h diff --git a/src/nvim/main.c b/src/nvim/main.c index 06fd116b86..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" diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 8f08fe2eed..29e82c5e77 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -61,11 +61,13 @@ #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 conceal_update_lines; @@ -98,6 +100,8 @@ static int restart_VIsual_select = 0; static inline void normal_state_init(NormalState *s) { memset(s, 0, sizeof(NormalState)); + s->state.check = normal_check; + s->state.execute = normal_execute; } /* @@ -455,46 +459,7 @@ void normal_enter(bool cmdwin, bool noexmode) state.cmdwin = cmdwin; state.noexmode = noexmode; state.toplevel = !cmdwin && !noexmode; - - for (;;) { - int check_result = normal_check(&state); - if (!check_result) { - break; - } else if (check_result == -1) { - continue; - } - - int key; - - 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(); - } - - if (!normal_execute(&state, key)) { - break; - } - } + state_enter(&state.state); } static void normal_prepare(NormalState *s) @@ -543,8 +508,9 @@ static void normal_prepare(NormalState *s) } } -static int normal_execute(NormalState *s, int c) +static int normal_execute(VimState *state, int c) { + NormalState *s = (NormalState *)state; bool ctrl_w = false; /* got CTRL-W command */ int old_col = curwin->w_curswant; bool need_flushbuf; /* need to call ui_flush() */ @@ -1106,8 +1072,9 @@ normal_end: // 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(NormalState *s) +static int normal_check(VimState *state) { + NormalState *s = (NormalState *)state; if (stuff_empty()) { did_check_timestamps = false; @@ -7625,6 +7592,6 @@ void normal_cmd(oparg_T *oap, bool toplevel) s.toplevel = toplevel; s.oa = *oap; normal_prepare(&s); - (void)normal_execute(&s, safe_vgetc()); + (void)normal_execute(&s.state, safe_vgetc()); *oap = s.oa; } 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 + +#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 + +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 From fa83b03feacb596088be54fabe6853872bdabba5 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Tue, 6 Oct 2015 08:30:09 -0300 Subject: [PATCH 09/23] normal: Split `normal_check` into multiple functions Split most code in `normal_check` in: - `normal_check_stuff_buffer` - `normal_check_interrupt` - `normal_check_cursor_moved` - `normal_check_text_changed` - `normal_check_folds` - `normal_redraw` --- src/nvim/normal.c | 236 +++++++++++++++++++++++++--------------------- 1 file changed, 131 insertions(+), 105 deletions(-) diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 29e82c5e77..c96de8ea08 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1067,14 +1067,8 @@ normal_end: return 1; } -// 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) +static void normal_check_stuff_buffer(NormalState *s) { - NormalState *s = (NormalState *)state; if (stuff_empty()) { did_check_timestamps = false; @@ -1095,7 +1089,10 @@ static int normal_check(VimState *state) 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. @@ -1119,6 +1116,129 @@ static int normal_check(VimState *state) } else { s->previous_got_int = false; } +} + +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 (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; + } + + last_cursormoved = curwin->w_cursor; + } +} + +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); + } + + 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(); + + if (fdo_flags & FDO_ALL) { + foldOpenCursor(); + } + } +} + +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(); + } + + redraw_statuslines(); + + if (need_maketitle) { + maketitle(); + } + + // 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 (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 (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); + } + + update_single_line(curwin, s->conceal_new_cursor_line); + curwin->w_valid &= ~VALID_CROW; + } + + 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; @@ -1131,32 +1251,8 @@ static int normal_check(VimState *state) 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) { - s->conceal_old_cursor_line = last_cursormoved.lnum; - s->conceal_new_cursor_line = curwin->w_cursor.lnum; - s->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; - } + 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. @@ -1165,78 +1261,8 @@ static int normal_check(VimState *state) 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) { - // 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 (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 (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); - } - - update_single_line(curwin, s->conceal_new_cursor_line); - curwin->w_valid &= ~VALID_CROW; - } - - setcursor(); - + normal_check_folds(s); + normal_redraw(s); do_redraw = false; // Now that we have drawn the first screen all the startup stuff From 0f0fae58b9576b970deab58acc606454d550ff05 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Tue, 6 Oct 2015 09:31:50 -0300 Subject: [PATCH 10/23] normal: Extract some functions from `normal_execute` - `normal_handle_special_visual_command` - `normal_need_aditional_char` - `normal_get_additional_char` - `normal_invert_horizontal` --- src/nvim/normal.c | 506 +++++++++++++++++++++++++--------------------- 1 file changed, 272 insertions(+), 234 deletions(-) diff --git a/src/nvim/normal.c b/src/nvim/normal.c index c96de8ea08..1cc5d3591c 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -70,6 +70,7 @@ typedef struct normal_state { VimState state; linenr_T conceal_old_cursor_line; linenr_T conceal_new_cursor_line; + bool need_flushbuf; bool conceal_update_lines; bool set_prevcount; bool previous_got_int; // `got_int` was true @@ -80,6 +81,8 @@ typedef struct normal_state { oparg_T oa; // operator arguments cmdarg_T ca; // command arguments int mapped_len; + int idx; + int c; } NormalState; /* @@ -508,17 +511,241 @@ static void normal_prepare(NormalState *s) } } -static int normal_execute(VimState *state, int c) +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); + } + + // 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; +} + +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))); +} + +// 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)); + + // 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; + } + + *cp = plain_vgetc(); + + 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); + } + } + + // 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); + } + } + + // 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" 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); + } + } + } + + // 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; +} + +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); +} + +static int normal_execute(VimState *state, int key) { NormalState *s = (NormalState *)state; 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 */ static int old_mapped_len = 0; - int idx; + s->c = key; - LANGMAP_ADJUST(c, true); + 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 @@ -530,42 +757,42 @@ static int normal_execute(VimState *state, int c) old_mapped_len = typebuf_maplen(); } - if (c == NUL) { - c = K_ZERO; + if (s->c == NUL) { + s->c = K_ZERO; } // In Select mode, typed text replaces the selection. - if (VIsual_active && VIsual_select - && (vim_isprintc(c) || c == NL || c == CAR || c == K_KENTER)) { + 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(c); + ins_char_typebuf(s->c); if (restart_edit != 0) { - c = 'd'; + s->c = 'd'; } else { - c = 'c'; + s->c = 'c'; } msg_nowait = true; // don't delay going to insert mode old_mapped_len = 0; // do go to Insert mode } - need_flushbuf = add_to_showcmd(c); + s->need_flushbuf = add_to_showcmd(s->c); 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') || (s->ca.count0 != 0 - && (c == K_DEL || c == K_KDEL || c == '0'))) { - if (c == K_DEL || c == K_KDEL) { + 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 + (c - '0'); + s->ca.count0 = s->ca.count0 * 10 + (s->c - '0'); } if (s->ca.count0 < 0) { @@ -586,33 +813,33 @@ getcount: } ++no_zero_mapping; // don't map zero here - c = plain_vgetc(); - LANGMAP_ADJUST(c, true); + s->c = plain_vgetc(); + LANGMAP_ADJUST(s->c, true); --no_zero_mapping; if (ctrl_w) { --no_mapping; --allow_keys; } - need_flushbuf |= add_to_showcmd(c); + s->need_flushbuf |= add_to_showcmd(s->c); } // If we got CTRL-W there may be a/another count - if (c == Ctrl_W && !ctrl_w && s->oa.op_type == OP_NOP) { + if (s->c == Ctrl_W && !ctrl_w && s->oa.op_type == OP_NOP) { 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 - c = plain_vgetc(); // get next character - LANGMAP_ADJUST(c, true); + s->c = plain_vgetc(); // get next character + LANGMAP_ADJUST(s->c, true); --no_mapping; --allow_keys; - need_flushbuf |= add_to_showcmd(c); + s->need_flushbuf |= add_to_showcmd(s->c); goto getcount; // jump back } } - if (c == K_EVENT) { + 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; @@ -648,243 +875,54 @@ getcount: // Find the command character in the table of commands. // For CTRL-W we already got nchar when looking for a count. if (ctrl_w) { - s->ca.nchar = c; + s->ca.nchar = s->c; s->ca.cmdchar = Ctrl_W; } else { - s->ca.cmdchar = c; + s->ca.cmdchar = s->c; } - idx = find_command(s->ca.cmdchar); + s->idx = find_command(s->ca.cmdchar); - if (idx < 0) { + if (s->idx < 0) { // Not a known command: beep. clearopbeep(&s->oa); goto normal_end; } - if (text_locked() && (nv_cmds[idx].cmd_flags & NV_NCW)) { + 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(); goto normal_end; } - if ((nv_cmds[idx].cmd_flags & NV_NCW) && curbuf_locked()) { + if ((nv_cmds[s->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); - } - - // Keys that work different when 'keymodel' contains "startsel" - if (km_startsel) { - if (nv_cmds[idx].cmd_flags & NV_SS) { - unshift_special(&s->ca); - idx = find_command(s->ca.cmdchar); - if (idx < 0) { - // Just in case - clearopbeep(&s->oa); - goto normal_end; - } - } else if ((nv_cmds[idx].cmd_flags & NV_SSS) - && (mod_mask & MOD_MASK_SHIFT)) { - mod_mask &= ~MOD_MASK_SHIFT; - } - } + if (VIsual_active && normal_handle_special_visual_command(s)) { + goto normal_end; } if (curwin->w_p_rl && KeyTyped && !KeyStuffed - && (nv_cmds[idx].cmd_flags & NV_RL)) { + && (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". - 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; - } - idx = find_command(s->ca.cmdchar); + normal_invert_horizontal(s); } // 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 - && s->oa.op_type == OP_NOP) - || (nv_cmds[idx].cmd_flags & NV_NCH_ALW) == NV_NCH_ALW - || (s->ca.cmdchar == 'q' - && s->oa.op_type == OP_NOP - && !Recording - && !Exec_reg) - || ((s->ca.cmdchar == 'a' || s->ca.cmdchar == 'i') - && (s->oa.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 - - ++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); - 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[idx].cmd_flags & NV_LANG)); - - // 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; - } - - *cp = plain_vgetc(); - - 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 == &s->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); - } - } - - // 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); - } - } - - // 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; - 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" 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 { - s->ca.cmdchar = Ctrl_BSL; - s->ca.nchar = c; - idx = find_command(s->ca.cmdchar); - assert(idx >= 0); - } - } - } - - // 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 (s->ca.ncharC1 == 0) { - s->ca.ncharC1 = c; - } else { - s->ca.ncharC2 = c; - } - } - no_mapping++; - } - --no_mapping; - --allow_keys; + 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 (need_flushbuf) { + if (s->need_flushbuf) { ui_flush(); } if (s->ca.cmdchar != K_IGNORE && s->ca.cmdchar != K_EVENT) { @@ -911,11 +949,11 @@ getcount: // 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(&s->ca); - idx = find_command(s->ca.cmdchar); - } else if ((nv_cmds[idx].cmd_flags & NV_SSS) + 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; @@ -924,14 +962,14 @@ getcount: // Execute the command! // Call the command function found in the commands table. - s->ca.arg = nv_cmds[idx].cmd_arg; - (nv_cmds[idx].cmd_func)(&s->ca); + 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 && !s->oa.op_type - && (idx < 0 || !(nv_cmds[idx].cmd_flags & NV_KEEPREG))) { + && (s->idx < 0 || !(nv_cmds[s->idx].cmd_flags & NV_KEEPREG))) { clearop(&s->oa); set_reg_var(get_default_register_name()); } @@ -1009,11 +1047,11 @@ normal_end: msg_nowait = false; // Reset finish_op, in case it was set - c = finish_op; + 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 (c || s->ca.cmdchar == 'r') { + if (s->c || s->ca.cmdchar == 'r') { ui_cursor_shape(); // may show different cursor shape } From 5abd25f6fbbdf5a72da8d7f196310229939a4b41 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Tue, 6 Oct 2015 09:47:57 -0300 Subject: [PATCH 11/23] normal: Extract `normal_get_command_count` from `normal_execute` --- src/nvim/normal.c | 126 ++++++++++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 59 deletions(-) diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 1cc5d3591c..3b23c4e7dc 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -70,6 +70,7 @@ typedef struct normal_state { VimState state; linenr_T conceal_old_cursor_line; linenr_T conceal_new_cursor_line; + bool ctrl_w; bool need_flushbuf; bool conceal_update_lines; bool set_prevcount; @@ -736,10 +737,73 @@ static void normal_invert_horizontal(NormalState *s) s->idx = find_command(s->ca.cmdchar); } +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'); + } + + if (s->ca.count0 < 0) { + // got too large! + s->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 (s->toplevel && readbuf1_empty()) { + set_vcount_ca(&s->ca, &s->set_prevcount); + } + + if (s->ctrl_w) { + ++no_mapping; + ++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; + } + s->need_flushbuf |= add_to_showcmd(s->c); + } + + // 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; + } + + return false; +} + static int normal_execute(VimState *state, int key) { NormalState *s = (NormalState *)state; - bool ctrl_w = false; /* got CTRL-W command */ + s->ctrl_w = false; /* got CTRL-W command */ int old_col = curwin->w_curswant; pos_T old_pos; /* cursor position before command */ static int old_mapped_len = 0; @@ -781,63 +845,7 @@ static int normal_execute(VimState *state, int key) s->need_flushbuf = add_to_showcmd(s->c); -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 ((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'); - } - - if (s->ca.count0 < 0) { - // got too large! - s->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 (s->toplevel && readbuf1_empty()) { - set_vcount_ca(&s->ca, &s->set_prevcount); - } - - if (ctrl_w) { - ++no_mapping; - ++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 (ctrl_w) { - --no_mapping; - --allow_keys; - } - s->need_flushbuf |= add_to_showcmd(s->c); - } - - // If we got CTRL-W there may be a/another count - if (s->c == Ctrl_W && !ctrl_w && s->oa.op_type == OP_NOP) { - 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); - goto getcount; // jump back - } - } + 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 @@ -874,7 +882,7 @@ getcount: // Find the command character in the table of commands. // For CTRL-W we already got nchar when looking for a count. - if (ctrl_w) { + if (s->ctrl_w) { s->ca.nchar = s->c; s->ca.cmdchar = Ctrl_W; } else { From 4c6e417d2a72dc6841dd76c74ec5bac19c9af227 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Tue, 6 Oct 2015 10:43:55 -0300 Subject: [PATCH 12/23] normal: Extract `normal_finish_command` from `normal_execute` --- src/nvim/normal.c | 370 ++++++++++++++++++++++++---------------------- 1 file changed, 194 insertions(+), 176 deletions(-) diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 3b23c4e7dc..de884acdcb 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -70,6 +70,7 @@ 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; @@ -82,8 +83,11 @@ typedef struct normal_state { 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; /* @@ -800,179 +804,12 @@ static bool normal_get_command_count(NormalState *s) return false; } -static int normal_execute(VimState *state, int key) +static void normal_finish_command(NormalState *s) { - NormalState *s = (NormalState *)state; - s->ctrl_w = false; /* got CTRL-W command */ - int old_col = curwin->w_curswant; - pos_T old_pos; /* cursor position before command */ - static int old_mapped_len = 0; - 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) { - old_mapped_len = 0; - } else if (old_mapped_len || (VIsual_active && s->mapped_len == 0 - && typebuf_maplen() > 0)) { - 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 { - s->c = 'c'; - } - msg_nowait = true; // don't delay going to insert mode - 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; - } - } - - // 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); - - // 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); - } - - // 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; - } - - s->idx = find_command(s->ca.cmdchar); - - if (s->idx < 0) { - // Not a known command: beep. - clearopbeep(&s->oa); + if (s->command_finished) { goto normal_end; } - 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(); - goto normal_end; - } - - if ((nv_cmds[s->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 && normal_handle_special_visual_command(s)) { - goto normal_end; - } - - 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 (s->ca.cmdchar != K_IGNORE && s->ca.cmdchar != K_EVENT) { - did_cursorhold = false; - } - - State = NORMAL; - - if (s->ca.nchar == ESC) { - clearop(&s->oa); - if (restart_edit == 0 && goto_im()) { - restart_edit = 'a'; - } - goto normal_end; - } - - 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 - - // When 'keymodel' contains "startsel" some keys start Select/Visual - // mode. - if (!VIsual_active && km_startsel) { - if (nv_cmds[s->idx].cmd_flags & NV_SS) { - start_selection(); - 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. - 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 @@ -984,12 +821,12 @@ static int normal_execute(VimState *state, int key) // Get the length of mapped chars again after typing a count, second // character or "z333". - if (old_mapped_len > 0) { - old_mapped_len = typebuf_maplen(); + if (s->old_mapped_len > 0) { + s->old_mapped_len = typebuf_maplen(); } // If an operation is pending, handle it... - do_pending_operator(&s->ca, old_col, false); + 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. @@ -1002,8 +839,8 @@ static int normal_execute(VimState *state, int key) // 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)) + && s->old_pos.lnum == curwin->w_cursor.lnum + && s->old_pos.col == curwin->w_cursor.col)) && (clear_cmdline || redraw_cmdline) && (msg_didout || (msg_didany && msg_scroll)) && !msg_nowait @@ -1089,7 +926,7 @@ normal_end: // 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 && old_mapped_len == 0) + && ((restart_edit != 0 && !VIsual_active && s->old_mapped_len == 0) || restart_VIsual_select == 1) && !(s->ca.retval & CA_COMMAND_BUSY) && stuff_empty() @@ -1099,7 +936,7 @@ normal_end: showmode(); restart_VIsual_select = 0; } - if (restart_edit != 0 && !VIsual_active && old_mapped_len == 0) { + if (restart_edit != 0 && !VIsual_active && s->old_mapped_len == 0) { (void)edit(restart_edit, false, 1L); } } @@ -1110,6 +947,187 @@ normal_end: // Save count before an operator for next time opcount = s->ca.opcount; +} + +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 { + s->c = 'c'; + } + 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; + } + } + + // 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); + + // 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); + } + + // 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; + } + + s->idx = find_command(s->ca.cmdchar); + + if (s->idx < 0) { + // Not a known command: beep. + clearopbeep(&s->oa); + s->command_finished = true; + goto finish; + } + + 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; + } + + 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 (s->ca.cmdchar != K_IGNORE && s->ca.cmdchar != K_EVENT) { + did_cursorhold = false; + } + + State = NORMAL; + + if (s->ca.nchar == ESC) { + clearop(&s->oa); + if (restart_edit == 0 && goto_im()) { + restart_edit = 'a'; + } + s->command_finished = true; + goto finish; + } + + if (s->ca.cmdchar != K_IGNORE) { + msg_didout = false; // don't scroll screen up for normal command + msg_col = 0; + } + + s->old_pos = curwin->w_cursor; // remember where cursor was + + // When 'keymodel' contains "startsel" some keys start Select/Visual + // mode. + if (!VIsual_active && km_startsel) { + if (nv_cmds[s->idx].cmd_flags & NV_SS) { + start_selection(); + 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. + s->ca.arg = nv_cmds[s->idx].cmd_arg; + (nv_cmds[s->idx].cmd_func)(&s->ca); + +finish: + normal_finish_command(s); return 1; } From f1bc3e4c0cdf2c85c3cd69589e56d6c83b1aa350 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Tue, 6 Oct 2015 14:01:02 -0300 Subject: [PATCH 13/23] normal: Extract some functions from `normal_finish_command` - `normal_need_redraw_mode_message` - `normal_redraw_mode_message` --- src/nvim/normal.c | 118 ++++++++++++++++++++++++++++------------------ 1 file changed, 72 insertions(+), 46 deletions(-) diff --git a/src/nvim/normal.c b/src/nvim/normal.c index de884acdcb..de575c0234 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -572,6 +572,76 @@ static bool normal_need_aditional_char(NormalState *s) || ((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) { @@ -838,52 +908,8 @@ static void normal_finish_command(NormalState *s) // 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 - && s->old_pos.lnum == curwin->w_cursor.lnum - && s->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))) - && 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) { - 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; + if (normal_need_redraw_mode_message(s)) { + normal_redraw_mode_message(s); } // Finish up after executing a Normal mode command. From f5b333f532082be88a113fa3c4c337d0b672c2ab Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Mon, 12 Oct 2015 09:25:01 -0300 Subject: [PATCH 14/23] edit: Extract local variables from edit() and fix code style Begin refactoring edit() into a state that can be managed by the `state_enter()`: - Move local variables into a local InsertState structure - Fix code style in the entire function. --- src/nvim/edit.c | 1083 +++++++++++++++++++++++++---------------------- 1 file changed, 572 insertions(+), 511 deletions(-) diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 67dbf8483b..c6ebe376e2 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,8 +251,9 @@ 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. @@ -256,6 +280,9 @@ edit ( long count ) { + static linenr_T o_lnum = 0; + InsertState state, *s = &state; + memset(s, 0, sizeof(InsertState)); if (curbuf->terminal) { if (ex_normal_busy) { // don't enter terminal mode from `ex_normal`, which can result in all @@ -270,71 +297,63 @@ edit ( return false; } - 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. */ + s->cmdchar = cmdchar; + s->startln = startln; + s->count = count; + 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; + return false; } - /* Don't allow changes in the buffer while editing the cmdline. The - * caller of getcmdline() may get confused. */ + + // 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; + return false; } - /* Don't allow recursive insert mode when busy with completion. */ + // Don't allow recursive insert mode when busy with completion. if (compl_started || compl_busy || pum_visible()) { EMSG(_(e_secure)); - return FALSE; + return false; } - ins_compl_clear(); /* clear stuff for CTRL-X mode */ - /* - * Trigger InsertEnter autocommands. Do not do this for "r" or "grx". - */ + ins_compl_clear(); // clear stuff for CTRL-X mode + + // Trigger InsertEnter autocommands. Do not do this for "r" or "grx". if (cmdchar != 'r' && 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); - set_vim_var_string(VV_CHAR, NULL, -1); /* clear v:char */ - apply_autocmds(EVENT_INSERTENTER, NULL, NULL, FALSE, curbuf); + if (cmdchar == 'R') { + s->ptr = (char_u *)"r"; + } else if (cmdchar == 'V') { + s->ptr = (char_u *)"v"; + } else { + s->ptr = (char_u *)"i"; + } - /* 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. */ + 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. if (!equalpos(curwin->w_cursor, save_cursor) && *get_vim_var_str(VV_CHAR) == NUL && save_cursor.lnum <= curbuf->b_ml.ml_line_count) { @@ -347,316 +366,316 @@ 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 (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) { ResetRedobuff(); AppendNumberToRedobuff(count); if (cmdchar == 'V' || cmdchar == 'v') { - /* "gR" or "gr" command */ + // "gR" or "gr" command AppendCharToRedobuff('g'); AppendCharToRedobuff((cmdchar == 'v') ? 'r' : 'R'); } else { AppendCharToRedobuff(cmdchar); - if (cmdchar == 'g') /* "gI" command */ + if (cmdchar == 'g') { // "gI" command AppendCharToRedobuff('I'); - else if (cmdchar == 'r') /* "r" command */ - count = 1; /* insert only one */ + } else if (cmdchar == 'r') { // "r" command + count = 1; // insert only one + } } } if (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') { 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 . - */ - 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 . + 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. - */ + + // Main loop in Insert mode: repeat until Insert mode is left. for (;; ) { - if (!revins_legal) - revins_scol = -1; /* reset on illegal motions */ - else + 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 */ + } + + if (arrow_used) { // don't repeat insert when arrow key used count = 0; + } if (update_Insstart_orig) { Insstart_orig = Insstart; } if (stop_insert_mode) { - /* ":stopinsert" used or 'insertmode' reset */ + // ":stopinsert" used or 'insertmode' reset count = 0; goto doESCkey; } - /* 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); + // set curwin->w_curswant for next K_DOWN or K_UP + if (!arrow_used) { + curwin->w_set_curswant = true; } - /* - * 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(); - - /* - * 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); + // 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); } } - /* May need to adjust w_topline to show the cursor. */ + // 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(); + } + + // 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(); - 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) + if (curwin->w_p_crb) { do_check_cursorbind(); + } + update_curswant(); - old_topline = curwin->w_topline; - old_topfill = curwin->w_topfill; + s->old_topline = curwin->w_topline; + s->old_topfill = curwin->w_topfill; - - /* - * Get a character for Insert mode. Ignore K_IGNORE. - */ - lastc = c; /* remember previous char for CTRL-D */ + // Get a character for Insert mode. Ignore K_IGNORE. + s->lastc = s->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; + if (dont_sync_undo == MAYBE) { + dont_sync_undo = true; + } else { + dont_sync_undo = false; + } input_enable_events(); do { - c = safe_vgetc(); - } while (c == K_IGNORE); + s->c = safe_vgetc(); + } while (s->c == K_IGNORE); input_disable_events(); // Don't want K_EVENT with cursorhold for the second key, e.g., after // CTRL-V. did_cursorhold = true; - if (p_hkmap && KeyTyped) - c = hkmap(c); /* Hebrew mode mapping */ - if (p_fkmap && KeyTyped) - c = fkmap(c); /* Farsi mode mapping */ + if (p_hkmap && KeyTyped) { + s->c = hkmap(s->c); // Hebrew 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 (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) + // BS: Delete one character from "compl_leader". + if ((s->c == K_BS || s->c == Ctrl_H) && curwin->w_cursor.col > compl_col - && (c = ins_compl_bs()) == NUL) + && (s->c = ins_compl_bs()) == NUL) { continue; + } - /* When no match was selected or it was edited. */ + // 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-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)) { @@ -664,124 +683,132 @@ edit ( 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); + // 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; if (str != NULL) { - for (p = str; *p != NUL; mb_ptr_adv(p)) + for (p = str; *p != NUL; mb_ptr_adv(p)) { ins_compl_addleader(PTR2CHAR(p)); + } xfree(str); - } else - ins_compl_addleader(c); + } else { + ins_compl_addleader(s->c); + } continue; } - /* 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))) { + // 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)) + // 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)) { 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); + // 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; - c = plain_vgetc(); + s->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) + 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) { continue; - else { - if (c == Ctrl_O) { + } else { + if (s->c == Ctrl_O) { ins_ctrl_o(); - ins_at_eol = FALSE; /* cursor keeps its column */ - nomove = TRUE; + ins_at_eol = false; // cursor keeps its column + s->nomove = true; } count = 0; goto doESCkey; } } - c = do_digraph(c); + s->c = do_digraph(s->c); - if ((c == Ctrl_V || c == Ctrl_Q) && ctrl_x_mode == CTRL_X_CMDLINE) + if ((s->c == Ctrl_V || s->c == Ctrl_Q) && ctrl_x_mode == CTRL_X_CMDLINE) { goto docomplete; - if (c == Ctrl_V || c == Ctrl_Q) { + } + + if (s->c == Ctrl_V || s->c == Ctrl_Q) { ins_ctrl_v(); - c = Ctrl_V; /* pretend CTRL-V is last typed character */ + s->c = Ctrl_V; // pretend CTRL-V is last typed character 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)) + && 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)) { goto force_cindent; - if (can_cindent && in_cinkeys(c, '*', line_is_white) - && stop_arrow() == OK) + } + + 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; + 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)) + // 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)) { continue; + } - /* - * The big switch to handle a character in insert mode. - */ - switch (c) { - case ESC: /* End input mode */ - if (echeck_abbr(ESC + ABBR_OFF)) + // 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*/ + } + // FALLTHROUGH - case Ctrl_C: /* End input mode */ - if (c == Ctrl_C && cmdwin_type != 0) { - /* Close the cmdline window. */ + 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. */ - nomove = TRUE; + got_int = false; // don't stop executing autocommands et al + s->nomove = true; goto doESCkey; } @@ -792,7 +819,7 @@ do_intr: // Insert mode if (goto_im()) { if (got_int) { - (void)vgetc(); /* flush all buffers */ + (void)vgetc(); // flush all buffers got_int = false; } else { vim_beep(BO_IM); @@ -800,137 +827,148 @@ do_intr: 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) + // 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; + if (ins_esc(&count, cmdchar, s->nomove)) { + if (cmdchar != 'r' && cmdchar != 'v') { + apply_autocmds(EVENT_INSERTLEAVE, NULL, NULL, false, curbuf); + } + did_cursorhold = false; + return s->c == Ctrl_O; } continue; - case Ctrl_Z: /* suspend when 'insertmode' set */ - if (!p_im) - goto normalchar; /* insert CTRL-Z as normal char */ + 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*/ + s->c = Ctrl_O; + // FALLTHROUGH - case Ctrl_O: /* execute one command */ - if (ctrl_x_mode == CTRL_X_OMNI) + case Ctrl_O: // execute one command + if (ctrl_x_mode == CTRL_X_OMNI) { goto docomplete; - if (echeck_abbr(Ctrl_O + ABBR_OFF)) + } + + if (echeck_abbr(Ctrl_O + ABBR_OFF)) { break; + } + ins_ctrl_o(); - /* don't move the cursor left when 'virtualedit' has "onemore". */ + // don't move the cursor left when 'virtualedit' has "onemore". if (ve_flags & VE_ONEMORE) { - ins_at_eol = FALSE; - nomove = TRUE; + ins_at_eol = false; + s->nomove = true; } + count = 0; goto doESCkey; - case K_INS: /* toggle insert/replace mode */ + case K_INS: // toggle insert/replace mode case K_KINS: - ins_insert(replaceState); + ins_insert(s->replaceState); break; - case K_SELECT: /* end of Select mode mapping - ignore */ + case K_SELECT: // end of Select mode mapping - ignore break; - case K_HELP: /* Help key works like */ + case K_HELP: // Help key works like case K_F1: case K_XF1: stuffcharReadbuff(K_HELP); - if (p_im) - need_start_insertmode = TRUE; + if (p_im) { + need_start_insertmode = true; + } goto doESCkey; - case K_ZERO: /* Insert the previously inserted text. */ + 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; + // 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) + goto doESCkey; // quit insert mode + s->inserted_space = false; break; - case Ctrl_R: /* insert the contents of a register */ + case Ctrl_R: // insert the contents of a register ins_reg(); - auto_format(FALSE, TRUE); - inserted_space = FALSE; + auto_format(false, true); + s->inserted_space = false; break; - case Ctrl_G: /* commands starting with CTRL-G */ + case Ctrl_G: // commands starting with CTRL-G ins_ctrl_g(); break; - case Ctrl_HAT: /* switch input mode and/or langmap */ + case Ctrl_HAT: // switch input mode and/or langmap ins_ctrl_hat(); break; - case Ctrl__: /* switch between languages */ - if (!p_ari) + 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) + case Ctrl_D: // Make indent one shiftwidth smaller. + if (ctrl_x_mode == CTRL_X_PATH_DEFINES) { goto docomplete; - /* FALLTHROUGH */ + } + // FALLTHROUGH - case Ctrl_T: /* Make indent one shiftwidth greater. */ - if (c == Ctrl_T && ctrl_x_mode == CTRL_X_THESAURUS) { - if (has_compl_option(FALSE)) + case Ctrl_T: // Make indent one shiftwidth greater. + if (s->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; + ins_shift(s->c, s->lastc); + auto_format(false, true); + s->inserted_space = false; break; - case K_DEL: /* delete character under the cursor */ + case K_DEL: // delete character under the cursor case K_KDEL: ins_del(); - auto_format(FALSE, TRUE); + auto_format(false, true); break; - case K_BS: /* delete character before the cursor */ + case K_BS: // delete character before the cursor case Ctrl_H: - did_backspace = ins_bs(c, BACKSPACE_CHAR, &inserted_space); - auto_format(FALSE, TRUE); + s->did_backspace = ins_bs(s->c, BACKSPACE_CHAR, &s->inserted_space); + auto_format(false, true); break; - case Ctrl_W: /* delete word before the cursor */ - did_backspace = ins_bs(c, BACKSPACE_WORD, &inserted_space); - auto_format(FALSE, TRUE); + 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 Ctrl_U: /* delete all inserted text in current line */ - /* CTRL-X CTRL-U completes with 'completefunc'. */ - if (ctrl_x_mode == CTRL_X_FUNCTION) + 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; + } + + s->did_backspace = ins_bs(s->c, BACKSPACE_LINE, &s->inserted_space); + auto_format(false, true); + s->inserted_space = false; break; - case K_LEFTMOUSE: /* mouse keys */ + case K_LEFTMOUSE: // mouse keys case K_LEFTMOUSE_NM: case K_LEFTDRAG: case K_LEFTRELEASE: @@ -947,301 +985,324 @@ doESCkey: case K_X2MOUSE: case K_X2DRAG: case K_X2RELEASE: - ins_mouse(c); + ins_mouse(s->c); break; - case K_MOUSEDOWN: /* Default action for scroll wheel up: scroll up */ + case K_MOUSEDOWN: // Default action for scroll wheel up: scroll up ins_mousescroll(MSCR_DOWN); break; - case K_MOUSEUP: /* Default action for scroll wheel down: scroll down */ + case K_MOUSEUP: // Default action for scroll wheel down: scroll down ins_mousescroll(MSCR_UP); break; - case K_MOUSELEFT: /* Scroll wheel left */ + case K_MOUSELEFT: // Scroll wheel left ins_mousescroll(MSCR_LEFT); break; - case K_MOUSERIGHT: /* Scroll wheel right */ + case K_MOUSERIGHT: // Scroll wheel right ins_mousescroll(MSCR_RIGHT); break; - case K_IGNORE: /* Something mapped to nothing */ + case K_IGNORE: // Something mapped to nothing break; case K_EVENT: // some event queue_process_events(loop.events); break; - case K_HOME: /* */ + case K_HOME: // case K_KHOME: case K_S_HOME: case K_C_HOME: - ins_home(c); + ins_home(s->c); break; - case K_END: /* */ + case K_END: // case K_KEND: case K_S_END: case K_C_END: - ins_end(c); + ins_end(s->c); break; - case K_LEFT: /* */ - if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)) + case K_LEFT: // + if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)) { ins_s_left(); - else + } else { ins_left(dont_sync_undo == false); + } break; - case K_S_LEFT: /* */ + case K_S_LEFT: // case K_C_LEFT: ins_s_left(); break; - case K_RIGHT: /* */ - if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)) + case K_RIGHT: // + if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)) { ins_s_right(); - else + } else { ins_right(dont_sync_undo == false); + } break; - case K_S_RIGHT: /* */ + case K_S_RIGHT: // case K_C_RIGHT: ins_s_right(); break; - case K_UP: /* */ - if (pum_visible()) + case K_UP: // + if (pum_visible()) { goto docomplete; - if (mod_mask & MOD_MASK_SHIFT) + } + + if (mod_mask & MOD_MASK_SHIFT) { ins_pageup(); - else - ins_up(FALSE); + } else { + ins_up(false); + } break; - case K_S_UP: /* */ + case K_S_UP: // case K_PAGEUP: case K_KPAGEUP: - if (pum_visible()) + if (pum_visible()) { goto docomplete; + } ins_pageup(); break; - case K_DOWN: /* */ - if (pum_visible()) + case K_DOWN: // + if (pum_visible()) { goto docomplete; - if (mod_mask & MOD_MASK_SHIFT) + } + + if (mod_mask & MOD_MASK_SHIFT) { ins_pagedown(); - else - ins_down(FALSE); + } else { + ins_down(false); + } break; - case K_S_DOWN: /* */ + case K_S_DOWN: // case K_PAGEDOWN: case K_KPAGEDOWN: - if (pum_visible()) + if (pum_visible()) { goto docomplete; + } + 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) + 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); + } + s->inserted_space = false; + if (ins_tab()) { + goto normalchar; // insert TAB as a normal char + } + auto_format(false, true); break; - case K_KENTER: /* */ - c = CAR; - /* FALLTHROUGH */ + case K_KENTER: // + s->c = CAR; + // FALLTHROUGH case CAR: case NL: - /* In a quickfix window a jumps to the error under the - * cursor. */ - if (bt_quickfix(curbuf) && c == CAR) { - if (curwin->w_llist_ref == NULL) /* quickfix window */ + // In a quickfix window a 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 */ + } else { // location list window do_cmdline_cmd(".ll"); + } break; } if (cmdwin_type != 0) { - /* Execute the command in the cmdline window. */ + // Execute the command in the cmdline window. cmdwin_result = CAR; goto doESCkey; } - if (ins_eol(c) && !p_im) - goto doESCkey; /* out of memory */ - auto_format(FALSE, FALSE); - inserted_space = FALSE; + if (ins_eol(s->c) && !p_im) { + goto doESCkey; // out of memory + } + auto_format(false, false); + s->inserted_space = false; break; - case Ctrl_K: /* digraph or keyword completion */ + case Ctrl_K: // digraph or keyword completion if (ctrl_x_mode == CTRL_X_DICTIONARY) { - if (has_compl_option(TRUE)) + if (has_compl_option(true)) { goto docomplete; + } break; } - c = ins_digraph(); - if (c == NUL) + + s->c = ins_digraph(); + if (s->c == NUL) { break; + } goto normalchar; - case Ctrl_X: /* Enter CTRL-X mode */ + case Ctrl_X: // Enter CTRL-X mode ins_ctrl_x(); break; - case Ctrl_RSB: /* Tag name completion after ^X */ - if (ctrl_x_mode != CTRL_X_TAGS) + case Ctrl_RSB: // Tag name completion after ^X + if (ctrl_x_mode != CTRL_X_TAGS) { goto normalchar; + } goto docomplete; - case Ctrl_F: /* File name completion after ^X */ - if (ctrl_x_mode != CTRL_X_FILES) + case Ctrl_F: // File name completion after ^X + if (ctrl_x_mode != CTRL_X_FILES) { goto normalchar; + } goto docomplete; - case 's': /* Spelling completion after ^X */ + case 's': // Spelling completion after ^X case Ctrl_S: - if (ctrl_x_mode != CTRL_X_SPELL) + if (ctrl_x_mode != CTRL_X_SPELL) { goto normalchar; + } goto docomplete; - case Ctrl_L: /* Whole line completion after ^X */ + case Ctrl_L: // Whole line completion after ^X if (ctrl_x_mode != CTRL_X_WHOLE_LINE) { - /* CTRL-L with 'insertmode' set: Leave Insert mode */ + // CTRL-L with 'insertmode' set: Leave Insert mode if (p_im) { - if (echeck_abbr(Ctrl_L + ABBR_OFF)) + if (echeck_abbr(Ctrl_L + ABBR_OFF)) { break; + } goto doESCkey; } goto normalchar; } - /* FALLTHROUGH */ + // FALLTHROUGH - case Ctrl_P: /* Do previous/next pattern completion */ + 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 '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)) + && !(compl_cont_status & CONT_LOCAL)) { goto normalchar; + } docomplete: - compl_busy = TRUE; - if (ins_complete(c) == FAIL) + compl_busy = true; + if (ins_complete(s->c) == FAIL) { compl_cont_status = 0; - compl_busy = FALSE; + } + compl_busy = false; break; - 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); + 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; default: #ifdef UNIX - if (c == intr_char) /* special interrupt char */ + if (s->c == intr_char) // special interrupt char goto do_intr; #endif normalchar: - /* - * Insert a normal character. - */ + // Insert a normal character. if (!p_paste) { - /* Trigger InsertCharPre. */ - char_u *str = do_insert_char_pre(c); + // 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. */ + // 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); + 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; + s->c = NUL; } - /* If the new value is already inserted or an empty string - * then don't insert any character. */ - if (c == NUL) + // 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(c); + // Try to perform smart-indenting. + ins_try_si(s->c); - if (c == ' ') { - inserted_space = TRUE; - if (inindent(0)) - can_cindent = FALSE; + if (s->c == ' ') { + s->inserted_space = true; + if (inindent(0)) { + can_cindent = false; + } if (Insstart_blank_vcol == MAXCOL - && curwin->w_cursor.lnum == Insstart.lnum) + && 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); + // 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. */ + // When inserting a character the cursor line must never be in a + // closed fold. foldOpenCursor(); break; - } /* end of switch (c) */ + } // end of switch (s->c) // If typed something may trigger CursorHoldI again. - if (c != K_EVENT) { + if (s->c != K_EVENT) { did_cursorhold = false; } - /* If the cursor was moved we didn't just insert a space */ - if (arrow_used) - inserted_space = 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(); - } + // If the cursor was moved we didn't just insert a space */ + if (arrow_used) { + s->inserted_space = false; } - } /* for (;;) */ - /* NOTREACHED */ + 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(s->c, ' ', s->line_is_white)) { + if (stop_arrow() == OK) { + // re-indent the current line + do_c_expr_indent(); + } + } + } + } // for (;;) + // NOTREACHED } /* From 091e7d033cbf0f4da068292ce4ac934f1c3dd91e Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Thu, 15 Oct 2015 15:50:34 -0300 Subject: [PATCH 15/23] edit: Move most code from `edit()` to `insert_{enter,check,execute}` Refactor insert mode to use `state_enter` as an event loop: - Move o_lnum(static variable) outside function - Move code before the insert mode loop into `insert_enter` - Move code before `safe_vgetc()` call into `insert_check` - Move code after `safe_vgetc()` call into `insert_execute` - Remove doESCkey label and handle insert mode repeating in the `insert_enter` function - Remove do_intr label(this is not the place for platform-specific interrupt charts) --- src/nvim/edit.c | 1639 ++++++++++++++++++++++++----------------------- 1 file changed, 823 insertions(+), 816 deletions(-) diff --git a/src/nvim/edit.c b/src/nvim/edit.c index c6ebe376e2..c4ebf4cac4 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -254,52 +254,10 @@ static int did_add_space = FALSE; /* auto_format() added an extra space static int dont_sync_undo = false; // CTRL-G U prevents syncing undo // for the next left/right cursor +static linenr_T o_lnum = 0; -/* - * edit(): Start inserting text. - * - * "cmdchar" can be: - * 'i' normal insert command - * 'a' normal append command - * 'R' replace command - * 'r' "r" command: insert one . Note: count can be > 1, for redo, - * but still only one is inserted. The 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 -) +static void insert_enter(InsertState *s) { - static linenr_T o_lnum = 0; - InsertState state, *s = &state; - memset(s, 0, sizeof(InsertState)); - 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; - } - - s->cmdchar = cmdchar; - s->startln = startln; - s->count = count; s->did_backspace = true; s->old_topfill = -1; s->replaceState = REPLACE; @@ -311,34 +269,15 @@ edit ( // 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 // Trigger InsertEnter autocommands. Do not do this for "r" or "grx". - if (cmdchar != 'r' && cmdchar != 'v') { + if (s->cmdchar != 'r' && s->cmdchar != 'v') { pos_T save_cursor = curwin->w_cursor; - if (cmdchar == 'R') { + if (s->cmdchar == 'R') { s->ptr = (char_u *)"r"; - } else if (cmdchar == 'V') { + } else if (s->cmdchar == 'V') { s->ptr = (char_u *)"v"; } else { s->ptr = (char_u *)"i"; @@ -376,7 +315,7 @@ edit ( Insstart = where_paste_started; } else { Insstart = curwin->w_cursor; - if (startln) { + if (s->startln) { Insstart.col = 0; } } @@ -388,24 +327,24 @@ edit ( ai_col = 0; } - if (cmdchar != NUL && restart_edit == 0) { + if (s->cmdchar != NUL && restart_edit == 0) { ResetRedobuff(); - AppendNumberToRedobuff(count); - if (cmdchar == 'V' || cmdchar == 'v') { + 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" command - count = 1; // insert only one + } else if (s->cmdchar == 'r') { // "r" command + s->count = 1; // insert only one } } } - if (cmdchar == 'R') { + if (s->cmdchar == 'R') { if (p_fkmap && p_ri) { beep_flush(); EMSG(farsi_text_3); // encoded in Farsi @@ -413,7 +352,7 @@ edit ( } else { State = REPLACE; } - } else if (cmdchar == 'V' || cmdchar == 'v') { + } else if (s->cmdchar == 'V' || s->cmdchar == 'v') { State = VREPLACE; s->replaceState = VREPLACE; orig_line_count = curbuf->b_ml.ml_line_count; @@ -525,784 +464,852 @@ edit ( old_indent = 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)); - // 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; + // 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 (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 (!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; + } + + // 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 (arrow_used) { // don't repeat insert when arrow key used - count = 0; - } - - if (update_Insstart_orig) { - Insstart_orig = Insstart; - } - - if (stop_insert_mode) { - // ":stopinsert" used or 'insertmode' reset - count = 0; - goto doESCkey; - } - - // 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); - } - } - - // 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(); - } + // 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(); - } + // 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 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(); - - s->did_backspace = false; - - validate_cursor(); // may set must_redraw - - // 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_crb) { - do_check_cursorbind(); - } - - update_curswant(); - s->old_topline = curwin->w_topline; - s->old_topfill = curwin->w_topfill; - - // Get a character for Insert mode. Ignore K_IGNORE. - s->lastc = s->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; - } - - input_enable_events(); - do { - s->c = safe_vgetc(); - } while (s->c == K_IGNORE); - input_disable_events(); - - // Don't want K_EVENT with 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_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 ((s->c == K_BS || s->c == Ctrl_H) - && curwin->w_cursor.col > compl_col - && (s->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 (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(); - 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; - - 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); - } - 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(s->c)) { - 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 (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) { - continue; + 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 { - if (s->c == Ctrl_O) { - ins_ctrl_o(); - ins_at_eol = false; // cursor keeps its column - s->nomove = true; - } - count = 0; - goto doESCkey; + set_topline(curwin, curwin->w_topline + 1); } } + } - s->c = do_digraph(s->c); + // May need to adjust w_topline to show the cursor. + update_topline(); - if ((s->c == Ctrl_V || s->c == Ctrl_Q) && ctrl_x_mode == CTRL_X_CMDLINE) { - goto docomplete; + s->did_backspace = false; + + validate_cursor(); // may set must_redraw + + // 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_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 + + // 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; +} + +static int insert_execute(VimState *state, int key) +{ + if (key == K_IGNORE) { + return -1; // get another key + } + InsertState *s = (InsertState *)state; + s->c = key; + + // Don't want K_EVENT with 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_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 ((s->c == K_BS || s->c == Ctrl_H) + && curwin->w_cursor.col > compl_col + && (s->c = ins_compl_bs()) == NUL) { + return 1; // continue } - if (s->c == Ctrl_V || s->c == Ctrl_Q) { - ins_ctrl_v(); - s->c = Ctrl_V; // pretend CTRL-V is last typed character - 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)) { - goto force_cindent; + // 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 } - if (can_cindent && in_cinkeys(s->c, '*', s->line_is_white) - && stop_arrow() == OK) { - do_c_expr_indent(); - } - } - - 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(s->c)) { - continue; - } - - // 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_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; - goto doESCkey; - } - -#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, s->nomove)) { - if (cmdchar != 'r' && cmdchar != 'v') { - apply_autocmds(EVENT_INSERTLEAVE, NULL, NULL, false, curbuf); - } - did_cursorhold = false; - return s->c == Ctrl_O; - } - continue; - - 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) { - goto docomplete; - } - - if (echeck_abbr(Ctrl_O + ABBR_OFF)) { - break; - } - - ins_ctrl_o(); - - // don't move the cursor left when 'virtualedit' has "onemore". - if (ve_flags & VE_ONEMORE) { - ins_at_eol = false; - s->nomove = true; - } - - count = 0; - goto doESCkey; - - case K_INS: // toggle insert/replace mode - case K_KINS: - ins_insert(s->replaceState); - break; - - case K_SELECT: // end of Select mode mapping - ignore - break; - - - case K_HELP: // Help key works like - 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, (s->c == Ctrl_A)) == FAIL - && s->c != Ctrl_A && !p_im) - goto doESCkey; // quit insert mode - s->inserted_space = false; - break; - - case Ctrl_R: // insert the contents of a register - ins_reg(); - auto_format(false, true); - s->inserted_space = false; - break; - - case Ctrl_G: // commands starting with CTRL-G - ins_ctrl_g(); - break; - - case Ctrl_HAT: // switch input mode and/or langmap - ins_ctrl_hat(); - 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 Ctrl_T: // Make indent one shiftwidth greater. - if (s->c == Ctrl_T && ctrl_x_mode == CTRL_X_THESAURUS) { - if (has_compl_option(false)) { - goto docomplete; - } - break; - } - ins_shift(s->c, s->lastc); - auto_format(false, true); - s->inserted_space = false; - break; - - case K_DEL: // delete character under the cursor - case K_KDEL: - ins_del(); - auto_format(false, true); - 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 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 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; - } - - s->did_backspace = ins_bs(s->c, BACKSPACE_LINE, &s->inserted_space); - auto_format(false, true); - s->inserted_space = false; - 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_MOUSEDOWN: // Default action for scroll wheel up: scroll up - ins_mousescroll(MSCR_DOWN); - 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_EVENT: // some event - queue_process_events(loop.events); - break; - - case K_HOME: // - case K_KHOME: - case K_S_HOME: - case K_C_HOME: - ins_home(s->c); - break; - - case K_END: // - case K_KEND: - case K_S_END: - case K_C_END: - ins_end(s->c); - break; - - case K_LEFT: // - if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)) { - ins_s_left(); - } else { - ins_left(dont_sync_undo == false); - } - break; - - case K_S_LEFT: // - case K_C_LEFT: - ins_s_left(); - break; - - case K_RIGHT: // - if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)) { - ins_s_right(); - } else { - ins_right(dont_sync_undo == false); - } - break; - - case K_S_RIGHT: // - case K_C_RIGHT: - ins_s_right(); - break; - - case K_UP: // - if (pum_visible()) { - goto docomplete; - } - - if (mod_mask & MOD_MASK_SHIFT) { - ins_pageup(); - } else { - ins_up(false); - } - break; - - case K_S_UP: // - case K_PAGEUP: - case K_KPAGEUP: - if (pum_visible()) { - goto docomplete; - } - ins_pageup(); - break; - - case K_DOWN: // - if (pum_visible()) { - goto docomplete; - } - - if (mod_mask & MOD_MASK_SHIFT) { - ins_pagedown(); - } else { - ins_down(false); - } - break; - - case K_S_DOWN: // - case K_PAGEDOWN: - case K_KPAGEDOWN: - if (pum_visible()) { - goto docomplete; - } - - ins_pagedown(); - break; - - - 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; - } - s->inserted_space = false; - if (ins_tab()) { - goto normalchar; // insert TAB as a normal char - } - auto_format(false, true); - break; - - case K_KENTER: // - s->c = CAR; - // FALLTHROUGH - case CAR: - case NL: - // In a quickfix window a 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"); - } - break; - } - if (cmdwin_type != 0) { - // Execute the command in the cmdline window. - cmdwin_result = CAR; - goto doESCkey; - } - if (ins_eol(s->c) && !p_im) { - goto doESCkey; // 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; - } - - s->c = ins_digraph(); - if (s->c == NUL) { - break; - } - goto normalchar; - - case Ctrl_X: // Enter CTRL-X mode - ins_ctrl_x(); - break; - - case Ctrl_RSB: // Tag name completion after ^X - if (ctrl_x_mode != CTRL_X_TAGS) { - goto normalchar; - } - goto docomplete; - - case Ctrl_F: // File name completion after ^X - if (ctrl_x_mode != CTRL_X_FILES) { - goto normalchar; - } - goto docomplete; - - 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; - } - 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(s->c) == FAIL) { - compl_cont_status = 0; - } - compl_busy = false; - break; - - 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; - - default: -#ifdef UNIX - if (s->c == intr_char) // special interrupt char - goto do_intr; -#endif - -normalchar: - // Insert a normal character. - if (!p_paste) { + // 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; 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); + for (p = str; *p != NUL; mb_ptr_adv(p)) { + ins_compl_addleader(PTR2CHAR(p)); } xfree(str); - s->c = NUL; - } - - // 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; - } - if (Insstart_blank_vcol == MAXCOL - && curwin->w_cursor.lnum == Insstart.lnum) { - Insstart_blank_vcol = get_nolist_virtcol(); + } else { + ins_compl_addleader(s->c); } + return 1; // continue } - // 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++; + // 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(); } + } + } - auto_format(false, true); + // 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 + } - // When inserting a character the cursor line must never be in a - // closed fold. - foldOpenCursor(); + // 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; + } + } + + s->c = do_digraph(s->c); + + if ((s->c == Ctrl_V || s->c == Ctrl_Q) && ctrl_x_mode == CTRL_X_CMDLINE) { + goto docomplete; + } + + 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)) { + goto force_cindent; + } + + if (can_cindent && in_cinkeys(s->c, '*', s->line_is_white) + && stop_arrow() == OK) { + do_c_expr_indent(); + } + } + + 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(s->c)) { + return 1; // continue + } + + // 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; - } // end of switch (s->c) + } + // FALLTHROUGH - // If typed something may trigger CursorHoldI again. - if (s->c != K_EVENT) { - did_cursorhold = false; + 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 } - // If the cursor was moved we didn't just insert a space */ - if (arrow_used) { - s->inserted_space = false; + // 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; + } + return 0; // exit insert mode + + 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) { + goto docomplete; } - 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(s->c, ' ', s->line_is_white)) { - if (stop_arrow() == OK) { - // re-indent the current line - do_c_expr_indent(); + if (echeck_abbr(Ctrl_O + ABBR_OFF)) { + break; + } + + ins_ctrl_o(); + + // don't move the cursor left when 'virtualedit' has "onemore". + if (ve_flags & VE_ONEMORE) { + ins_at_eol = false; + s->nomove = true; + } + + s->count = 0; + return 0; // exit insert mode + + case K_INS: // toggle insert/replace mode + case K_KINS: + ins_insert(s->replaceState); + break; + + case K_SELECT: // end of Select mode mapping - ignore + break; + + + case K_HELP: // Help key works like + case K_F1: + case K_XF1: + stuffcharReadbuff(K_HELP); + if (p_im) { + need_start_insertmode = true; + } + return 0; // exit insert mode + + + 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 Ctrl_R: // insert the contents of a register + ins_reg(); + auto_format(false, true); + s->inserted_space = false; + break; + + case Ctrl_G: // commands starting with CTRL-G + ins_ctrl_g(); + break; + + case Ctrl_HAT: // switch input mode and/or langmap + ins_ctrl_hat(); + 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 Ctrl_T: // Make indent one shiftwidth greater. + if (s->c == Ctrl_T && ctrl_x_mode == CTRL_X_THESAURUS) { + if (has_compl_option(false)) { + goto docomplete; + } + break; + } + ins_shift(s->c, s->lastc); + auto_format(false, true); + s->inserted_space = false; + break; + + case K_DEL: // delete character under the cursor + case K_KDEL: + ins_del(); + auto_format(false, true); + 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 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 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; + } + + s->did_backspace = ins_bs(s->c, BACKSPACE_LINE, &s->inserted_space); + auto_format(false, true); + s->inserted_space = false; + 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_MOUSEDOWN: // Default action for scroll wheel up: scroll up + ins_mousescroll(MSCR_DOWN); + 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_EVENT: // some event + queue_process_events(loop.events); + break; + + case K_HOME: // + case K_KHOME: + case K_S_HOME: + case K_C_HOME: + ins_home(s->c); + break; + + case K_END: // + case K_KEND: + case K_S_END: + case K_C_END: + ins_end(s->c); + break; + + case K_LEFT: // + if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)) { + ins_s_left(); + } else { + ins_left(dont_sync_undo == false); + } + break; + + case K_S_LEFT: // + case K_C_LEFT: + ins_s_left(); + break; + + case K_RIGHT: // + if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)) { + ins_s_right(); + } else { + ins_right(dont_sync_undo == false); + } + break; + + case K_S_RIGHT: // + case K_C_RIGHT: + ins_s_right(); + break; + + case K_UP: // + if (pum_visible()) { + goto docomplete; + } + + if (mod_mask & MOD_MASK_SHIFT) { + ins_pageup(); + } else { + ins_up(false); + } + break; + + case K_S_UP: // + case K_PAGEUP: + case K_KPAGEUP: + if (pum_visible()) { + goto docomplete; + } + ins_pageup(); + break; + + case K_DOWN: // + if (pum_visible()) { + goto docomplete; + } + + if (mod_mask & MOD_MASK_SHIFT) { + ins_pagedown(); + } else { + ins_down(false); + } + break; + + case K_S_DOWN: // + case K_PAGEDOWN: + case K_KPAGEDOWN: + if (pum_visible()) { + goto docomplete; + } + + ins_pagedown(); + break; + + + 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; + } + s->inserted_space = false; + if (ins_tab()) { + goto normalchar; // insert TAB as a normal char + } + auto_format(false, true); + break; + + case K_KENTER: // + s->c = CAR; + // FALLTHROUGH + case CAR: + case NL: + // In a quickfix window a 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"); + } + 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; + } + + s->c = ins_digraph(); + if (s->c == NUL) { + break; + } + goto normalchar; + + case Ctrl_X: // Enter CTRL-X mode + ins_ctrl_x(); + break; + + case Ctrl_RSB: // Tag name completion after ^X + if (ctrl_x_mode != CTRL_X_TAGS) { + goto normalchar; + } + goto docomplete; + + case Ctrl_F: // File name completion after ^X + if (ctrl_x_mode != CTRL_X_FILES) { + goto normalchar; + } + goto docomplete; + + 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; } + return 0; // exit insert mode + } + 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(s->c) == FAIL) { + compl_cont_status = 0; + } + compl_busy = false; + break; + + 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; + + default: + +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); + s->c = NUL; + } + + // 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; + } + if (Insstart_blank_vcol == MAXCOL + && curwin->w_cursor.lnum == Insstart.lnum) { + Insstart_blank_vcol = get_nolist_virtcol(); } } - } // for (;;) - // NOTREACHED + + // 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); + + // 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 (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) { +force_cindent: + // 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(); + } + } + } + + return 1; // continue +} + +/* + * edit(): Start inserting text. + * + * "cmdchar" can be: + * 'i' normal insert command + * 'a' normal append command + * 'R' replace command + * 'r' "r" command: insert one . Note: count can be > 1, for redo, + * but still only one is inserted. The 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; } /* From 8890852cb79a6688a37ed9f0f45b58ed48708312 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Fri, 16 Oct 2015 07:50:49 -0300 Subject: [PATCH 16/23] edit: Extract some functions from `insert_execute` - `insert_handle_key`: Contains the big insert mode switch statement. - `insert_do_complete`: Code that used to be in the `docomplete` label. - `insert_do_cindent`: Code that used to be in the `force_cindent` label. Also move some code after the switch statement into the beginning of `insert_check`. --- src/nvim/edit.c | 124 ++++++++++++++++++++++++++++-------------------- 1 file changed, 73 insertions(+), 51 deletions(-) diff --git a/src/nvim/edit.c b/src/nvim/edit.c index c4ebf4cac4..31fc50d4d6 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -485,6 +485,21 @@ static void insert_enter(InsertState *s) 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 { @@ -709,7 +724,8 @@ static int insert_execute(VimState *state, int key) s->c = do_digraph(s->c); if ((s->c == Ctrl_V || s->c == Ctrl_Q) && ctrl_x_mode == CTRL_X_CMDLINE) { - goto docomplete; + insert_do_complete(s); + return 1; } if (s->c == Ctrl_V || s->c == Ctrl_Q) { @@ -726,7 +742,8 @@ static int insert_execute(VimState *state, int key) // done before inserting the key. s->line_is_white = inindent(0); if (in_cinkeys(s->c, '!', s->line_is_white)) { - goto force_cindent; + insert_do_cindent(s); + return 1; // continue } if (can_cindent && in_cinkeys(s->c, '*', s->line_is_white) @@ -752,6 +769,11 @@ static int insert_execute(VimState *state, int key) return 1; // continue } + return insert_handle_key(s); +} + +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[]`) @@ -794,7 +816,8 @@ static int insert_execute(VimState *state, int key) case Ctrl_O: // execute one command if (ctrl_x_mode == CTRL_X_OMNI) { - goto docomplete; + insert_do_complete(s); + break; } if (echeck_abbr(Ctrl_O + ABBR_OFF)) { @@ -866,14 +889,15 @@ static int insert_execute(VimState *state, int key) case Ctrl_D: // Make indent one shiftwidth smaller. if (ctrl_x_mode == CTRL_X_PATH_DEFINES) { - goto docomplete; + insert_do_complete(s); + break; } // FALLTHROUGH case Ctrl_T: // Make indent one shiftwidth greater. if (s->c == Ctrl_T && ctrl_x_mode == CTRL_X_THESAURUS) { if (has_compl_option(false)) { - goto docomplete; + insert_do_complete(s); } break; } @@ -902,12 +926,12 @@ static int insert_execute(VimState *state, int key) 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; + 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; } - - s->did_backspace = ins_bs(s->c, BACKSPACE_LINE, &s->inserted_space); - auto_format(false, true); - s->inserted_space = false; break; case K_LEFTMOUSE: // mouse keys @@ -995,10 +1019,8 @@ static int insert_execute(VimState *state, int key) case K_UP: // if (pum_visible()) { - goto docomplete; - } - - if (mod_mask & MOD_MASK_SHIFT) { + insert_do_complete(s); + } else if (mod_mask & MOD_MASK_SHIFT) { ins_pageup(); } else { ins_up(false); @@ -1009,17 +1031,16 @@ static int insert_execute(VimState *state, int key) case K_PAGEUP: case K_KPAGEUP: if (pum_visible()) { - goto docomplete; + insert_do_complete(s); + } else { + ins_pageup(); } - ins_pageup(); break; case K_DOWN: // if (pum_visible()) { - goto docomplete; - } - - if (mod_mask & MOD_MASK_SHIFT) { + insert_do_complete(s); + } else if (mod_mask & MOD_MASK_SHIFT) { ins_pagedown(); } else { ins_down(false); @@ -1030,10 +1051,10 @@ static int insert_execute(VimState *state, int key) case K_PAGEDOWN: case K_KPAGEDOWN: if (pum_visible()) { - goto docomplete; + insert_do_complete(s); + } else { + ins_pagedown(); } - - ins_pagedown(); break; @@ -1043,7 +1064,8 @@ static int insert_execute(VimState *state, int key) case TAB: // TAB or Complete patterns along path if (ctrl_x_mode == CTRL_X_PATH_PATTERNS) { - goto docomplete; + insert_do_complete(s); + break; } s->inserted_space = false; if (ins_tab()) { @@ -1082,7 +1104,7 @@ static int insert_execute(VimState *state, int key) case Ctrl_K: // digraph or keyword completion if (ctrl_x_mode == CTRL_X_DICTIONARY) { if (has_compl_option(true)) { - goto docomplete; + insert_do_complete(s); } break; } @@ -1100,21 +1122,27 @@ static int insert_execute(VimState *state, int key) case Ctrl_RSB: // Tag name completion after ^X if (ctrl_x_mode != CTRL_X_TAGS) { goto normalchar; + } else { + insert_do_complete(s); } - goto docomplete; + break; case Ctrl_F: // File name completion after ^X if (ctrl_x_mode != CTRL_X_FILES) { goto normalchar; + } else { + insert_do_complete(s); } - goto docomplete; + break; case 's': // Spelling completion after ^X case Ctrl_S: if (ctrl_x_mode != CTRL_X_SPELL) { goto normalchar; + } else { + insert_do_complete(s); } - goto docomplete; + break; case Ctrl_L: // Whole line completion after ^X if (ctrl_x_mode != CTRL_X_WHOLE_LINE) { @@ -1139,12 +1167,7 @@ static int insert_execute(VimState *state, int key) goto normalchar; } -docomplete: - compl_busy = true; - if (ins_complete(s->c) == FAIL) { - compl_cont_status = 0; - } - compl_busy = false; + insert_do_complete(s); break; case Ctrl_Y: // copy from previous line or scroll down @@ -1219,28 +1242,27 @@ normalchar: break; } // end of switch (s->c) - // If typed something may trigger CursorHoldI again. - if (s->c != K_EVENT) { - did_cursorhold = false; - } + return 1; // continue +} - // If the cursor was moved we didn't just insert a space */ - if (arrow_used) { - s->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(s->c, ' ', s->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(); } } - - return 1; // continue } /* From 344bd64f96661d6fbbe55c67228be3cfeed94370 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Mon, 19 Oct 2015 10:51:11 -0300 Subject: [PATCH 17/23] terminal: Refactor to use `state_enter` - Create `TerminalState` structure containing data used in terminal mode - Extract `terminal_execute` from `terminal_enter` and use it with `state_enter`. --- src/nvim/terminal.c | 160 ++++++++++++++++++++++++-------------------- 1 file changed, 86 insertions(+), 74 deletions(-) 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 +} 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 + s->state.execute = terminal_execute; + state_enter(&s->state); - 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); - } - } - -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); From 52d4978b034670935054ca320ca34ff9e4596fe0 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Wed, 21 Oct 2015 14:29:52 -0300 Subject: [PATCH 18/23] ex_getln: Extract local variables from getcmdline() and fix code style Begin refactoring getcmdline() into a state that can be managed by the `state_enter()`: - Move local variables into a local CommandLineState structure - Fix code style in the entire function. --- src/nvim/ex_getln.c | 1704 +++++++++++++++++++++++-------------------- 1 file changed, 901 insertions(+), 803 deletions(-) diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 7b9fd1e61e..38670052bb 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 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(). @@ -143,269 +183,248 @@ static int cmd_fkmap = 0; /* Farsi mapping during command line */ char_u * getcmdline ( int firstc, - long count, /* only used for incremental search */ - int indent /* indent for inside conditionals */ + long count, // only used for incremental search + int indent // indent for inside conditionals ) { - int c; - int i; - int j; - int gotesc = FALSE; /* TRUE when 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; + 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 (firstc == -1) { - firstc = NUL; - break_ctrl_c = 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; - cursorcmd(); /* set the cursor on the right spot */ + // 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 - /* Get a character. Ignore K_IGNORE, it should not do anything, such - * as stop completion. */ + cursorcmd(); // set the cursor on the right spot + + // 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); + s->c = safe_vgetc(); + } while (s->c == K_IGNORE || s->c == K_PASTE); input_disable_events(); - if (c == K_EVENT) { + if (s->c == K_EVENT) { queue_process_events(loop.events); continue; } if (KeyTyped) { - some_key_typed = TRUE; - if (cmd_hkmap) - c = hkmap(c); - if (cmd_fkmap) - c = cmdl_fkmap(c); + s->some_key_typed = true; + if (cmd_hkmap) { + s->c = hkmap(s->c); + } + + if (cmd_fkmap) { + s->c = cmdl_fkmap(s->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; + // 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; } } } - /* - * 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 ). - * 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; + // 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 ). + // Don't ignore it for the input() function. + if ((s->c == Ctrl_C) + && s->firstc != '@' + && !s->break_ctrl_c + && !global_busy) { + got_int = false; } - /* - * When there are matching completions to select works like - * CTRL-P (unless 'wc' is ). - */ - 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; + // 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; } - /* Hitting CR after "emenu Name.": complete submenu */ - if (xpc.xp_context == EXPAND_MENUNAMES && p_wmnu + + // When there are matching completions to select works like + // CTRL-P (unless 'wc' is ). + 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] != '\\' - && (c == '\n' || c == '\r' || c == K_KENTER)) - c = K_DOWN; + && (s->c == '\n' || s->c == '\r' || s->c == K_KENTER)) { + s->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; + // 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) + if (ccline.input_fn) { RedrawingDisabled = 0; + } if (wild_menu_showing == WM_SCROLLED) { - /* Entered command line, move it up */ + // Entered command line, move it up cmdline_row--; redrawcmd(); } else if (save_p_ls != -1) { - /* restore 'laststatus' and 'winminheight' */ + // 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); + 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 { @@ -414,50 +433,54 @@ getcmdline ( } KeyTyped = skt; wild_menu_showing = 0; - if (ccline.input_fn) + if (ccline.input_fn) { RedrawingDisabled = old_RedrawingDisabled; + } } } - /* Special translations for 'wildmenu' */ - if (xpc.xp_context == EXPAND_MENUNAMES && p_wmnu) { - /* Hitting 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 : Remove one submenu name in front of the - * cursor */ - int found = FALSE; + // Special translations for 'wildmenu' + if (s->xpc.xp_context == EXPAND_MENUNAMES && p_wmnu) { + // Hitting 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 : 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; + 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[j] == '.' - && ccline.cmdbuff[j - 1] != '\\') { + + // check for start of submenu name + if (ccline.cmdbuff[s->j] == '.' + && ccline.cmdbuff[s->j - 1] != '\\') { if (found) { - i = j + 1; + s->i = s->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) { + 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; @@ -466,124 +489,129 @@ getcmdline ( upseg[3] = PATHSEP; upseg[4] = NUL; - if (c == K_DOWN + 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 */ - 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; + // 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; - 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; + 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 (vim_ispathsep(ccline.cmdbuff[s->j])) { + found = true; break; } } 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; + && 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 (c == K_UP) { - /* go up a directory */ - int found = FALSE; + } else if (s->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]) + 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 #endif ) { if (found) { - i = j + 1; + s->i = s->j + 1; break; - } else - found = TRUE; + } 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); + 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; + } - /* Now complete in the new directory. Set KeyTyped in case the - * Up key came from a mapping. */ - c = p_wc; - KeyTyped = TRUE; + 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 (c == Ctrl_BSL) { + // 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; - c = plain_vgetc(); + s->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' + // 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(c); - c = Ctrl_BSL; - } else if (c == 'e') { + vungetc(s->c); + s->c = Ctrl_BSL; + } else if (s->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 + // 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); + 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(&save_ccline); + restore_cmdline(&s->save_ccline); if (p != NULL) { len = (int)STRLEN(p); @@ -592,62 +620,66 @@ getcmdline ( STRCPY(ccline.cmdbuff, p); xfree(p); - /* Restore the cursor or use the position set with - * set_cmdline_pos(). */ - if (new_cmdpos > ccline.cmdlen) + // Restore the cursor or use the position set with + // set_cmdline_pos(). + if (new_cmdpos > ccline.cmdlen) { ccline.cmdpos = ccline.cmdlen; - else + } else { ccline.cmdpos = new_cmdpos; + } - KeyTyped = FALSE; /* Don't do p_wc completion. */ + KeyTyped = false; // Don't do p_wc completion. redrawcmd(); goto cmdline_changed; } } beep_flush(); - got_int = FALSE; /* don't abandon the command line */ - did_emsg = FALSE; - emsg_on_display = FALSE; + 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) + if (s->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 */ + } + s->gotesc = true; // will free ccline.cmdbuff after putting it + // in history + goto returncmd; // 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 - c = do_digraph(c); + } else { + s->c = do_digraph(s->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 (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 - && c != ESC + && s->c != ESC && ccline.cmdpos == ccline.cmdlen && ccline.cmdpos > 0 && ccline.cmdbuff[ccline.cmdpos - 1] == '\\') { - if (c == K_KENTER) - c = '\n'; + if (s->c == K_KENTER) { + s->c = '\n'; + } } else { - gotesc = FALSE; /* Might have typed ESC previously, don't - truncate the cmdline now. */ - if (ccheck_abbr(c + ABBR_OFF)) + s->gotesc = false; // Might have typed ESC previously, don't + // truncate the cmdline now. + if (ccheck_abbr(s->c + ABBR_OFF)) { goto cmdline_changed; + } + if (!cmd_silent) { ui_cursor_goto(msg_row, 0); ui_flush(); @@ -656,357 +688,406 @@ getcmdline ( } } - /* - * Completion for 'wildchar' or 'wildcharm' key. - * - hitting 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); + // Completion for 'wildchar' or 'wildcharm' key. + // - hitting 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(); - did_wild_list = TRUE; + s->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 (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 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; + (void)vpeekc(); // remove 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; goto cmdline_changed; } - /* 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) - ) { + // 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(&xpc, WILD_PREV, 0, firstc != '@'); + // remove match + nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@'); p_wmnu = p_wmnu_save; } - (void)showmatches(&xpc, p_wmnu - && ((wim_flags[wim_index] & WIM_LIST) == 0)); + + (void)showmatches(&s->xpc, p_wmnu + && ((wim_flags[s->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 != '@'); + 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 { vim_beep(BO_WILD); } - } else if (xpc.xp_numfiles == -1) { - xpc.xp_context = EXPAND_NOTHING; + } 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) + + if (s->wim_index < 3) { + ++s->wim_index; + } + + if (s->c == ESC) { + s->gotesc = true; + } + + if (s->res == OK) { goto cmdline_changed; + } } - gotesc = FALSE; + s->gotesc = false; - /* 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) + // 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) { goto cmdline_changed; + } } - if (c == NUL || c == K_ZERO) /* NUL is stored as NL */ - c = NL; + if (s->c == NUL || s->c == K_ZERO) { + // NUL is stored as NL + s->c = NL; + } - do_abbr = TRUE; /* default: check for abbreviation */ + s->do_abbr = true; // default: check for abbreviation - /* - * Big switch for a typed command line character. - */ - switch (c) { + // 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 && c == K_BS) - c = K_DEL; - if (c == K_KDEL) - c = K_DEL; + if (cmd_fkmap && s->c == K_BS) { + s->c = K_DEL; + } - /* - * delete current character is the same as backspace on next - * character, except at end of line - */ - if (c == K_DEL && ccline.cmdpos != ccline.cmdlen) + 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 && c == K_DEL) + } + + 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; - j = ccline.cmdpos; - p = ccline.cmdbuff + j; + s->j = ccline.cmdpos; + p = ccline.cmdbuff + s->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); - } - } 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 - --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. */ + if (s->c == Ctrl_W) { + while (p > ccline.cmdbuff && ascii_isspace(*p)) { + p = mb_prevptr(ccline.cmdbuff, p); + } + + 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; + } + + s->i = vim_iswordc(p[-1]); + while (p > ccline.cmdbuff && !ascii_isspace(p[-1]) + && vim_iswordc(p[-1]) == s->i) + --p; + } else { + --p; + } + + 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++]; + } + + // 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 == '>' - ) + } 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 == '>') { goto cmdline_not_changed; + } - xfree(ccline.cmdbuff); /* no commandline to return */ + xfree(ccline.cmdbuff); // no commandline to return ccline.cmdbuff = NULL; if (!cmd_silent) { - if (cmdmsg_rl) + if (cmdmsg_rl) { msg_col = Columns; - else + } else { msg_col = 0; - msg_putchar(' '); /* delete ':' */ + } + msg_putchar(' '); // delete ':' } - redraw_cmdline = TRUE; - goto returncmd; /* back to cmd mode */ + 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) + // if Farsi mode set, we are in reverse insert mode - + // Do not change the mode + if (cmd_fkmap) { beep_flush(); - else + } else { ccline.overstrike = !ccline.overstrike; + } - ui_cursor_shape(); /* may show different cursor shape */ + 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. */ + 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; + if (s->b_im_ptr != NULL) { + if (State & LANGMAP) { + *s->b_im_ptr = B_IMODE_LMAP; + } else { + *s->b_im_ptr = B_IMODE_NONE; + } } } - if (b_im_ptr != NULL) { - if (b_im_ptr == &curbuf->b_p_iminsert) + + if (s->b_im_ptr != NULL) { + if (s->b_im_ptr == &curbuf->b_p_iminsert) { set_iminsert_global(); - else + } else { set_imsearch_global(); + } } - ui_cursor_shape(); /* may show different cursor shape */ - /* Show/unshow value of 'keymap' in status lines later. */ + 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 '@': 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. */ + // 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++]; + } + + // 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 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) - ) + // 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 */ + s->gotesc = true; // will free ccline.cmdbuff after + // putting it in history + goto returncmd; // back to cmd mode - case Ctrl_R: /* insert register */ - putcmdline('"', TRUE); + case Ctrl_R: // insert register + putcmdline('"', true); ++no_mapping; - i = c = plain_vgetc(); /* CTRL-R */ - 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 */ + s->i = s->c = plain_vgetc(); // CTRL-R + if (s->i == Ctrl_O) { + s->i = Ctrl_R; // CTRL-R CTRL-O == CTRL-R CTRL-R + } + + if (s->i == Ctrl_R) { + s->c = plain_vgetc(); // CTRL-R CTRL-R + } --no_mapping; - /* - * Insert the result of an expression. - * Need to save the current command line, to be able to enter - * a new one... - */ + // 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 */ + if (s->c == '=') { + if (ccline.cmdfirstc == '=') { // can't do this recursively beep_flush(); - c = ESC; + s->c = ESC; } else { - save_cmdline(&save_ccline); - c = get_expr_register(); - restore_cmdline(&save_ccline); + save_cmdline(&s->save_ccline); + s->c = get_expr_register(); + restore_cmdline(&s->save_ccline); } } - 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 (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()) { - gotesc = TRUE; /* will free ccline.cmdbuff after - putting it in history */ - goto returncmd; /* back to cmd mode */ + s->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. */ + KeyTyped = false; // Don't do p_wc completion. if (new_cmdpos >= 0) { - /* set_cmdline_pos() was used */ - if (new_cmdpos > ccline.cmdlen) + // set_cmdline_pos() was used + if (new_cmdpos > ccline.cmdlen) { ccline.cmdpos = ccline.cmdlen; - else + } else { ccline.cmdpos = new_cmdpos; + } } } redrawcmd(); goto cmdline_changed; case Ctrl_D: - if (showmatches(&xpc, FALSE) == EXPAND_NOTHING) - break; /* Use ^D as normal char instead */ + if (showmatches(&s->xpc, false) == EXPAND_NOTHING) { + break; // Use ^D as normal char instead + } redrawcmd(); - continue; /* don't do incremental search now */ + continue; // don't do incremental search now case K_RIGHT: case K_S_RIGHT: case K_C_RIGHT: do { - if (ccline.cmdpos >= ccline.cmdlen) + if (ccline.cmdpos >= ccline.cmdlen) { break; - i = cmdline_charsize(ccline.cmdpos); - if (KeyTyped && ccline.cmdspos + i >= Columns * Rows) + } + + s->i = cmdline_charsize(ccline.cmdpos); + if (KeyTyped && ccline.cmdspos + s->i >= Columns * Rows) { break; - ccline.cmdspos += i; - if (has_mbyte) + } + + ccline.cmdspos += s->i; + if (has_mbyte) { ccline.cmdpos += (*mb_ptr2len)(ccline.cmdbuff + ccline.cmdpos); - else + } else { ++ccline.cmdpos; - } while ((c == K_S_RIGHT || c == K_C_RIGHT + } + } 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) + 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) + if (ccline.cmdpos == 0) { goto cmdline_not_changed; + } do { --ccline.cmdpos; - if (has_mbyte) /* move to first byte of char */ + 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 + && (s->c == K_S_LEFT || s->c == K_C_LEFT || (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL))) && ccline.cmdbuff[ccline.cmdpos - 1] != ' '); - if (has_mbyte) + + if (has_mbyte) { set_cmdspos_cursor(); + } + goto cmdline_not_changed; case K_IGNORE: - /* Ignore mouse event or ex_window() result. */ + // Ignore mouse event or ex_window() result. goto cmdline_not_changed; case K_MIDDLEDRAG: case K_MIDDLERELEASE: - goto cmdline_not_changed; /* Ignore mouse */ + goto cmdline_not_changed; // Ignore mouse case K_MIDDLEMOUSE: - if (!mouse_has(MOUSE_COMMAND)) - goto cmdline_not_changed; /* Ignore mouse */ - cmdline_paste(0, TRUE, TRUE); + if (!mouse_has(MOUSE_COMMAND)) { + goto cmdline_not_changed; // Ignore mouse + } + cmdline_paste(0, true, true); redrawcmd(); goto cmdline_changed; @@ -1015,43 +1096,49 @@ getcmdline ( 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) + // Ignore drag and release events when the button-down wasn't + // seen before. + if (s->ignore_drag_release) { goto cmdline_not_changed; - /* FALLTHROUGH */ + } + // 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 */ + 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)) { + goto cmdline_not_changed; // Ignore mouse + } set_cmdspos(); for (ccline.cmdpos = 0; ccline.cmdpos < ccline.cmdlen; ++ccline.cmdpos) { - i = cmdline_charsize(ccline.cmdpos); + s->i = cmdline_charsize(ccline.cmdpos); if (mouse_row <= cmdline_row + ccline.cmdspos / Columns - && mouse_col < ccline.cmdspos % Columns + i) + && mouse_col < ccline.cmdspos % Columns + s->i) { break; + } + if (has_mbyte) { - /* Count ">" for double-wide char that doesn't fit. */ - correct_cmdspos(ccline.cmdpos, i); + // 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 += i; + ccline.cmdspos += s->i; } goto cmdline_not_changed; - /* Mouse scroll wheel: ignored here */ + // Mouse scroll wheel: ignored here case K_MOUSEDOWN: case K_MOUSEUP: case K_MOUSELEFT: case K_MOUSERIGHT: - /* Alternate buttons ignored here */ + // Alternate buttons ignored here case K_X1MOUSE: case K_X1DRAG: case K_X1RELEASE: @@ -1062,10 +1149,10 @@ getcmdline ( - case K_SELECT: /* end of Select mode mapping - ignore */ + case K_SELECT: // end of Select mode mapping - ignore goto cmdline_not_changed; - case Ctrl_B: /* begin of command line */ + case Ctrl_B: // begin of command line case K_HOME: case K_KHOME: case K_S_HOME: @@ -1074,7 +1161,7 @@ getcmdline ( set_cmdspos(); goto cmdline_not_changed; - case Ctrl_E: /* end of command line */ + case Ctrl_E: // end of command line case K_END: case K_KEND: case K_S_END: @@ -1083,29 +1170,30 @@ getcmdline ( set_cmdspos_cursor(); goto cmdline_not_changed; - case Ctrl_A: /* all matches */ - if (nextwild(&xpc, WILD_ALL, 0, firstc != '@') == FAIL) + case Ctrl_A: // all matches + if (nextwild(&s->xpc, WILD_ALL, 0, s->firstc != '@') == FAIL) 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) + 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 (s->c != NUL) { + if (s->c == s->firstc + || vim_strchr((char_u *)(p_magic ? "\\^$.*[" : "\\^$"), s->c) != NULL) { - /* put a backslash before special characters */ - stuffcharReadbuff(c); - c = '\\'; + // put a backslash before special characters + stuffcharReadbuff(s->c); + s->c = '\\'; } break; } @@ -1113,17 +1201,19 @@ getcmdline ( goto cmdline_not_changed; } - /* completion: longest common part */ - if (nextwild(&xpc, WILD_LONGEST, 0, firstc != '@') == FAIL) + // completion: longest common part + if (nextwild(&s->xpc, WILD_LONGEST, 0, s->firstc != '@') == FAIL) { 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) + 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; + } goto cmdline_changed; } @@ -1135,101 +1225,119 @@ getcmdline ( case K_KPAGEUP: case K_PAGEDOWN: case K_KPAGEDOWN: - if (hislen == 0 || firstc == NUL) /* no history */ + if (hislen == 0 || s->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; } - j = (int)STRLEN(lookfor); + 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 (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; + // 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 { + // at top of list + s->hiscnt = s->i; break; } - } else { /* one step forwards */ - /* on last entry, clear the line */ - if (hiscnt == hisidx[histype]) { - hiscnt = hislen; + } else { // one step forwards + // on last entry, clear the line + if (s->hiscnt == hisidx[s->histype]) { + s->hiscnt = hislen; break; } - /* not on a history line, nothing to do */ - if (hiscnt == hislen) + // not on a history line, nothing to do + if (s->hiscnt == hislen) { break; - if (hiscnt == hislen - 1) /* wrap around */ - hiscnt = 0; - else - ++hiscnt; + } + + if (s->hiscnt == hislen - 1) { + // wrap around + s->hiscnt = 0; + } else { + ++s->hiscnt; + } } - if (hiscnt < 0 || history[histype][hiscnt].hisstr == NULL) { - hiscnt = i; + + if (s->hiscnt < 0 || history[s->histype][s->hiscnt].hisstr == NULL) { + s->hiscnt = s->i; break; } - if ((c != K_UP && c != K_DOWN) - || hiscnt == i - || STRNCMP(history[histype][hiscnt].hisstr, - lookfor, (size_t)j) == 0) + + 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 (hiscnt != i) { /* jumped to other entry */ + if (s->hiscnt != s->i) { + // jumped to other entry char_u *p; int len; int old_firstc; xfree(ccline.cmdbuff); - xpc.xp_context = EXPAND_NOTHING; - if (hiscnt == hislen) - p = lookfor; /* back to the old one */ - else - p = history[histype][hiscnt].hisstr; + s->xpc.xp_context = EXPAND_NOTHING; + if (s->hiscnt == hislen) { + p = s->lookfor; // back to the old one + } else { + p = history[s->histype][s->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) { + 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 (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; + 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[j] == firstc - && (j == 0 || p[j - 1] != '\\')) { - if (i > 0) + // 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; } - if (i > 0) - ccline.cmdbuff[len] = p[j]; + + if (s->i > 0) { + ccline.cmdbuff[len] = p[s->j]; + } } ++len; } - if (i == 0) { + + if (s->i == 0) { alloc_cmdbuff(len); } } @@ -1248,12 +1356,12 @@ getcmdline ( 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) { + 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(); @@ -1261,149 +1369,138 @@ getcmdline ( break; case Ctrl_K: - ignore_drag_release = TRUE; - putcmdline('?', TRUE); - c = get_digraph(TRUE); - if (c != NUL) + s->ignore_drag_release = true; + putcmdline('?', true); + s->c = get_digraph(true); + + if (s->c != NUL) { break; + } redrawcmd(); goto cmdline_not_changed; - case Ctrl__: /* CTRL-_: switch language mode */ - if (!p_ari) + case Ctrl__: // CTRL-_: switch language mode + if (!p_ari) { break; + } if (p_altkeymap) { cmd_fkmap = !cmd_fkmap; - if (cmd_fkmap) /* in Farsi always in Insert mode */ - ccline.overstrike = FALSE; - } else /* Hebrew is default */ + 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 . This should only happen after ^V. - */ - if (!IS_SPECIAL(c)) + // 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 . This should only happen after ^V. + if (!IS_SPECIAL(s->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)) + // 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)) { goto cmdline_changed; + } - /* - * 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 { + // 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) { - j = (*mb_char2bytes)(c, IObuff); - IObuff[j] = NUL; /* exclude composing chars */ - put_on_cmdline(IObuff, j, TRUE); + s->j = (*mb_char2bytes)(s->c, IObuff); + IObuff[s->j] = NUL; // exclude composing chars + put_on_cmdline(IObuff, s->j, true); } else { - IObuff[0] = c; - put_on_cmdline(IObuff, 1, TRUE); + IObuff[0] = s->c; + put_on_cmdline(IObuff, 1, true); } } 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). - */ + // 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) + if (!s->incsearch_postponed) { continue; + } cmdline_changed: - /* - * 'incsearch' highlighting. - */ - if (p_is && !cmd_silent && (firstc == '/' || firstc == '?')) { + // '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 there is a character waiting, search and redraw later if (char_avail()) { - incsearch_postponed = TRUE; + s->incsearch_postponed = true; continue; } - incsearch_postponed = FALSE; - curwin->w_cursor = old_cursor; /* start at old position */ + 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) - i = 0; - else { + // 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. */ + ++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, + s->i = do_search(NULL, s->firstc, ccline.cmdbuff, s->count, SEARCH_KEEP + SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK, - &tm - ); + &tm); --emsg_off; - /* if interrupted while searching, behave like it failed */ + // if interrupted while searching, behave like it failed if (got_int) { - (void)vpeekc(); /* remove 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; + (void)vpeekc(); // remove 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 (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; + if (s->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 = 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 (i != 0) { + 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. - */ + // 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) { @@ -1413,104 +1510,105 @@ cmdline_changed: validate_cursor(); end_pos = curwin->w_cursor; curwin->w_cursor = save_pos; - } else - end_pos = curwin->w_cursor; /* shutup gcc 4 */ + } else { + end_pos = curwin->w_cursor; // shutup gcc 4 + } 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; + // May redraw the status line to show the cursor position. + if (p_ru && curwin->w_status_height > 0) { + curwin->w_redr_status = true; + } - save_cmdline(&save_ccline); + save_cmdline(&s->save_ccline); update_screen(SOME_VALID); - restore_cmdline(&save_ccline); + restore_cmdline(&s->save_ccline); - /* Leave it at the end to make CTRL-R CTRL-W work. */ - if (i != 0) + // Leave it at the end to make CTRL-R CTRL-W work. + if (s->i != 0) { curwin->w_cursor = end_pos; + } msg_starthere(); redrawcmdline(); - did_incsearch = TRUE; + 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) + 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(); + } + } } returncmd: - cmdmsg_rl = FALSE; + cmdmsg_rl = false; cmd_fkmap = 0; - ExpandCleanup(&xpc); + ExpandCleanup(&s->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 */ + 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 && firstc != NUL - && (some_key_typed || histype == HIST_SEARCH)) { - add_to_history(histype, ccline.cmdbuff, TRUE, - histype == HIST_SEARCH ? firstc : NUL); - if (firstc == ':') { + // 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 (gotesc) { /* abandon command line */ + if (s->gotesc) { // abandon command line xfree(ccline.cmdbuff); ccline.cmdbuff = NULL; - if (msg_scrolled == 0) + if (msg_scrolled == 0) { compute_cmdrow(); + } MSG(""); - redraw_cmdline = TRUE; + redraw_cmdline = true; } } - /* - * 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. - */ + // 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; + msg_scroll = s->save_msg_scroll; + redir_off = false; - /* When the command line was typed, no need for a wait-return prompt. */ - if (some_key_typed) - need_wait_return = FALSE; + // When the command line was typed, no need for a wait-return prompt. + if (s->some_key_typed) { + need_wait_return = false; + } - State = save_State; + State = s->save_State; setmouse(); - ui_cursor_shape(); /* may show different cursor shape */ + ui_cursor_shape(); // may show different cursor shape { char_u *p = ccline.cmdbuff; - /* Make ccline empty, getcmdline() may try to use it. */ + // Make ccline empty, getcmdline() may try to use it. ccline.cmdbuff = NULL; return p; } From 0701e1bfa4ba5b54748d9f99b39ca007177a898b Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Fri, 23 Oct 2015 08:22:16 -0300 Subject: [PATCH 19/23] ex_getln: refactor command line mode to use the `state_enter` loop Split `getcmdline()` into command_line_{enter,check,execute}` --- src/nvim/ex_getln.c | 2558 ++++++++++++++++++++++--------------------- 1 file changed, 1284 insertions(+), 1274 deletions(-) diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 38670052bb..19b21875ad 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -160,32 +160,7 @@ 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) { CommandLineState state, *s = &state; memset(s, 0, sizeof(CommandLineState)); @@ -298,1254 +273,9 @@ getcmdline ( 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 - - cursorcmd(); // set the cursor on the right spot - - // Get a character. Ignore K_IGNORE, it should not do anything, such - // as stop completion. - input_enable_events(); - do { - s->c = safe_vgetc(); - } while (s->c == K_IGNORE || s->c == K_PASTE); - input_disable_events(); - - if (s->c == K_EVENT) { - queue_process_events(loop.events); - continue; - } - - if (KeyTyped) { - s->some_key_typed = true; - if (cmd_hkmap) { - s->c = hkmap(s->c); - } - - if (cmd_fkmap) { - s->c = cmdl_fkmap(s->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 (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; - } - } - } - - // 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 ). - // 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 works like - // CTRL-P (unless 'wc' is ). - 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 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 : 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; - } else { - found = true; - } - } - } - if (s->i > 0) { - cmdline_del(s->i); - } - s->c = p_wc; - s->xpc.xp_context = EXPAND_NOTHING; - } - } - 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 (vim_ispathsep(ccline.cmdbuff[s->j])) { - found = true; - break; - } - } - 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 -#endif - ) { - if (found) { - s->i = s->j + 1; - break; - } else { - found = 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; - - // 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(); - goto cmdline_changed; - } - } - 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 (s->c == Ctrl_G && p_im && restart_edit == 0) { - restart_edit = 'a'; - } - s->gotesc = true; // will free ccline.cmdbuff after putting it - // in history - goto returncmd; // back to Normal mode - } - } - - 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 { - s->gotesc = false; // Might have typed ESC previously, don't - // truncate the cmdline now. - if (ccheck_abbr(s->c + ABBR_OFF)) { - goto cmdline_changed; - } - - if (!cmd_silent) { - ui_cursor_goto(msg_row, 0); - ui_flush(); - } - break; - } - } - - // Completion for 'wildchar' or 'wildcharm' key. - // - hitting 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 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; - goto cmdline_changed; - } - - // 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; - } - - (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 { - vim_beep(BO_WILD); - } - } else if (s->xpc.xp_numfiles == -1) { - s->xpc.xp_context = EXPAND_NOTHING; - } - } - - if (s->wim_index < 3) { - ++s->wim_index; - } - - if (s->c == ESC) { - s->gotesc = true; - } - - if (s->res == OK) { - goto cmdline_changed; - } - } - - s->gotesc = false; - - // 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) { - goto cmdline_changed; - } - } - - if (s->c == NUL || s->c == K_ZERO) { - // NUL is stored as NL - s->c = NL; - } - - s->do_abbr = true; // default: check for abbreviation - - // 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); - } - - 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; - } - - s->i = vim_iswordc(p[-1]); - while (p > ccline.cmdbuff && !ascii_isspace(p[-1]) - && vim_iswordc(p[-1]) == s->i) - --p; - } else { - --p; - } - - 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++]; - } - - // Truncate at the end, required for multi-byte chars. - ccline.cmdbuff[ccline.cmdlen] = NUL; - redrawcmd(); - } 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 == '>') { - 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 (s->b_im_ptr != NULL) { - if (State & LANGMAP) { - *s->b_im_ptr = B_IMODE_LMAP; - } else { - *s->b_im_ptr = B_IMODE_NONE; - } - } - } - - 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(); - goto cmdline_not_changed; - - // 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++]; - } - - // 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; - } - - s->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; - s->i = s->c = plain_vgetc(); // CTRL-R - if (s->i == Ctrl_O) { - s->i = Ctrl_R; // CTRL-R CTRL-O == CTRL-R CTRL-R - } - - if (s->i == Ctrl_R) { - s->c = plain_vgetc(); // CTRL-R CTRL-R - } - --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); - } - } - - 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 - 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; - } - } - } - redrawcmd(); - goto cmdline_changed; - - case Ctrl_D: - if (showmatches(&s->xpc, false) == EXPAND_NOTHING) { - break; // Use ^D as normal char instead - } - - redrawcmd(); - continue; // don't do incremental search now - - case K_RIGHT: - case K_S_RIGHT: - case K_C_RIGHT: - do { - if (ccline.cmdpos >= ccline.cmdlen) { - break; - } - - s->i = cmdline_charsize(ccline.cmdpos); - if (KeyTyped && ccline.cmdspos + s->i >= Columns * Rows) { - break; - } - - 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(); - } - 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 - && (s->c == K_S_LEFT || s->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 K_IGNORE: - // Ignore mouse event or ex_window() result. - goto cmdline_not_changed; - - - case K_MIDDLEDRAG: - case K_MIDDLERELEASE: - goto cmdline_not_changed; // Ignore mouse - - 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 (s->ignore_drag_release) { - goto cmdline_not_changed; - } - // 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)) { - goto cmdline_not_changed; // 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; - } - 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; - - case Ctrl_A: // all matches - if (nextwild(&s->xpc, WILD_ALL, 0, s->firstc != '@') == FAIL) - break; - goto cmdline_changed; - - 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 (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; - } - } - goto cmdline_not_changed; - } - - // completion: longest common part - if (nextwild(&s->xpc, WILD_LONGEST, 0, s->firstc != '@') == FAIL) { - break; - } - goto cmdline_changed; - - 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; - } - goto cmdline_changed; - } - - 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 - goto cmdline_not_changed; - } - - 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 { - // 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; - } - - // 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; - } - } - - if (s->hiscnt < 0 || history[s->histype][s->hiscnt].hisstr == NULL) { - s->hiscnt = s->i; - break; - } - - 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 (s->hiscnt != s->i) { - // jumped to other entry - char_u *p; - int len; - int old_firstc; - - xfree(ccline.cmdbuff); - s->xpc.xp_context = EXPAND_NOTHING; - if (s->hiscnt == hislen) { - p = s->lookfor; // back to the old one - } else { - p = history[s->histype][s->hiscnt].hisstr; - } - - 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; - } - - if (s->i > 0) { - ccline.cmdbuff[len] = p[s->j]; - } - } - ++len; - } - - if (s->i == 0) { - alloc_cmdbuff(len); - } - } - ccline.cmdbuff[len] = NUL; - } else { - alloc_cmdbuff((int)STRLEN(p)); - STRCPY(ccline.cmdbuff, p); - } - - ccline.cmdpos = ccline.cmdlen = (int)STRLEN(ccline.cmdbuff); - redrawcmd(); - goto cmdline_changed; - } - beep_flush(); - goto cmdline_not_changed; - - 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; - - case Ctrl_K: - s->ignore_drag_release = true; - putcmdline('?', true); - s->c = get_digraph(true); - - if (s->c != NUL) { - break; - } - - redrawcmd(); - goto cmdline_not_changed; - - case Ctrl__: // CTRL-_: switch language mode - if (!p_ari) { - 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: - // 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 . This should only happen after ^V. - if (!IS_SPECIAL(s->c)) { - mod_mask = 0x0; - } - break; - } - - // 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)) { - goto cmdline_changed; - } - - // 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); - } - } - 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 (!s->incsearch_postponed) { - continue; - } - -cmdline_changed: - // '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; - continue; - } - 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 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 (s->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 = 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 - } - - 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; - } - - save_cmdline(&s->save_ccline); - update_screen(SOME_VALID); - restore_cmdline(&s->save_ccline); - - // Leave it at the end to make CTRL-R CTRL-W work. - if (s->i != 0) { - curwin->w_cursor = end_pos; - } - - 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(); - } - } - } - -returncmd: + s->state.check = command_line_check; + s->state.execute = command_line_execute; + state_enter(&s->state); cmdmsg_rl = false; @@ -1614,6 +344,1286 @@ returncmd: } } +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); + } + + if (cmd_fkmap) { + s->c = cmdl_fkmap(s->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 (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; + } + } + } + + // 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 ). + // 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 works like + // CTRL-P (unless 'wc' is ). + 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 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 : 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; + } else { + found = true; + } + } + } + if (s->i > 0) { + cmdline_del(s->i); + } + s->c = p_wc; + s->xpc.xp_context = EXPAND_NOTHING; + } + } + 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 (vim_ispathsep(ccline.cmdbuff[s->j])) { + found = true; + break; + } + } + 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 +#endif + ) { + if (found) { + s->i = s->j + 1; + break; + } else { + found = 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; + + // 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(); + goto cmdline_changed; + } + } + 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 (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 (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 { + s->gotesc = false; // Might have typed ESC previously, don't + // truncate the cmdline now. + if (ccheck_abbr(s->c + ABBR_OFF)) { + goto cmdline_changed; + } + + if (!cmd_silent) { + ui_cursor_goto(msg_row, 0); + ui_flush(); + } + return 0; + } + } + + // Completion for 'wildchar' or 'wildcharm' key. + // - hitting 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 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; + goto cmdline_changed; + } + + // 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; + } + + (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 { + vim_beep(BO_WILD); + } + } else if (s->xpc.xp_numfiles == -1) { + s->xpc.xp_context = EXPAND_NOTHING; + } + } + + if (s->wim_index < 3) { + ++s->wim_index; + } + + if (s->c == ESC) { + s->gotesc = true; + } + + if (s->res == OK) { + goto cmdline_changed; + } + } + + s->gotesc = false; + + // 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) { + goto cmdline_changed; + } + } + + if (s->c == NUL || s->c == K_ZERO) { + // NUL is stored as NL + s->c = NL; + } + + s->do_abbr = true; // default: check for abbreviation + + // 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); + } + + 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; + } + + s->i = vim_iswordc(p[-1]); + while (p > ccline.cmdbuff && !ascii_isspace(p[-1]) + && vim_iswordc(p[-1]) == s->i) + --p; + } else { + --p; + } + + 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++]; + } + + // Truncate at the end, required for multi-byte chars. + ccline.cmdbuff[ccline.cmdlen] = NUL; + redrawcmd(); + } 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 == '>') { + 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; + return 0; // 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 (s->b_im_ptr != NULL) { + if (State & LANGMAP) { + *s->b_im_ptr = B_IMODE_LMAP; + } else { + *s->b_im_ptr = B_IMODE_NONE; + } + } + } + + 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(); + goto cmdline_not_changed; + + // 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++]; + } + + // 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; + } + + 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 + if (s->i == Ctrl_O) { + s->i = Ctrl_R; // CTRL-R CTRL-O == CTRL-R CTRL-R + } + + if (s->i == Ctrl_R) { + s->c = plain_vgetc(); // CTRL-R CTRL-R + } + --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); + } + } + + 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; + } + } + } + redrawcmd(); + goto cmdline_changed; + + 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; + } + + s->i = cmdline_charsize(ccline.cmdpos); + if (KeyTyped && ccline.cmdspos + s->i >= Columns * Rows) { + break; + } + + 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(); + } + 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 + && (s->c == K_S_LEFT || s->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 K_IGNORE: + // Ignore mouse event or ex_window() result. + goto cmdline_not_changed; + + + case K_MIDDLEDRAG: + case K_MIDDLERELEASE: + goto cmdline_not_changed; // Ignore mouse + + 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 (s->ignore_drag_release) { + goto cmdline_not_changed; + } + // 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)) { + goto cmdline_not_changed; // 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; + } + 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; + + case Ctrl_A: // all matches + if (nextwild(&s->xpc, WILD_ALL, 0, s->firstc != '@') == FAIL) + break; + goto cmdline_changed; + + 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 (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; + } + } + goto cmdline_not_changed; + } + + // completion: longest common part + if (nextwild(&s->xpc, WILD_LONGEST, 0, s->firstc != '@') == FAIL) { + break; + } + goto cmdline_changed; + + 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; + } + goto cmdline_changed; + } + + 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 + goto cmdline_not_changed; + } + + 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 { + // 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; + } + + // 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; + } + } + + if (s->hiscnt < 0 || history[s->histype][s->hiscnt].hisstr == NULL) { + s->hiscnt = s->i; + break; + } + + 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 (s->hiscnt != s->i) { + // jumped to other entry + char_u *p; + int len; + int old_firstc; + + xfree(ccline.cmdbuff); + s->xpc.xp_context = EXPAND_NOTHING; + if (s->hiscnt == hislen) { + p = s->lookfor; // back to the old one + } else { + p = history[s->histype][s->hiscnt].hisstr; + } + + 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; + } + + if (s->i > 0) { + ccline.cmdbuff[len] = p[s->j]; + } + } + ++len; + } + + if (s->i == 0) { + alloc_cmdbuff(len); + } + } + ccline.cmdbuff[len] = NUL; + } else { + alloc_cmdbuff((int)STRLEN(p)); + STRCPY(ccline.cmdbuff, p); + } + + ccline.cmdpos = ccline.cmdlen = (int)STRLEN(ccline.cmdbuff); + redrawcmd(); + goto cmdline_changed; + } + beep_flush(); + goto cmdline_not_changed; + + 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; + + case Ctrl_K: + s->ignore_drag_release = true; + putcmdline('?', true); + s->c = get_digraph(true); + + if (s->c != NUL) { + break; + } + + redrawcmd(); + goto cmdline_not_changed; + + case Ctrl__: // CTRL-_: switch language mode + if (!p_ari) { + 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: + // 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 . This should only happen after ^V. + if (!IS_SPECIAL(s->c)) { + mod_mask = 0x0; + } + break; + } + + // 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)) { + goto cmdline_changed; + } + + // 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); + } + } + 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 (!s->incsearch_postponed) { + return 1; + } + +cmdline_changed: + // '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 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 (s->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 = 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 + } + + 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; + } + + save_cmdline(&s->save_ccline); + update_screen(SOME_VALID); + restore_cmdline(&s->save_ccline); + + // Leave it at the end to make CTRL-R CTRL-W work. + if (s->i != 0) { + curwin->w_cursor = end_pos; + } + + 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); +} + /* * Get a command line with a prompt. * This is prepared to be called recursively from getcmdline() (e.g. by From c0c562f97fccd02d61048b444c59e8d0fe4cf833 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Fri, 23 Oct 2015 08:53:16 -0300 Subject: [PATCH 20/23] ex_getln: Convert `cmdline_{not_,}changed` labels into functions --- src/nvim/ex_getln.c | 86 ++++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 40 deletions(-) diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 19b21875ad..13515be8fa 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -678,7 +678,7 @@ static int command_line_execute(VimState *state, int key) KeyTyped = false; // Don't do p_wc completion. redrawcmd(); - goto cmdline_changed; + return command_line_changed(s); } } beep_flush(); @@ -686,7 +686,7 @@ static int command_line_execute(VimState *state, int key) did_emsg = false; emsg_on_display = false; redrawcmd(); - goto cmdline_not_changed; + return command_line_not_changed(s); } else { if (s->c == Ctrl_G && p_im && restart_edit == 0) { restart_edit = 'a'; @@ -725,7 +725,7 @@ static int command_line_execute(VimState *state, int key) s->gotesc = false; // Might have typed ESC previously, don't // truncate the cmdline now. if (ccheck_abbr(s->c + ABBR_OFF)) { - goto cmdline_changed; + return command_line_changed(s); } if (!cmd_silent) { @@ -780,7 +780,7 @@ static int command_line_execute(VimState *state, int key) got_int = false; // don't abandon the command line (void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE); s->xpc.xp_context = EXPAND_NOTHING; - goto cmdline_changed; + return command_line_changed(s); } // when more than one match, and 'wildmode' first contains @@ -831,7 +831,7 @@ static int command_line_execute(VimState *state, int key) } if (s->res == OK) { - goto cmdline_changed; + return command_line_changed(s); } } @@ -842,7 +842,7 @@ static int command_line_execute(VimState *state, int key) 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) { - goto cmdline_changed; + return command_line_changed(s); } } @@ -928,7 +928,7 @@ static int command_line_execute(VimState *state, int key) && ccline.cmdprompt == NULL && s->indent == 0) { // In ex and debug mode it doesn't make sense to return. if (exmode_active || ccline.cmdfirstc == '>') { - goto cmdline_not_changed; + return command_line_not_changed(s); } xfree(ccline.cmdbuff); // no commandline to return @@ -944,7 +944,7 @@ static int command_line_execute(VimState *state, int key) redraw_cmdline = true; return 0; // back to cmd mode } - goto cmdline_changed; + return command_line_changed(s); case K_INS: case K_KINS: @@ -957,7 +957,7 @@ static int command_line_execute(VimState *state, int key) } ui_cursor_shape(); // may show different cursor shape - goto cmdline_not_changed; + return command_line_not_changed(s); case Ctrl_HAT: if (map_to_exists_mode((char_u *)"", LANGMAP, false)) { @@ -982,7 +982,7 @@ static int command_line_execute(VimState *state, int key) ui_cursor_shape(); // may show different cursor shape // Show/unshow value of 'keymap' in status lines later. status_redraw_curbuf(); - goto cmdline_not_changed; + return command_line_not_changed(s); // case '@': only in very old vi case Ctrl_U: @@ -997,7 +997,7 @@ static int command_line_execute(VimState *state, int key) // Truncate at the end, required for multi-byte chars. ccline.cmdbuff[ccline.cmdlen] = NUL; redrawcmd(); - goto cmdline_changed; + return command_line_changed(s); case ESC: // get here if p_wc != ESC or when ESC typed twice @@ -1005,7 +1005,7 @@ static int command_line_execute(VimState *state, int key) // 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; + return command_line_not_changed(s); } s->gotesc = true; // will free ccline.cmdbuff after @@ -1060,7 +1060,7 @@ static int command_line_execute(VimState *state, int key) } } redrawcmd(); - goto cmdline_changed; + return command_line_changed(s); case Ctrl_D: if (showmatches(&s->xpc, false) == EXPAND_NOTHING) { @@ -1096,13 +1096,13 @@ static int command_line_execute(VimState *state, int key) if (has_mbyte) { set_cmdspos_cursor(); } - goto cmdline_not_changed; + return command_line_not_changed(s); case K_LEFT: case K_S_LEFT: case K_C_LEFT: if (ccline.cmdpos == 0) { - goto cmdline_not_changed; + return command_line_not_changed(s); } do { --ccline.cmdpos; @@ -1120,24 +1120,24 @@ static int command_line_execute(VimState *state, int key) set_cmdspos_cursor(); } - goto cmdline_not_changed; + return command_line_not_changed(s); case K_IGNORE: // Ignore mouse event or ex_window() result. - goto cmdline_not_changed; + return command_line_not_changed(s); case K_MIDDLEDRAG: case K_MIDDLERELEASE: - goto cmdline_not_changed; // Ignore mouse + return command_line_not_changed(s); // Ignore mouse case K_MIDDLEMOUSE: if (!mouse_has(MOUSE_COMMAND)) { - goto cmdline_not_changed; // Ignore mouse + return command_line_not_changed(s); // Ignore mouse } cmdline_paste(0, true, true); redrawcmd(); - goto cmdline_changed; + return command_line_changed(s); case K_LEFTDRAG: @@ -1147,7 +1147,7 @@ static int command_line_execute(VimState *state, int key) // Ignore drag and release events when the button-down wasn't // seen before. if (s->ignore_drag_release) { - goto cmdline_not_changed; + return command_line_not_changed(s); } // FALLTHROUGH case K_LEFTMOUSE: @@ -1159,7 +1159,7 @@ static int command_line_execute(VimState *state, int key) } if (!mouse_has(MOUSE_COMMAND)) { - goto cmdline_not_changed; // Ignore mouse + return command_line_not_changed(s); // Ignore mouse } set_cmdspos(); @@ -1179,7 +1179,7 @@ static int command_line_execute(VimState *state, int key) } ccline.cmdspos += s->i; } - goto cmdline_not_changed; + return command_line_not_changed(s); // Mouse scroll wheel: ignored here case K_MOUSEDOWN: @@ -1193,12 +1193,12 @@ static int command_line_execute(VimState *state, int key) case K_X2MOUSE: case K_X2DRAG: case K_X2RELEASE: - goto cmdline_not_changed; + return command_line_not_changed(s); case K_SELECT: // end of Select mode mapping - ignore - goto cmdline_not_changed; + return command_line_not_changed(s); case Ctrl_B: // begin of command line case K_HOME: @@ -1207,7 +1207,7 @@ static int command_line_execute(VimState *state, int key) case K_C_HOME: ccline.cmdpos = 0; set_cmdspos(); - goto cmdline_not_changed; + return command_line_not_changed(s); case Ctrl_E: // end of command line case K_END: @@ -1216,12 +1216,12 @@ static int command_line_execute(VimState *state, int key) case K_C_END: ccline.cmdpos = ccline.cmdlen; set_cmdspos_cursor(); - goto cmdline_not_changed; + return command_line_not_changed(s); case Ctrl_A: // all matches if (nextwild(&s->xpc, WILD_ALL, 0, s->firstc != '@') == FAIL) break; - goto cmdline_changed; + return command_line_changed(s); case Ctrl_L: if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) { @@ -1246,14 +1246,14 @@ static int command_line_execute(VimState *state, int key) break; } } - goto cmdline_not_changed; + return command_line_not_changed(s); } // completion: longest common part if (nextwild(&s->xpc, WILD_LONGEST, 0, s->firstc != '@') == FAIL) { break; } - goto cmdline_changed; + return command_line_changed(s); case Ctrl_N: // next match case Ctrl_P: // previous match @@ -1262,7 +1262,7 @@ static int command_line_execute(VimState *state, int key) 0, s->firstc != '@') == FAIL) { break; } - goto cmdline_changed; + return command_line_changed(s); } case K_UP: @@ -1275,7 +1275,7 @@ static int command_line_execute(VimState *state, int key) case K_KPAGEDOWN: if (hislen == 0 || s->firstc == NUL) { // no history - goto cmdline_not_changed; + return command_line_not_changed(s); } s->i = s->hiscnt; @@ -1397,10 +1397,10 @@ static int command_line_execute(VimState *state, int key) ccline.cmdpos = ccline.cmdlen = (int)STRLEN(ccline.cmdbuff); redrawcmd(); - goto cmdline_changed; + return command_line_changed(s); } beep_flush(); - goto cmdline_not_changed; + return command_line_not_changed(s); case Ctrl_V: case Ctrl_Q: @@ -1426,7 +1426,7 @@ static int command_line_execute(VimState *state, int key) } redrawcmd(); - goto cmdline_not_changed; + return command_line_not_changed(s); case Ctrl__: // CTRL-_: switch language mode if (!p_ari) { @@ -1442,7 +1442,7 @@ static int command_line_execute(VimState *state, int key) // Hebrew is default cmd_hkmap = !cmd_hkmap; } - goto cmdline_not_changed; + return command_line_not_changed(s); default: // Normal character with no special meaning. Just set mod_mask @@ -1462,7 +1462,7 @@ static int command_line_execute(VimState *state, int key) && (ccheck_abbr((has_mbyte && s->c >= 0x100) ? (s->c + ABBR_OFF) : s->c) || s->c == Ctrl_RSB)) { - goto cmdline_changed; + return command_line_changed(s); } // put the character in the command line @@ -1478,19 +1478,25 @@ static int command_line_execute(VimState *state, int key) put_on_cmdline(IObuff, 1, true); } } - goto cmdline_changed; + return command_line_changed(s); +} + +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). -cmdline_not_changed: if (!s->incsearch_postponed) { return 1; } + return command_line_changed(s); +} -cmdline_changed: +static int command_line_changed(CommandLineState *s) +{ // 'incsearch' highlighting. if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) { pos_T end_pos; From ed985d06efdecaa5262cb2f713696edec3147ac9 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Fri, 23 Oct 2015 08:56:53 -0300 Subject: [PATCH 21/23] ex_getln: Extract command_line_handle_key from command_line_execute --- src/nvim/ex_getln.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 13515be8fa..52292128d8 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -852,7 +852,11 @@ static int command_line_execute(VimState *state, int key) } 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: From b4c82dba5cd33c4025274b8da030ddaac0b2824e Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Fri, 23 Oct 2015 15:21:40 -0300 Subject: [PATCH 22/23] Start documenting code --- src/nvim/README.md | 190 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 src/nvim/README.md 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` +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` +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 == '': + if data['input']: + execute_ex_command(data['input']) + return false + elif key == '' + 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 == '': + 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). From 1726c7d999e68b4ed8aee234b7dfa339ed0784b2 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Mon, 26 Oct 2015 09:56:24 -0300 Subject: [PATCH 23/23] edit.c: Fix one clint warning --- src/nvim/edit.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 31fc50d4d6..abd16e57ae 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -7782,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);