From d77dcde9fb1bce5e68076a5c74a96aae4a0cc094 Mon Sep 17 00:00:00 2001 From: bfredl Date: Mon, 2 May 2022 21:10:01 +0200 Subject: [PATCH] fix(tui): more work in the TUI --- runtime/doc/api.txt | 14 +- runtime/doc/news.txt | 9 + runtime/doc/remote.txt | 4 + src/nvim/api/keysets.lua | 5 + src/nvim/api/vim.c | 25 ++- src/nvim/charset.c | 11 ++ src/nvim/charset.h | 1 + src/nvim/event/libuv_process.c | 3 +- src/nvim/event/process.c | 3 +- src/nvim/generators/gen_api_dispatch.lua | 2 +- src/nvim/globals.h | 22 ++- src/nvim/highlight.c | 26 +-- src/nvim/main.c | 83 ++++----- src/nvim/msgpack_rpc/channel.c | 2 +- src/nvim/msgpack_rpc/unpacker.c | 25 +-- src/nvim/options.lua | 2 +- src/nvim/tui/input.c | 52 ++---- src/nvim/tui/terminfo.c | 71 ++++---- src/nvim/tui/terminfo.h | 2 + src/nvim/tui/tui.c | 117 ++++++------- src/nvim/ui.c | 13 +- src/nvim/ui_client.c | 60 ++++--- test/functional/core/startup_spec.lua | 2 +- test/functional/helpers.lua | 12 +- test/functional/terminal/tui_spec.lua | 205 +++++++++++------------ test/functional/ui/options_spec.lua | 1 + test/functional/ui/screen.lua | 3 +- 27 files changed, 401 insertions(+), 374 deletions(-) diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index d555cff4434077..215a5e26a36458 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -717,7 +717,7 @@ nvim_del_var({name}) *nvim_del_var()* Parameters: ~ • {name} Variable name -nvim_echo({chunks}, {history}, {opts}) *nvim_echo()* +nvim_echo({chunks}, {history}, {*opts}) *nvim_echo()* Echo a message. Parameters: ~ @@ -725,7 +725,11 @@ nvim_echo({chunks}, {history}, {opts}) *nvim_echo()* chunk with specified highlight. `hl_group` element can be omitted for no highlight. • {history} if true, add to |message-history|. - • {opts} Optional parameters. Reserved for future use. + • {opts} Optional parameters. + • verbose: Message was printed as a result of 'verbose' + option if Nvim was invoked with -V3log_file, the message + will be redirected to the log_file and surpressed from + direct output. nvim_err_write({str}) *nvim_err_write()* Writes a message to the Vim error buffer. Does not append "\n", the @@ -3467,6 +3471,12 @@ nvim_ui_pum_set_height({height}) *nvim_ui_pum_set_height()* Parameters: ~ • {height} Popupmenu height, must be greater than zero. +nvim_ui_set_focus({gained}) *nvim_ui_set_focus()* + Tells the nvim server if focus was gained by the GUI or not. + + Attributes: ~ + |RPC| only + nvim_ui_set_option({name}, {value}) *nvim_ui_set_option()* TODO: Documentation diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index bd0d1cfc5b40aa..d85ba19a00e48d 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -89,6 +89,11 @@ The following new APIs or features were added. See https://github.com/neovim/neovim/pull/14537. +• |--remote-ui| option was added to connect to a remote instance and display + in it in a |TUI| in the local terminal. This can be used run a headless nvim + instance in the background and display its UI on demand, which previously + only was possible usiing a external UI implementation. + ============================================================================== CHANGED FEATURES *news-changes* @@ -96,6 +101,10 @@ The following changes to existing APIs or features add new behavior. • 'exrc' is no longer marked deprecated. +• the |TUI| is changed to run in a separate process (previously, a separate + thread was used). This is not supposed to be a visible change to the user, + but might be the cause of subtle changes of behavior and bugs. + ============================================================================== REMOVED FEATURES *news-removed* diff --git a/runtime/doc/remote.txt b/runtime/doc/remote.txt index 0c1e3438dee18d..8b4f2da8fb45e1 100644 --- a/runtime/doc/remote.txt +++ b/runtime/doc/remote.txt @@ -52,6 +52,10 @@ The following command line arguments are available: *--remote-expr* --remote-expr {expr} Evaluate {expr} in server and print the result on stdout. + *--remote-ui* + --remote-ui {expr} Display the UI of the server in the terminal. + Fully interactive: keyboard and mouse input + are forwarded to the server. *--server* --server {addr} Connect to the named pipe or socket at the given address for executing remote commands. diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index 7e0d39957397df..8f909e937f61ca 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -126,6 +126,8 @@ return { "global_link"; "fallback"; "blend"; + "fg_indexed"; + "bg_indexed"; }; highlight_cterm = { "bold"; @@ -219,5 +221,8 @@ return { cmd_opts = { "output"; }; + echo_opts = { + "verbose"; + }; } diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 70b07dabe83136..3e9812a430634b 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -726,8 +726,11 @@ void nvim_set_vvar(String name, Object value, Error *err) /// text chunk with specified highlight. `hl_group` element /// can be omitted for no highlight. /// @param history if true, add to |message-history|. -/// @param opts Optional parameters. Reserved for future use. -void nvim_echo(Array chunks, Boolean history, Dictionary opts, Error *err) +/// @param opts Optional parameters. +/// - verbose: Message was printed as a result of 'verbose' option +/// if Nvim was invoked with -V3log_file, the message will be +/// redirected to the log_file and surpressed from direct output. +void nvim_echo(Array chunks, Boolean history, Dict(echo_opts) *opts, Error *err) FUNC_API_SINCE(7) { HlMessage hl_msg = parse_hl_msg(chunks, err); @@ -735,13 +738,19 @@ void nvim_echo(Array chunks, Boolean history, Dictionary opts, Error *err) goto error; } - if (opts.size > 0) { - api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); - goto error; + bool verbose = api_object_to_bool(opts->verbose, "verbose", false, err); + + if (verbose) { + verbose_enter(); } msg_multiattr(hl_msg, history ? "echomsg" : "echo", history); + if (verbose) { + verbose_leave(); + verbose_stop(); // flush now + } + if (history) { // history takes ownership return; @@ -1204,6 +1213,12 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err) draining = false; } + // TODO(bfredl): ugly, maybe only when notification? + if (ERROR_SET(err)) { + semsg("paste: %s", err->msg); + api_clear_error(err); + } + return !cancel; } diff --git a/src/nvim/charset.c b/src/nvim/charset.c index a8abee42be05ef..08264043bacf12 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -418,6 +418,17 @@ char *transstr(const char *const s, bool untab) return buf; } +size_t kv_transstr(StringBuilder *str, const char *const s, bool untab) +{ + // Compute the length of the result, taking account of unprintable + // multi-byte characters. + const size_t len = transstr_len(s, untab); + kv_ensure_space(*str, len + 1); + transstr_buf(s, str->items + str->size, len + 1, untab); + str->size += len; // do not include NUL byte + return len; +} + /// Convert the string "str[orglen]" to do ignore-case comparing. /// Use the current locale. /// diff --git a/src/nvim/charset.h b/src/nvim/charset.h index 978a357aa7ce4e..e1ef06ef1de217 100644 --- a/src/nvim/charset.h +++ b/src/nvim/charset.h @@ -7,6 +7,7 @@ #include "nvim/eval/typval.h" #include "nvim/option_defs.h" #include "nvim/pos.h" +#include "nvim/strings.h" #include "nvim/types.h" /// Return the folded-case equivalent of the given character diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c index cf4ff16c4d3b39..99b7f21089dec3 100644 --- a/src/nvim/event/libuv_process.c +++ b/src/nvim/event/libuv_process.c @@ -47,8 +47,7 @@ int libuv_process_spawn(LibuvProcess *uvproc) uvproc->uvstdio[1].flags = UV_IGNORE; uvproc->uvstdio[2].flags = UV_IGNORE; - // TODO: this should just be single flag! - if (TUI_process && !is_remote_client && !stdin_isatty) { + if (ui_client_embed && !stdin_isatty) { uvproc->uvopts.stdio_count = 4; uvproc->uvstdio[3].data.fd = 0; uvproc->uvstdio[3].flags = UV_INHERIT_FD; diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index 52a9394e883777..5587f68432bfaa 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -404,8 +404,7 @@ static void on_process_exit(Process *proc) ILOG("exited: pid=%d status=%d stoptime=%" PRIu64, proc->pid, proc->status, proc->stopped_time); - if (TUI_process && !is_remote_client) { - // Set only in "builtin" TUI + if (ui_client_embed) { server_process_exit_status = proc->status; } // Process has terminated, but there could still be data to be read from the diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 240b99ca290b9a..55ab9fdf0e905f 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -363,7 +363,7 @@ for i = 1, #functions do -- if the function recieves the array args, pass it the second argument output:write('args, ') end - output:write(call_args) + output:write(call_args) else output:write('channel_id') if fn.receives_array_args then diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 68667130605b63..d958633f1bbc0f 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -324,9 +324,21 @@ EXTERN sctx_T current_sctx INIT(= { 0, 0, 0 }); // ID of the current channel making a client API call EXTERN uint64_t current_channel_id INIT(= 0); -// ID of the client channel. Used by ui client +// ID of the ui client channel. Used by ui client EXTERN uint64_t ui_client_channel_id INIT(= 0); +/// whether we are connecting to a remote neovim instance +EXTERN bool ui_client_remote INIT(= false); +/// whether we are embedding a neovim server as child process +EXTERN bool ui_client_embed INIT(= false); + +EXTERN char *termname_local INIT(= "null"); + +/// This process is a TUI process (run the tui rather than executing the main loop) +EXTERN bool tui_process INIT(= false); + +EXTERN long server_process_exit_status INIT(= false); // Used by TUI process + EXTERN bool did_source_packages INIT(= false); // Scope information for the code that indirectly triggered the current @@ -846,14 +858,6 @@ EXTERN linenr_T printer_page_num; EXTERN bool typebuf_was_filled INIT(= false); // received text from client // or from feedkeys() -EXTERN bool is_remote_client INIT(= false); // Initially the TUI is not - // a remote client - -EXTERN bool TUI_process INIT(= false); // This is the TUI process - - -EXTERN long server_process_exit_status INIT(= false); // Used by TUI process - #ifdef BACKSLASH_IN_FILENAME EXTERN char psepc INIT(= '\\'); // normal path separator character EXTERN char psepcN INIT(= '/'); // abnormal path separator character diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 774551242f3ba8..2c55e840f1f545 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -933,22 +933,26 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e CHECK_FLAG(dict, mask, italic, , HL_ITALIC); CHECK_FLAG(dict, mask, reverse, , HL_INVERSE); CHECK_FLAG(dict, mask, strikethrough, , HL_STRIKETHROUGH); + if (use_rgb) { + CHECK_FLAG(dict, mask, fg_indexed, , HL_FG_INDEXED); + CHECK_FLAG(dict, mask, bg_indexed, , HL_BG_INDEXED); + } CHECK_FLAG(dict, mask, nocombine, , HL_NOCOMBINE); CHECK_FLAG(dict, mask, default, _, HL_DEFAULT); if (HAS_KEY(dict->fg)) { - fg = object_to_color(dict->fg, "fg", true, err); + fg = object_to_color(dict->fg, "fg", use_rgb, err); } else if (HAS_KEY(dict->foreground)) { - fg = object_to_color(dict->foreground, "foreground", true, err); + fg = object_to_color(dict->foreground, "foreground", use_rgb, err); } if (ERROR_SET(err)) { return hlattrs; } if (HAS_KEY(dict->bg)) { - bg = object_to_color(dict->bg, "bg", true, err); + bg = object_to_color(dict->bg, "bg", use_rgb, err); } else if (HAS_KEY(dict->background)) { - bg = object_to_color(dict->background, "background", true, err); + bg = object_to_color(dict->background, "background", use_rgb, err); } if (ERROR_SET(err)) { return hlattrs; @@ -1035,11 +1039,11 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e } } - // apply gui mask as default for cterm mask - if (!cterm_mask_provided) { - cterm_mask = mask; - } if (use_rgb) { + // apply gui mask as default for cterm mask + if (!cterm_mask_provided) { + cterm_mask = mask; + } hlattrs.rgb_ae_attr = mask; hlattrs.rgb_bg_color = bg; hlattrs.rgb_fg_color = fg; @@ -1049,9 +1053,9 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e hlattrs.cterm_fg_color = ctermfg == -1 ? 0 : ctermfg + 1; hlattrs.cterm_ae_attr = cterm_mask; } else { - hlattrs.cterm_bg_color = ctermbg == -1 ? 0 : ctermbg + 1; - hlattrs.cterm_fg_color = ctermfg == -1 ? 0 : ctermfg + 1; - hlattrs.cterm_ae_attr = cterm_mask; + hlattrs.cterm_bg_color = bg == -1 ? 0 : bg + 1; + hlattrs.cterm_fg_color = fg == -1 ? 0 : fg + 1; + hlattrs.cterm_ae_attr = mask; } return hlattrs; diff --git a/src/nvim/main.c b/src/nvim/main.c index 9361127c98fd53..32d9dab3cf7893 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -141,7 +141,6 @@ void event_init(void) // early msgpack-rpc initialization msgpack_rpc_helpers_init(); - // Initialize input events input_init(); signal_init(); // finish mspgack-rpc initialization @@ -295,14 +294,30 @@ int main(int argc, char **argv) } bool use_builtin_ui = (!headless_mode && !embedded_mode && !silent_mode); - // bool is_remote_client = false; // TODO: rename to specifically for --remote-ui - // - if (!(is_remote_client || use_builtin_ui)) { + + // don't bind the server yet, if we are using builtin ui. + // This will be done when nvim server has been forked from the ui process + if (!use_builtin_ui) { server_init(params.listen_addr); } if (params.remote) { - remote_request(¶ms, params.remote, params.server_addr, argc, argv); + remote_request(¶ms, params.remote, params.server_addr, argc, argv, + use_builtin_ui); + } + + // TODO(bfredl): not true yet if we implement fork-without-exec + tui_process = ui_client_remote || use_builtin_ui; + bool do_the_read_stdin = (params.edit_type == EDIT_STDIN && !recoverymode); + + if (use_builtin_ui && !ui_client_remote) { + uint64_t rv = ui_client_start_server(params.argc, params.argv, do_the_read_stdin); + if (!rv) { + mch_errmsg("Failed to start Nvim server!\n"); + getout(1); + } + ui_client_channel_id = rv; + ui_client_embed = true; } if (GARGCOUNT > 0) { @@ -361,8 +376,7 @@ int main(int argc, char **argv) // Wait for UIs to set up Nvim or show early messages // and prompts (--cmd, swapfile dialog, …). bool use_remote_ui = (embedded_mode && !headless_mode); - TUI_process = is_remote_client || use_builtin_ui; - if (use_remote_ui || use_builtin_ui) { + if (use_remote_ui || tui_process) { TIME_MSG("waiting for UI"); if (use_remote_ui) { remote_ui_wait_for_attach(); @@ -380,36 +394,11 @@ int main(int argc, char **argv) TIME_MSG("clear screen"); if (ui_client_channel_id) { - ui_client_init(ui_client_channel_id); - ui_client_execute(ui_client_channel_id); + ui_client_init(); + ui_client_execute(); abort(); // unreachable } - // Setting up the remote connection. - // This has to be always after ui_builtin_start or - // after the start of atleast one GUI - // as size of "uis[]" must be greater than 1 - if (TUI_process) { - input_stop(); // Stop reading input, let the UI take over. - uint64_t rv = ui_client_start(params.argc, params.argv, - (params.edit_type == EDIT_STDIN - && !recoverymode)); - if (!rv) { - // cannot continue without a channel - // TODO: use ui_call_stop() ? - tui_exit_safe(ui_get_by_index(1)); - ELOG("RPC: ", NULL, -1, true, - "Could not establish connection with address : %s", params.server_addr); - mch_msg("Could not establish connection with remote server\n"); - getout(1); - } - // TODO: fuuu, deduplicate with ui_client_channel_id block above - ui_client_channel_id = rv; - ui_client_execute(ui_client_channel_id); - abort(); // unreachable - } - - // Default mappings (incl. menus) Error err = ERROR_INIT; Object o = NLUA_EXEC_STATIC("return vim._init_default_mappings()", @@ -659,7 +648,8 @@ void os_exit(int r) free_all_mem(); #endif - if (TUI_process && !is_remote_client) { + if (ui_client_embed) { + // TODO(bfredl): pass in this already as the arg! r = (int)server_process_exit_status; } exit(r); @@ -875,19 +865,28 @@ static uint64_t server_connect(char *server_addr, const char **errmsg) /// Handle remote subcommands static void remote_request(mparm_T *params, int remote_args, char *server_addr, int argc, - char **argv) + char **argv, bool ui_only) { + bool is_ui = strequal(argv[remote_args], "--remote-ui"); + if (ui_only && !is_ui) { + // TODO(bfredl): this implies always starting the TUI. + // if we be smart we could delay this past should_exit + return; + } + const char *connect_error = NULL; uint64_t chan = server_connect(server_addr, &connect_error); Object rvobj = OBJECT_INIT; - if (strequal(argv[remote_args], "--remote-ui-test")) { + if (is_ui) { if (!chan) { - emsg(connect_error); + mch_errmsg(connect_error); + mch_errmsg("\n"); exit(1); } ui_client_channel_id = chan; + ui_client_remote = true; return; } @@ -1440,13 +1439,6 @@ static void command_line_scan(mparm_T *parmp) // Handle "foo | nvim". EDIT_FILE may be overwritten now. #6299 if (edit_stdin(had_stdin_file, parmp)) { parmp->edit_type = EDIT_STDIN; - // TODO: copy - bool use_builtin_ui = (!headless_mode && !embedded_mode && !silent_mode); - if (use_builtin_ui && !is_remote_client) { - // must be set only in builtin TUI - // TODO - //implicit_readstdin = true; - } } TIME_MSG("parsing arguments"); @@ -2162,7 +2154,6 @@ static void usage(void) mch_msg(_(" --embed Use stdin/stdout as a msgpack-rpc channel\n")); mch_msg(_(" --headless Don't start a user interface\n")); mch_msg(_(" --listen
Serve RPC API from this address\n")); - mch_msg(_(" --connect
Specify Nvim server to connect to\n")); mch_msg(_(" --noplugin Don't load plugins\n")); mch_msg(_(" --remote[-subcommand] Execute commands remotely on a server\n")); mch_msg(_(" --server
Specify RPC server to send commands to\n")); diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 516af20fe956c0..ac60544c0a75a8 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -250,8 +250,8 @@ static void parse_msgpack(Channel *channel) ui_client_event_raw_line(p->grid_line_event); } else if (p->ui_handler.fn != NULL && p->result.type == kObjectTypeArray) { p->ui_handler.fn(p->result.data.array); - arena_mem_free(arena_finish(&p->arena)); } + arena_mem_free(arena_finish(&p->arena)); } else if (p->type == kMessageTypeResponse) { ChannelCallFrame *frame = kv_last(channel->rpc.call_stack); if (p->request_id != frame->request_id) { diff --git a/src/nvim/msgpack_rpc/unpacker.c b/src/nvim/msgpack_rpc/unpacker.c index 897ea1f768aa46..44a16beb48512b 100644 --- a/src/nvim/msgpack_rpc/unpacker.c +++ b/src/nvim/msgpack_rpc/unpacker.c @@ -298,7 +298,7 @@ bool unpacker_parse_header(Unpacker *p) // // When method is "grid_line", we furthermore decode a cell at a time like: // -// <0>[2, "redraw", <10>[{11}["grid_line", <13>[g, r, c, [<14>[cell], <14>[cell], ...]], ...], <11>[...], ...]] +// <0>[2, "redraw", <10>[{11}["grid_line", <14>[g, r, c, [<15>[cell], <15>[cell], ...]], ...], <11>[...], ...]] // // where [cell] is [char, repeat, attr], where 'repeat' and 'attr' is optional @@ -318,17 +318,19 @@ bool unpacker_advance(Unpacker *p) } } - if (p->state >= 10 && p->state != 12) { + if (p->state >= 10 && p->state != 13) { if (!unpacker_parse_redraw(p)) { return false; } - if (p->state == 14) { + if (p->state == 15) { // grid_line event already unpacked goto done; } else { + assert(p->state == 12); // unpack other ui events using mpack_parse() p->arena = (Arena)ARENA_EMPTY; + p->state = 13; } } @@ -355,11 +357,11 @@ bool unpacker_advance(Unpacker *p) case 2: p->state = 0; return true; - case 12: - case 14: + case 13: + case 15: p->ncalls--; if (p->ncalls > 0) { - p->state = (p->state == 14) ? 13 : 12; + p->state = (p->state == 15) ? 14 : 12; } else if (p->nevents > 0) { p->state = 11; } else { @@ -428,14 +430,14 @@ bool unpacker_parse_redraw(Unpacker *p) } return true; } else { - p->state = 13; + p->state = 14; p->arena = (Arena)ARENA_EMPTY; p->grid_line_event = arena_alloc(&p->arena, sizeof *p->grid_line_event, true); g = p->grid_line_event; } FALLTHROUGH; - case 13: + case 14: NEXT_TYPE(tok, MPACK_TOKEN_ARRAY); int eventarrsize = (int)tok.length; if (eventarrsize != 4) { @@ -456,10 +458,10 @@ bool unpacker_parse_redraw(Unpacker *p) p->read_ptr = data; p->read_size = size; - p->state = 14; + p->state = 15; FALLTHROUGH; - case 14: + case 15: assert(g->icell < g->ncells); NEXT_TYPE(tok, MPACK_TOKEN_ARRAY); @@ -513,6 +515,9 @@ bool unpacker_parse_redraw(Unpacker *p) } goto redo; + case 12: + return true; + default: abort(); } diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 1cf8ab3253b1ee..3c2fb1797b169b 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2690,7 +2690,7 @@ return { full_name='verbose', abbreviation='vbs', short_desc=N_("give informative messages"), type='number', scope={'global'}, - varname='p_verbose', + varname='p_verbose', redraw={'ui_option'}, defaults={if_true=0} }, { diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index ca1f7c25d7af74..dd6186c3d06183 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -220,17 +220,11 @@ static void tinput_wait_enqueue(void **argv) const size_t len = rbuffer_size(input->key_buffer); String keys = { .data = xmallocz(len), .size = len }; rbuffer_read(input->key_buffer, keys.data, len); - if (ui_client_channel_id) { - Array args = ARRAY_DICT_INIT; - ADD(args, STRING_OBJ(keys)); // 'data' - ADD(args, BOOLEAN_OBJ(true)); // 'crlf' - ADD(args, INTEGER_OBJ(input->paste)); // 'phase' - rpc_send_event(ui_client_channel_id, "nvim_paste", args); - } else { - // TODO - // multiqueue_put(main_loop.events, tinput_paste_event, 3, - // keys.data, keys.size, (intptr_t)input->paste); - } + Array args = ARRAY_DICT_INIT; + ADD(args, STRING_OBJ(keys)); // 'data' + ADD(args, BOOLEAN_OBJ(true)); // 'crlf' + ADD(args, INTEGER_OBJ(input->paste)); // 'phase' + rpc_send_event(ui_client_channel_id, "nvim_paste", args); if (input->paste == 1) { // Paste phase: "continue" input->paste = 2; @@ -239,39 +233,22 @@ static void tinput_wait_enqueue(void **argv) } else { // enqueue input for the main thread or Nvim server RBUFFER_UNTIL_EMPTY(input->key_buffer, buf, len) { const String keys = { .data = buf, .size = len }; - size_t consumed; - if (ui_client_channel_id) { - Array args = ARRAY_DICT_INIT; - Error err = ERROR_INIT; - ADD(args, STRING_OBJ(copy_string(keys, NULL))); - // TODO(bfredl): could be non-blocking now with paste? - ArenaMem res_mem = NULL; - Object result = rpc_send_call(ui_client_channel_id, "nvim_input", args, &res_mem, &err); - consumed = result.type == kObjectTypeInteger ? (size_t)result.data.integer : 0; - arena_mem_free(res_mem); - } else { - // TODO - // consumed = input_enqueue(keys); - abort(); - } - if (consumed) { - rbuffer_consumed(input->key_buffer, consumed); - } + Array args = ARRAY_DICT_INIT; + ADD(args, STRING_OBJ(copy_string(keys, NULL))); + // NOTE: This is non-blocking and won't check partially processed input, + // but should be fine as all big sends are handled with nvim_paste, not nvim_input + rpc_send_event(ui_client_channel_id, "nvim_input", args); + rbuffer_consumed(input->key_buffer, len); rbuffer_reset(input->key_buffer); - if (consumed < len) { - break; - } } } } - static void tinput_flush(TermInput *input, bool wait_until_empty) { size_t drain_boundary = wait_until_empty ? 0 : 0xff; - // TODO: fuuuuuuuuuuuuuuu do { - tinput_wait_enqueue((void**)&input); + tinput_wait_enqueue((void **)&input); } while (rbuffer_size(input->key_buffer) > drain_boundary); } @@ -550,7 +527,8 @@ static bool handle_focus_event(TermInput *input) || !rbuffer_cmp(input->read_stream.buffer, "\x1b[O", 3))) { bool focus_gained = *rbuffer_get(input->read_stream.buffer, 2) == 'I'; // Advance past the sequence - + rbuffer_consumed(input->read_stream.buffer, 3); + Array args = ARRAY_DICT_INIT; ADD(args, BOOLEAN_OBJ(focus_gained)); rpc_send_event(ui_client_channel_id, "nvim_ui_set_focus", args); @@ -605,7 +583,7 @@ static void set_bg(char *bgvalue) Array args = ARRAY_DICT_INIT; ADD(args, STRING_OBJ(cstr_to_string("term_background"))); ADD(args, STRING_OBJ(cstr_as_string(xstrdup(bgvalue)))); - + rpc_send_event(ui_client_channel_id, "nvim_ui_set_option", args); } diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c index 0f6ae03d35b374..0806d4d5b279b6 100644 --- a/src/nvim/tui/terminfo.c +++ b/src/nvim/tui/terminfo.c @@ -7,10 +7,13 @@ #include #include +#include "nvim/api/private/helpers.h" +#include "nvim/charset.h" #include "nvim/globals.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/option.h" +#include "nvim/strings.h" #include "nvim/tui/terminfo.h" #include "nvim/tui/terminfo_defs.h" @@ -147,82 +150,82 @@ unibi_term *terminfo_from_builtin(const char *term, char **termname) /// Serves a similar purpose as Vim `:set termcap` (removed in Nvim). /// /// @note adapted from unibilium unibi-dump.c -void terminfo_info_msg(const unibi_term *const ut) +/// @return allocated string +String terminfo_info_msg(const unibi_term *const ut) { - if (exiting) { - return; - } - msg_puts_title("\n\n--- Terminal info --- {{{\n"); + StringBuilder data = KV_INITIAL_VALUE; char *term; get_tty_option("term", &term); - msg_printf_attr(0, "&term: %s\n", term); - msg_printf_attr(0, "Description: %s\n", unibi_get_name(ut)); + kv_printf(data, "&term: %s\n", term); + + kv_printf(data, "Description: %s\n", unibi_get_name(ut)); const char **a = unibi_get_aliases(ut); if (*a) { - msg_puts("Aliases: "); + kv_printf(data, "Aliases: "); do { - msg_printf_attr(0, "%s%s\n", *a, a[1] ? " | " : ""); + kv_printf(data, "%s%s\n", *a, a[1] ? " | " : ""); a++; } while (*a); } - msg_puts("Boolean capabilities:\n"); + kv_printf(data, "Boolean capabilities:\n"); for (enum unibi_boolean i = unibi_boolean_begin_ + 1; i < unibi_boolean_end_; i++) { - msg_printf_attr(0, " %-25s %-10s = %s\n", unibi_name_bool(i), - unibi_short_name_bool(i), - unibi_get_bool(ut, i) ? "true" : "false"); + kv_printf(data, " %-25s %-10s = %s\n", unibi_name_bool(i), + unibi_short_name_bool(i), + unibi_get_bool(ut, i) ? "true" : "false"); } - msg_puts("Numeric capabilities:\n"); + kv_printf(data, "Numeric capabilities:\n"); for (enum unibi_numeric i = unibi_numeric_begin_ + 1; i < unibi_numeric_end_; i++) { int n = unibi_get_num(ut, i); // -1 means "empty" - msg_printf_attr(0, " %-25s %-10s = %d\n", unibi_name_num(i), - unibi_short_name_num(i), n); + kv_printf(data, " %-25s %-10s = %d\n", unibi_name_num(i), + unibi_short_name_num(i), n); } - msg_puts("String capabilities:\n"); + kv_printf(data, "String capabilities:\n"); for (enum unibi_string i = unibi_string_begin_ + 1; i < unibi_string_end_; i++) { const char *s = unibi_get_str(ut, i); if (s) { - msg_printf_attr(0, " %-25s %-10s = ", unibi_name_str(i), - unibi_short_name_str(i)); + kv_printf(data, " %-25s %-10s = ", unibi_name_str(i), + unibi_short_name_str(i)); // Most of these strings will contain escape sequences. - msg_outtrans_special(s, false, 0); - msg_putchar('\n'); + kv_transstr(&data, s, false); + kv_push(data, '\n'); } } if (unibi_count_ext_bool(ut)) { - msg_puts("Extended boolean capabilities:\n"); + kv_printf(data, "Extended boolean capabilities:\n"); for (size_t i = 0; i < unibi_count_ext_bool(ut); i++) { - msg_printf_attr(0, " %-25s = %s\n", - unibi_get_ext_bool_name(ut, i), - unibi_get_ext_bool(ut, i) ? "true" : "false"); + kv_printf(data, " %-25s = %s\n", + unibi_get_ext_bool_name(ut, i), + unibi_get_ext_bool(ut, i) ? "true" : "false"); } } if (unibi_count_ext_num(ut)) { - msg_puts("Extended numeric capabilities:\n"); + kv_printf(data, "Extended numeric capabilities:\n"); for (size_t i = 0; i < unibi_count_ext_num(ut); i++) { - msg_printf_attr(0, " %-25s = %d\n", - unibi_get_ext_num_name(ut, i), - unibi_get_ext_num(ut, i)); + kv_printf(data, " %-25s = %d\n", + unibi_get_ext_num_name(ut, i), + unibi_get_ext_num(ut, i)); } } if (unibi_count_ext_str(ut)) { - msg_puts("Extended string capabilities:\n"); + kv_printf(data, "Extended string capabilities:\n"); for (size_t i = 0; i < unibi_count_ext_str(ut); i++) { - msg_printf_attr(0, " %-25s = ", unibi_get_ext_str_name(ut, i)); - msg_outtrans_special(unibi_get_ext_str(ut, i), false, 0); - msg_putchar('\n'); + kv_printf(data, " %-25s = ", unibi_get_ext_str_name(ut, i)); + kv_transstr(&data, unibi_get_ext_str(ut, i), false); + kv_push(data, '\n'); } } + kv_push(data, NUL); - msg_puts("}}}\n"); xfree(term); + return cbuf_as_string(data.items, data.size); } diff --git a/src/nvim/tui/terminfo.h b/src/nvim/tui/terminfo.h index 099df8967f2d43..178d3844573aa4 100644 --- a/src/nvim/tui/terminfo.h +++ b/src/nvim/tui/terminfo.h @@ -3,6 +3,8 @@ #include +#include "nvim/api/private/defs.h" + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "tui/terminfo.h.generated.h" #endif diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 3f51ac8791b1bb..2859565636aa21 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -17,6 +17,7 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" +#include "nvim/cursor_shape.h" #include "nvim/event/defs.h" #include "nvim/event/loop.h" #include "nvim/event/multiqueue.h" @@ -26,26 +27,25 @@ #include "nvim/grid_defs.h" #include "nvim/highlight_defs.h" #include "nvim/log.h" +#include "nvim/macros.h" #include "nvim/main.h" #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/msgpack_rpc/channel.h" #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/signal.h" -#include "nvim/ui.h" -#include "nvim/vim.h" #ifdef MSWIN # include "nvim/os/os_win_console.h" #endif -#include "nvim/cursor_shape.h" -#include "nvim/macros.h" #include "nvim/tui/input.h" #include "nvim/tui/terminfo.h" #include "nvim/tui/tui.h" #include "nvim/ugrid.h" -#include "nvim/msgpack_rpc/channel.h" +#include "nvim/ui.h" +#include "nvim/vim.h" // Space reserved in two output buffers to make the cursor normal or invisible // when flushing. No existing terminal will require 32 bytes to do that. @@ -119,6 +119,7 @@ struct TUIData { bool default_attr; bool can_clear_attr; ModeShape showing_mode; + Integer verbose; struct { int enable_mouse, disable_mouse; int enable_mouse_move, disable_mouse_move; @@ -145,7 +146,6 @@ struct TUIData { static int got_winch = 0; static bool cursor_style_enabled = false; -char *termname_local; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "tui/tui.c.generated.h" #endif @@ -180,7 +180,7 @@ UI *tui_start(void) CLEAR_FIELD(ui->ui_ext); ui->ui_ext[kUILinegrid] = true; ui->ui_ext[kUITermColors] = true; - + tui_main(ui); ui_attach_impl(ui, 0); @@ -273,9 +273,7 @@ static void terminfo_start(UI *ui) // Set up unibilium/terminfo. termname_local = NULL; if (term) { - os_env_var_lock(); data->ut = unibi_from_term(term); - os_env_var_unlock(); if (data->ut) { termname_local = xstrdup(term); } @@ -440,7 +438,7 @@ static void tui_terminal_stop(UI *ui) if (uv_is_closing(STRUCT_CAST(uv_handle_t, &data->output_handle))) { // Race between SIGCONT (tui.c) and SIGHUP (os/signal.c)? #8075 ELOG("TUI already stopped (race?)"); - data->stopped = true; + data->stopped = true; return; } tinput_stop(&data->input); @@ -451,9 +449,13 @@ static void tui_terminal_stop(UI *ui) static void tui_stop(UI *ui) { - tui_terminal_stop(ui); TUIData *data = ui->data; + tui_terminal_stop(ui); + tinput_destroy(&data->input); data->stopped = true; + signal_watcher_stop(&data->cont_handle); + signal_watcher_close(&data->cont_handle, NULL); + signal_watcher_close(&data->winch_handle, NULL); } /// Returns true if UI `ui` is stopped. @@ -486,32 +488,12 @@ static void tui_main(UI *ui) data->input.tk_ti_hook_fn = tui_tk_ti_getstr; tinput_init(&data->input, &main_loop); tui_terminal_start(ui); - // TODO: borked! - // loop_schedule(&main_loop, event_create(show_termcap_event, 1, data->ut)); - } -void tui_execute(void) - FUNC_ATTR_NORETURN -{ - UI *ui = ui_get_by_index(1); - LOOP_PROCESS_EVENTS(&main_loop, main_loop.events, -1); - tui_io_driven_loop(ui); - tui_exit_safe(ui); - getout(0); -} - -// Doesn't return until the TUI is closed (by call of tui_stop()) -static void tui_io_driven_loop(UI *ui){ - // "Passive" (I/O-driven) loop: TUI process's "main loop". - while (!tui_is_stopped(ui)) { - loop_poll_events(&main_loop, -1); - } -} - -// TODO: call me when EXITFREE +// TODO(bfredl): call me when EXITFREE #if 0 -static void tui_data_destroy(void **argv) { +static void tui_data_destroy(void **argv) +{ UI *ui = argv[0]; TUIData *data = ui->data; kv_destroy(data->invalid_regions); @@ -522,17 +504,6 @@ static void tui_data_destroy(void **argv) { } #endif -void tui_exit_safe(UI *ui) { - TUIData *data = ui->data; - if (!tui_is_stopped(ui)) { - tui_stop(ui); - } - tinput_destroy(&data->input); - signal_watcher_stop(&data->cont_handle); - signal_watcher_close(&data->cont_handle, NULL); - signal_watcher_close(&data->winch_handle, NULL); -} - #ifdef UNIX static void sigcont_cb(SignalWatcher *watcher, int signum, void *data) { @@ -549,7 +520,6 @@ static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data) } tui_guess_size(ui); - ui_schedule_refresh(); } static bool attrs_differ(UI *ui, int id1, int id2, bool rgb) @@ -1229,6 +1199,11 @@ static void tui_mode_change(UI *ui, String mode, Integer mode_idx) } #endif tui_set_mode(ui, (ModeShape)mode_idx); + if (data->is_starting) { + if (data->verbose >= 3) { + show_verbose_terminfo(data); + } + } data->is_starting = false; // mode entered, no longer starting data->showing_mode = (ModeShape)mode_idx; } @@ -1375,25 +1350,41 @@ static void tui_flush(UI *ui) flush_buf(ui); } -#if 0 /// Dumps termcap info to the messages area, if 'verbose' >= 3. -static void show_termcap_event(void **argv) +static void show_verbose_terminfo(TUIData *data) { - if (p_verbose < 3) { - return; - } - const unibi_term *const ut = argv[0]; + const unibi_term *const ut = data->ut; if (!ut) { abort(); } - verbose_enter(); - // XXX: (future) if unibi_term is modified (e.g. after a terminal - // query-response) this is a race condition. - terminfo_info_msg(ut); - verbose_leave(); - verbose_stop(); // flush now + + Array items = ARRAY_DICT_INIT; + Array title = ARRAY_DICT_INIT; + ADD(title, STRING_OBJ(cstr_to_string("\n\n--- Terminal info --- {{{\n"))); + ADD(title, STRING_OBJ(cstr_to_string("Title"))); + ADD(items, ARRAY_OBJ(title)); + Array info = ARRAY_DICT_INIT; + String str = terminfo_info_msg(ut); + ADD(info, STRING_OBJ(str)); + ADD(items, ARRAY_OBJ(info)); + Array end_fold = ARRAY_DICT_INIT; + ADD(end_fold, STRING_OBJ(cstr_to_string("}}}\n"))); + ADD(end_fold, STRING_OBJ(cstr_to_string("Title"))); + ADD(items, ARRAY_OBJ(end_fold)); + + Array args = ARRAY_DICT_INIT; + ADD(args, ARRAY_OBJ(items)); + ADD(args, BOOLEAN_OBJ(true)); // history + Dictionary opts = ARRAY_DICT_INIT; + PUT(opts, "verbose", BOOLEAN_OBJ(true)); + ADD(args, DICTIONARY_OBJ(opts)); + rpc_send_event(ui_client_channel_id, "nvim_echo", args); + + // Array args = ARRAY_DICT_INIT; + // ADD(args, STRING_OBJ(cstr_to_string("klaaa"))); // history + // ADD(args, ARRAY_OBJ(items)); + // rpc_send_event(ui_client_channel_id, "nvim_set_var", args); } -#endif #ifdef UNIX static void suspend_event(void **argv) @@ -1503,6 +1494,8 @@ static void tui_option_set(UI *ui, String name, Object value) data->input.ttimeout = value.data.boolean; } else if (strequal(name.data, "ttimeoutlen")) { data->input.ttimeoutlen = (long)value.data.integer; + } else if (strequal(name.data, "verbose")) { + data->verbose = value.data.integer; } } @@ -1545,10 +1538,6 @@ static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol, I // printed immediately without an intervening newline. final_column_wrap(ui); } - - // TODO: wat - //xfree((void *) chunk); - //xfree((void *) attrs); } static void invalidate(UI *ui, int top, int bot, int left, int right) @@ -1615,6 +1604,8 @@ static void tui_guess_size(UI *ui) ui->width = width; ui->height = height; + // TODO(bfredl): only if different from last value + ui_schedule_refresh(); } static void unibi_goto(UI *ui, int row, int col) diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 232bfc8b3cce41..166121b97495c8 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -35,7 +35,6 @@ #include "nvim/ui_compositor.h" #include "nvim/vim.h" #include "nvim/window.h" -#include "nvim/msgpack_rpc/channel.h" #ifdef FEAT_TUI # include "nvim/tui/tui.h" #else @@ -151,14 +150,7 @@ void ui_builtin_start(void) #endif } -uint64_t ui_client_start(int argc, char **argv, bool pass_stdin) -{ - ui_comp_detach(uis[1]); // Bypassing compositor in client - uint64_t rv = ui_client_start_server(argc, argv, pass_stdin); - return rv; -} - -UI* ui_get_by_index(int idx) +UI *ui_get_by_index(int idx) { assert(idx < 16); return uis[idx]; @@ -242,7 +234,8 @@ void ui_refresh(void) screen_resize(width, height); p_lz = save_p_lz; } else { - // TODO: not like this + // TODO(bfredl): ui_refresh() should only be used on the server + // we are in the client process. forward the resize Array args = ARRAY_DICT_INIT; ADD(args, INTEGER_OBJ((int)width)); ADD(args, INTEGER_OBJ((int)height)); diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c index a56513f42f3b94..839b32538e8c87 100644 --- a/src/nvim/ui_client.c +++ b/src/nvim/ui_client.c @@ -6,10 +6,10 @@ #include #include "nvim/api/private/helpers.h" +#include "nvim/eval.h" #include "nvim/event/loop.h" #include "nvim/event/multiqueue.h" #include "nvim/globals.h" -#include "nvim/eval.h" #include "nvim/highlight.h" #include "nvim/log.h" #include "nvim/main.h" @@ -27,30 +27,29 @@ uint64_t ui_client_start_server(int argc, char **argv, bool pass_stdin) { - varnumber_T exit_status; - char **args = xmalloc(((size_t)(2 + argc)) * sizeof(char*)); - int args_idx = 0; - args[args_idx++] = xstrdup((const char*)get_vim_var_str(VV_PROGPATH)); - args[args_idx++] = xstrdup("--embed"); - for (int i = 1; i < argc; i++) { - args[args_idx++] = xstrdup(argv[i]); - } - args[args_idx++] = NULL; // last value of argv should be NULL - - Channel *channel = channel_job_start(args, CALLBACK_READER_INIT, - CALLBACK_READER_INIT, CALLBACK_NONE, - false, true, true, false, kChannelStdinPipe, - NULL, 0, 0, NULL, &exit_status); - if (pass_stdin && !stdin_isatty) { - close(0); - dup(2); - } - - ui_client_init(channel->id); - return channel->id;; + varnumber_T exit_status; + char **args = xmalloc(((size_t)(2 + argc)) * sizeof(char *)); + int args_idx = 0; + args[args_idx++] = xstrdup((const char *)get_vim_var_str(VV_PROGPATH)); + args[args_idx++] = xstrdup("--embed"); + for (int i = 1; i < argc; i++) { + args[args_idx++] = xstrdup(argv[i]); + } + args[args_idx++] = NULL; + + Channel *channel = channel_job_start(args, CALLBACK_READER_INIT, + CALLBACK_READER_INIT, CALLBACK_NONE, + false, true, true, false, kChannelStdinPipe, + NULL, 0, 0, NULL, &exit_status); + if (pass_stdin && !stdin_isatty) { + close(0); + dup(2); + } + + return channel->id; } -void ui_client_init(uint64_t chan) +void ui_client_init(void) { Array args = ARRAY_DICT_INIT; int width = Columns; @@ -61,9 +60,11 @@ void ui_client_init(uint64_t chan) PUT(opts, "ext_linegrid", BOOLEAN_OBJ(true)); PUT(opts, "ext_termcolors", BOOLEAN_OBJ(true)); - // TODO: PUT(opts, "term_name", STRING_OBJ(cstr_as_string(termname_local))); + if (termname_local) { + PUT(opts, "term_name", STRING_OBJ(cstr_as_string(termname_local))); + } PUT(opts, "term_colors", INTEGER_OBJ(t_colors)); - if (!is_remote_client) { + if (ui_client_embed) { PUT(opts, "term_ttyin", INTEGER_OBJ(stdin_isatty)); PUT(opts, "term_ttyout", INTEGER_OBJ(stdout_isatty)); } @@ -72,8 +73,7 @@ void ui_client_init(uint64_t chan) ADD(args, INTEGER_OBJ((int)height)); ADD(args, DICTIONARY_OBJ(opts)); - rpc_send_event(chan, "nvim_ui_attach", args); - ui_client_channel_id = chan; + rpc_send_event(ui_client_channel_id, "nvim_ui_attach", args); } UIClientHandler ui_client_get_redraw_handler(const char *name, size_t name_len, Error *error) @@ -97,9 +97,7 @@ Object handle_ui_client_redraw(uint64_t channel_id, Array args, Arena *arena, Er } /// run the main thread in ui client mode -/// -/// This is just a stub. the full version will handle input, resizing, etc -void ui_client_execute(uint64_t chan) +void ui_client_execute(void) FUNC_ATTR_NORETURN { while (true) { @@ -118,7 +116,7 @@ static HlAttrs ui_client_dict2hlattrs(Dictionary d, bool rgb) // TODO(bfredl): log "err" return HLATTRS_INIT; } - return dict2hlattrs(&dict, true, NULL, &err); + return dict2hlattrs(&dict, rgb, NULL, &err); } void ui_client_event_grid_resize(Array args) diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index 41596f5416ffb1..8f6d2dd3404915 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -531,7 +531,7 @@ describe('sysinit', function() nvim_exec() | cmd: aunmenu * | > | - <" -u NONE -i NONE --cmd "set noruler" -D 1,1 All| + <" -u NONE -i NONE --cmd "set noruler" -D 1,0-1 All| | ]]) command([[call chansend(g:id, "cont\n")]]) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index ca59eb31828bdd..a14bedbbbd3181 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -243,7 +243,7 @@ function module.run_session(lsession, request_cb, notification_cb, setup_cb, tim end loop_running = true - session:run(on_request, on_notification, on_setup, timeout) + lsession:run(on_request, on_notification, on_setup, timeout) loop_running = false if last_error then local err = last_error @@ -251,7 +251,7 @@ function module.run_session(lsession, request_cb, notification_cb, setup_cb, tim error(err) end - return session.eof_err + return lsession.eof_err end function module.run(request_cb, notification_cb, setup_cb, timeout) @@ -465,8 +465,14 @@ end -- clear('-e') -- clear{args={'-e'}, args_rm={'-i'}, env={TERM=term}} function module.clear(...) + module.set_session(module.spawn_argv(false, ...)) +end + +-- same params as clear, but does returns the session instead +-- of replacing the default session +function module.spawn_argv(keep, ...) local argv, env, io_extra = module.new_argv(...) - module.set_session(module.spawn(argv, nil, env, nil, io_extra)) + return module.spawn(argv, nil, env, keep, io_extra) end -- Builds an argument list for use in clear(). diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index fd42644ca51be2..6476f641fdd137 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -23,8 +23,11 @@ local funcs = helpers.funcs local meths = helpers.meths local is_ci = helpers.is_ci local is_os = helpers.is_os -local spawn = helpers.spawn +local new_pipename = helpers.new_pipename +local spawn_argv = helpers.spawn_argv local set_session = helpers.set_session +local feed = helpers.feed +local eval = helpers.eval if helpers.skip(helpers.is_os('win')) then return end @@ -35,7 +38,7 @@ describe('TUI', function() before_each(function() clear() - local child_server = helpers.new_pipename() + local child_server = new_pipename() screen = thelpers.screen_setup(0, string.format([=[["%s", "--listen", "%s", "-u", "NONE", "-i", "NONE", "--cmd", "%s laststatus=2 background=dark"]]=], nvim_prog, child_server, nvim_set)) @@ -2030,7 +2033,7 @@ describe("TUI", function() retry(nil, 3000, function() -- Wait for log file to be flushed. local log = read_file('Xtest_tui_verbose_log') or '' - eq('--- Terminal info --- {{{\n', string.match(log, '%-%-%- Terminal.-\n')) + eq('--- Terminal info --- {{{\n', string.match(log, '%-%-%- Terminal.-\n')) -- }}} ok(#log > 50) end) end) @@ -2159,110 +2162,94 @@ end) -- does not initialize the TUI. describe("TUI as a client", function() - it("connects to remote instance (full)", function() - clear() - local server_super = spawn(helpers.nvim_argv) - local client_super = spawn(helpers.nvim_argv) - - set_session(server_super, true) - screen_server = thelpers.screen_setup(0, '["'..nvim_prog - ..'", "-u", "NONE", "-i", "NONE", "--listen", "127.0.0.1:7777"]') - - helpers.feed("iHello, World") + it("connects to remote instance (with its own TUI)", function() + local server_super = spawn_argv(false) -- equivalent to clear() + local client_super = spawn_argv(true) - set_session(client_super, true) - screen = thelpers.screen_setup(0, '["'..nvim_prog - ..'", "-u", "NONE", "-i", "NONE", "--connect", "127.0.0.1:7777"]') + set_session(server_super) + local server_pipe = new_pipename() + local screen_server = thelpers.screen_setup(0, + string.format([=[["%s", "--listen", "%s", "-u", "NONE", "-i", "NONE", "--cmd", "%s laststatus=2 background=dark"]]=], + nvim_prog, server_pipe, nvim_set)) - screen.timeout = 1000 - screen:expect([[ + feed_data("iHello, World") + screen_server:expect{grid=[[ + Hello, World{1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} + feed_data("\027") + screen_server:expect{grid=[[ Hello, Worl{1:d} | {4:~ }| {4:~ }| {4:~ }| - {5:[No Name] [+] 1,12 All}| + {5:[No Name] [+] }| | {3:-- TERMINAL --} | - ]]) + ]]} + + set_session(client_super) + local screen_client = thelpers.screen_setup(0, + string.format([=[["%s", "-u", "NONE", "-i", "NONE", "--server", "%s", "--remote-ui"]]=], + nvim_prog, server_pipe)) + + screen_client:expect{grid=[[ + Hello, Worl{1:d} | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} feed_data(":q!\n") - -- tear down - helpers.feed(":q!") - set_session(server_super, true) - helpers.feed(":q!") server_super:close() client_super:close() end) it("connects to remote instance (--headless)", function() - local server = spawn({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless', '--listen', '127.0.0.1:7777', '-c', ":%! echo 'Hello, World'" }) - -- wait till the server session starts - helpers.sleep(1000) + local server = helpers.spawn_argv(false) -- equivalent to clear() + local client_super = spawn_argv(true) - clear() - screen = thelpers.screen_setup(0, '["'..nvim_prog - ..'", "-u", "NONE", "-i", "NONE", "--connect", "127.0.0.1:7777"]') - - screen.timeout = 1000 - screen:expect([[ - {1:H}ello, World | - {4:~ }| - {4:~ }| - {4:~ }| - {5:[No Name] [+] 1,1 All}| - | - {3:-- TERMINAL --} | - ]]) + set_session(server) + local server_pipe = eval'v:servername' + feed'iHalloj!' - feed_data(":q!\n") - server:close() - end) - - it("connects to remote instance (pipe)", function() - clear() - local server_super = spawn(helpers.nvim_argv) - local client_super = spawn(helpers.nvim_argv) - - set_session(server_super, true) - screen_server = thelpers.screen_setup(0, '["'..nvim_prog - ..'", "-u", "NONE", "-i", "NONE", "--listen", "127.0.0.119"]') - - helpers.feed("iHello, World") - - set_session(client_super, true) - screen = thelpers.screen_setup(0, '["'..nvim_prog - ..'", "-u", "NONE", "-i", "NONE", "--connect", "127.0.0.119"]') + set_session(client_super) + local screen = thelpers.screen_setup(0, + string.format([=[["%s", "-u", "NONE", "-i", "NONE", "--server", "%s", "--remote-ui"]]=], + nvim_prog, server_pipe)) - screen.timeout = 1000 - screen:expect([[ - Hello, Worl{1:d} | + screen:expect{grid=[[ + Halloj{1:!} | + {4:~ }| {4:~ }| {4:~ }| {4:~ }| - {5:[No Name] [+] 1,12 All}| | {3:-- TERMINAL --} | - ]]) - - feed_data(":q!\n") + ]]} - -- tear down - helpers.feed(":q!") - set_session(server_super, true) - helpers.feed(":q!") - server_super:close() client_super:close() + server:close() end) + it("throws error when no server exists", function() clear() - screen = thelpers.screen_setup(0, '["'..nvim_prog - ..'", "-u", "NONE", "-i", "NONE", "--connect", "127.0.0.1:7777"]') + local screen = thelpers.screen_setup(0, + string.format([=[["%s", "-u", "NONE", "-i", "NONE", "--server", "127.0.0.1:2436546", "--remote-ui"]]=], + nvim_prog)) - screen.timeout = 1000 screen:expect([[ - Could not establish connection with remote server | + connection refused | | [Process exited 1]{1: } | | @@ -2273,50 +2260,60 @@ describe("TUI as a client", function() end) it("exits when server quits", function() - clear() - local server_super = spawn(helpers.nvim_argv) - local client_super = spawn(helpers.nvim_argv) - - set_session(server_super, true) - screen_server = thelpers.screen_setup(0, '["'..nvim_prog - ..'", "-u", "NONE", "-i", "NONE", "--listen", "127.0.0.1:7777"]') - - helpers.feed("iHello, World") - - set_session(client_super, true) - screen_client = thelpers.screen_setup(0, '["'..nvim_prog - ..'", "-u", "NONE", "-i", "NONE", "--connect", "127.0.0.1:7777"]') - - -- assert that client has connected to server - screen_client.timeout = 1000 - screen_client:expect([[ + local server_super = spawn_argv(false) -- equivalent to clear() + local client_super = spawn_argv(true) + + set_session(server_super) + local server_pipe = new_pipename() + local screen_server = thelpers.screen_setup(0, + string.format([=[["%s", "--listen", "%s", "-u", "NONE", "-i", "NONE", "--cmd", "%s laststatus=2 background=dark"]]=], + nvim_prog, server_pipe, nvim_set)) + + feed_data("iHello, World") + screen_server:expect{grid=[[ + Hello, World{1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} + feed_data("\027") + screen_server:expect{grid=[[ Hello, Worl{1:d} | {4:~ }| {4:~ }| {4:~ }| - {5:[No Name] [+] 1,12 All}| + {5:[No Name] [+] }| | {3:-- TERMINAL --} | - ]]) + ]]} + + set_session(client_super) + local screen_client = thelpers.screen_setup(0, + string.format([=[["%s", "-u", "NONE", "-i", "NONE", "--server", "%s", "--remote-ui"]]=], + nvim_prog, server_pipe)) + + screen_client:expect{grid=[[ + Hello, Worl{1:d} | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]} -- quitting the server - set_session(server_super, true) + set_session(server_super) feed_data(":q!\n") - screen_server.timeout = 1000 screen_server:expect({any="Process exited 0"}) -- assert that client has exited - set_session(client_super, true) screen_client:expect({any="Process exited 0"}) - -- tear down - helpers.feed(":q!") - set_session(server_super, true) - helpers.feed(":q!") server_super:close() client_super:close() - - -- Restore the original session - set_session(spawn(helpers.nvim_argv), true) end) end) diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua index 6f9cea8f2454ce..9d20229ce1b8e5 100644 --- a/test/functional/ui/options_spec.lua +++ b/test/functional/ui/options_spec.lua @@ -24,6 +24,7 @@ describe('UI receives option updates', function() termguicolors=false, ttimeout=true, ttimeoutlen=50, + verbose=0, ext_cmdline=false, ext_popupmenu=false, ext_tabline=false, diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 79927273a6d6c0..3b9cce0e6f3673 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -1550,7 +1550,8 @@ function Screen:_get_attr_id(attr_state, attrs, hl_id) attr_state.modified = true return id end - return "UNEXPECTED "..self:_pprint_attrs(self._attr_table[hl_id][1]) + local kind = self._options.rgb and 1 or 2 + return "UNEXPECTED "..self:_pprint_attrs(self._attr_table[hl_id][kind]) else if self:_equal_attrs(attrs, {}) then -- ignore this attrs