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'; } +