From 8b7b71f4742d94cf7a2e5a08e7b2f5a725619476 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Wed, 18 Mar 2015 12:34:36 -0300 Subject: ui: Refactor so that busy state won't be the default Even though assuming nvim is busy most times is simpler, it has a problem: A lot of unnecessary busy_start/busy_stop notifications are sent to the UI. That's because in the majority of scenarios almost no time is spent between `event_poll` calls. This restores the normal behavior which is to call busy_start only when nvim is going to perform some task that can take a significant amount of time. Also improve the usage of buffering in the TUI when changing the cursor state. --- src/nvim/ex_getln.c | 2 ++ src/nvim/getchar.c | 5 +++++ src/nvim/misc1.c | 1 + src/nvim/os/event.h | 9 --------- src/nvim/os/shell.c | 1 + src/nvim/tui/tui.c | 40 +++++++++++++++++++++------------------- src/nvim/ui.c | 5 +---- 7 files changed, 31 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index c9e990b713..d5f7a218f4 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -1364,6 +1364,7 @@ cmdline_changed: if (ccline.cmdlen == 0) i = 0; else { + ui_busy_start(); ui_flush(); ++emsg_off; /* So it doesn't beep if bad expr */ /* Set the time limit to half a second. */ @@ -1381,6 +1382,7 @@ cmdline_changed: } else if (char_avail()) /* cancelled searching because a char was typed */ incsearch_postponed = TRUE; + ui_busy_stop(); } if (i != 0) highlight_match = TRUE; /* highlight position */ diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 41b3d9dda8..96ce586a9e 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -2379,6 +2379,11 @@ inchar ( int retesc = FALSE; /* return ESC with gotint */ int script_char; + if (wait_time == -1L || wait_time > 100L) { + // flush output before waiting + ui_flush(); + } + /* * Don't reset these when at the hit-return prompt, otherwise an endless * recursive loop may result (write error in swapfile, hit-return, timeout diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index 07cbc0f7c4..c9f3fbd511 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -2364,6 +2364,7 @@ int get_keystroke(void) mapped_ctrl_c = FALSE; /* mappings are not used here */ for (;; ) { + // flush output before waiting ui_flush(); /* Leave some room for check_termcode() to insert a key code into (max * 5 chars plus NUL). And fix_input_buffer() can triple the number of diff --git a/src/nvim/os/event.h b/src/nvim/os/event.h index 986db51431..db02b38c7f 100644 --- a/src/nvim/os/event.h +++ b/src/nvim/os/event.h @@ -8,15 +8,9 @@ #include "nvim/os/job_defs.h" #include "nvim/os/time.h" -void ui_busy_start(void); -void ui_busy_stop(void); - // Poll for events until a condition or timeout #define event_poll_until(timeout, condition) \ do { \ - if (timeout < 0 || timeout > 100) { \ - ui_busy_stop(); \ - } \ int remaining = timeout; \ uint64_t before = (remaining > 0) ? os_hrtime() : 0; \ while (!(condition)) { \ @@ -32,9 +26,6 @@ void ui_busy_stop(void); } \ } \ } \ - if (timeout < 0 || timeout > 100) { \ - ui_busy_start(); \ - } \ } while (0) #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 290d6a9ec9..6fcb62a5f3 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -241,6 +241,7 @@ static int shell(const char *cmd, // invoke busy_start here so event_poll_until wont change the busy state for // the UI ui_busy_start(); + ui_flush(); status = job_wait(job, -1); ui_busy_stop(); diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 4f858456f9..4736e7a8ba 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -16,6 +16,11 @@ #include "nvim/os/event.h" #include "nvim/tui/tui.h" +// Space reserved in the output buffer to restore the cursor to normal when +// flushing. No existing terminal will require 32 bytes to do that. +#define CNORM_COMMAND_MAX_SIZE 32 +#define OUTBUF_SIZE 0xffff + typedef struct term_input TermInput; #include "term_input.inl" @@ -31,8 +36,8 @@ typedef struct { typedef struct { unibi_var_t params[9]; - char buf[0xffff]; - size_t bufpos; + char buf[OUTBUF_SIZE]; + size_t bufpos, bufsize; TermInput *input; uv_loop_t *write_loop; unibi_term *ut; @@ -81,13 +86,13 @@ typedef struct { void tui_start(void) { TUIData *data = xcalloc(1, sizeof(TUIData)); - data->busy = true; UI *ui = xcalloc(1, sizeof(UI)); ui->data = data; data->attrs = data->print_attrs = EMPTY_ATTRS; data->fg = data->bg = -1; data->can_use_terminal_scroll = true; data->bufpos = 0; + data->bufsize = sizeof(data->buf) - CNORM_COMMAND_MAX_SIZE; data->unibi_ext.enable_mouse = -1; data->unibi_ext.disable_mouse = -1; data->unibi_ext.enable_bracketed_paste = -1; @@ -108,7 +113,6 @@ void tui_start(void) data->ut = unibi_dummy(); } fix_terminfo(data); - unibi_out(ui, unibi_cursor_invisible); // Enter alternate screen and clear unibi_out(ui, unibi_enter_ca_mode); unibi_out(ui, unibi_clear_screen); @@ -172,6 +176,7 @@ static void tui_stop(UI *ui) tui_normal_mode(ui); tui_mouse_off(ui); unibi_out(ui, unibi_exit_attribute_mode); + // cursor should be set to normal before exiting alternate screen unibi_out(ui, unibi_cursor_normal); unibi_out(ui, unibi_exit_ca_mode); // Disable bracketed paste @@ -661,7 +666,7 @@ static void out(void *ctx, const char *str, size_t len) { UI *ui = ctx; TUIData *data = ui->data; - size_t available = sizeof(data->buf) - data->bufpos; + size_t available = data->bufsize - data->bufpos; if (len > available) { flush_buf(ui); @@ -786,29 +791,26 @@ end: static void flush_buf(UI *ui) { uv_write_t req; - uv_buf_t buf[2]; - unsigned int buf_count = 1; - + uv_buf_t buf; TUIData *data = ui->data; - buf[0].base = data->buf; - buf[0].len = data->bufpos; - - char normal_buf[64]; if (!data->busy) { - // Cannot use unibi_out(ui, unibi_cursor_normal), in case there is not - // enough remaining space in data->buf. - const char *str = unibi_get_str(data->ut, unibi_cursor_normal); - buf[1].base = normal_buf; - buf[1].len = unibi_run(str, data->params, normal_buf, sizeof(normal_buf)); - buf_count++; + // not busy and the cursor is invisible(see below). Append a "cursor + // normal" command to the end of the buffer. + data->bufsize += CNORM_COMMAND_MAX_SIZE; + unibi_out(ui, unibi_cursor_normal); + data->bufsize -= CNORM_COMMAND_MAX_SIZE; } - uv_write(&req, (uv_stream_t *)&data->output_handle, buf, buf_count, NULL); + buf.base = data->buf; + buf.len = data->bufpos; + uv_write(&req, (uv_stream_t *)&data->output_handle, &buf, 1, NULL); uv_run(data->write_loop, UV_RUN_DEFAULT); data->bufpos = 0; if (!data->busy) { + // not busy and cursor is visible(see above), append a "cursor invisible" + // command to the beginning of the buffer for the next flush unibi_out(ui, unibi_cursor_invisible); } } diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 2189021d22..443b50da87 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -45,7 +45,7 @@ static struct { } sr; static int current_attr_code = 0; static bool pending_cursor_update = false; -static int busy = 1; +static int busy = 0; static int height, width; // This set of macros allow us to use UI_CALL to invoke any function on @@ -155,7 +155,6 @@ void ui_busy_start(void) { if (!(busy++)) { UI_CALL(busy_start); - ui_flush(); } } @@ -163,11 +162,9 @@ void ui_busy_stop(void) { if (!(--busy)) { UI_CALL(busy_stop); - ui_flush(); } } - void ui_mouse_on(void) { UI_CALL(mouse_on); -- cgit From 4d63d9917482a7d7a002bf7688233a675b76cd3e Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Wed, 18 Mar 2015 12:46:04 -0300 Subject: main: Simplify code that deals with early user input A read stream will be started before the first ex command is processed. This stream will be used to read early user input before handling control over to the UI module. Which stdio stream will be used depends on which types of file descriptors are connected, and whether the "-" argument was passed. --- src/nvim/main.c | 54 +++++++++++++++++++++++++++-------------------------- src/nvim/os/input.c | 6 +++--- 2 files changed, 31 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/nvim/main.c b/src/nvim/main.c index 9479d80f31..2f1e6e6d3b 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -268,16 +268,26 @@ int main(int argc, char **argv) /* Set the break level after the terminal is initialized. */ debug_break_level = params.use_debug_break_level; + bool reading_input = !params.headless && (params.input_isatty + || params.output_isatty || params.err_isatty); + + if (reading_input) { + // Its possible that one of the startup commands(arguments, sourced scripts + // or plugins) will prompt the user, so start reading from a tty stream + // now. + int fd = fileno(stdin); + if (!params.input_isatty || params.edit_type == EDIT_STDIN) { + // use stderr or stdout since stdin is not a tty and/or could be used to + // read the file we'll edit when the "-" argument is given(eg: cat file | + // nvim -) + fd = params.err_isatty ? fileno(stderr) : fileno(stdout); + } + input_start_stdin(fd); + } + /* Execute --cmd arguments. */ exe_pre_commands(¶ms); - if (!params.headless && params.input_isatty) { - // Its possible that one of the scripts sourced at startup will prompt the - // user, so start stdin now. TODO(tarruda): This is only for compatibility. - // Startup user prompting should be done in the VimEnter autocmd - input_start_stdin(); - } - /* Source startup scripts. */ source_startup_scripts(¶ms); @@ -358,22 +368,19 @@ int main(int argc, char **argv) if (params.edit_type == EDIT_STDIN && !recoverymode) read_stdin(); - if (!params.headless) { - if ((params.output_isatty || params.err_isatty) - && (need_wait_return || msg_didany)) { - // Since at this point there's no UI instance running yet, error messages - // would have been printed to stdout. Before starting (which can result in - // a alternate screen buffer being shown) we need confirmation that the - // user has seen the messages and that is done with a call to wait_return. - TIME_MSG("waiting for return"); - wait_return(TRUE); - } - if (params.input_isatty) { - // Stop reading from stdin, the UI module will take over now. - input_stop_stdin(); - } + if (reading_input && (need_wait_return || msg_didany)) { + // Since at this point there's no UI instance running yet, error messages + // would have been printed to stdout. Before starting (which can result in + // a alternate screen buffer being shown) we need confirmation that the + // user has seen the messages and that is done with a call to wait_return. + TIME_MSG("waiting for return"); + wait_return(TRUE); + } + if (!params.headless) { + // Stop reading from stdin, the UI layer will take over now + input_stop_stdin(); ui_builtin_start(); } @@ -1465,11 +1472,6 @@ static void check_tty(mparm_T *parmp) TIME_MSG("Warning delay"); } - - if (parmp->edit_type != EDIT_STDIN && !parmp->input_isatty) { - // read commands from directly from stdin - input_start_stdin(); - } } /* diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 00efa28161..a409a9ed13 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -46,7 +46,7 @@ void input_init(void) input_buffer = rbuffer_new(INPUT_BUFFER_SIZE + MAX_KEY_CODE_LEN); } -void input_start_stdin(void) +void input_start_stdin(int fd) { if (read_stream) { return; @@ -54,7 +54,7 @@ void input_start_stdin(void) read_buffer = rbuffer_new(READ_BUFFER_SIZE); read_stream = rstream_new(read_cb, read_buffer, NULL); - rstream_set_file(read_stream, fileno(stdin)); + rstream_set_file(read_stream, fd); rstream_start(read_stream); } @@ -69,7 +69,7 @@ void input_stop_stdin(void) read_stream = NULL; } -// Low level input function. +// Low level input function int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt) { if (rbuffer_pending(input_buffer)) { -- cgit