From 24488169564c39a506c235bf6a33b8e23a8cb528 Mon Sep 17 00:00:00 2001 From: hlpr98 Date: Mon, 27 May 2019 22:04:24 +0530 Subject: [PATCH 1/3] feat(tui): run TUI as external process --- runtime/doc/starting.txt | 4 + runtime/doc/ui.txt | 2 + src/nvim/CMakeLists.txt | 4 - src/nvim/api/ui.c | 35 +++- src/nvim/api/ui_events.in.h | 17 +- src/nvim/event/libuv_process.c | 8 + src/nvim/event/libuv_process.h | 2 +- src/nvim/event/process.c | 4 + src/nvim/generators/gen_api_dispatch.lua | 40 +++- src/nvim/generators/gen_api_ui_events.lua | 63 +----- src/nvim/globals.h | 8 + src/nvim/main.c | 52 ++++- src/nvim/tui/input.c | 55 ++---- src/nvim/tui/tui.c | 135 +++++++------ src/nvim/ui.c | 15 ++ src/nvim/ui_bridge.c | 223 ---------------------- src/nvim/ui_bridge.h | 47 ----- src/nvim/ui_client.c | 33 ++++ test/functional/terminal/tui_spec.lua | 170 ++++++++++++++++- 19 files changed, 468 insertions(+), 449 deletions(-) delete mode 100644 src/nvim/ui_bridge.c delete mode 100644 src/nvim/ui_bridge.h diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 1a7b73601e..24d22c62f8 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -384,6 +384,10 @@ argument. Start |RPC| server on pipe or TCP address {addr}. Sets the primary listen address |v:servername| to {addr}. |serverstart()| +--connect {addr} *--connect* + Connect to the remote nvim server instance which is listening to + {addr}. {addr} can be either a pipe or a TCP address. + ============================================================================== Initialization *initialization* *startup* diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index 1cffe1f902..1b699cf0a9 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -56,6 +56,8 @@ with these (optional) keys: - `stdin_fd` Read buffer from `fd` as if it was a stdin pipe This option can only used by |--embed| ui, see |ui-startup-stdin|. + `term_ttyin` Tells if `stdin` is a `tty` or not. + `term_ttyout` Tells if `stdout` is a `tty` or not. Specifying an unknown option is an error; UIs can check the |api-metadata| `ui_options` key for supported options. diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 5a64128bcb..92f75dbf75 100755 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -39,7 +39,6 @@ set(GENERATED_FUNCS_METADATA ${GENERATED_DIR}/api/private/funcs_metadata.generat set(GENERATED_UI_EVENTS ${GENERATED_DIR}/ui_events.generated.h) set(GENERATED_UI_EVENTS_CALL ${GENERATED_DIR}/ui_events_call.generated.h) set(GENERATED_UI_EVENTS_REMOTE ${GENERATED_DIR}/ui_events_remote.generated.h) -set(GENERATED_UI_EVENTS_BRIDGE ${GENERATED_DIR}/ui_events_bridge.generated.h) set(GENERATED_UI_EVENTS_CLIENT ${GENERATED_DIR}/ui_events_client.generated.h) set(GENERATED_UI_EVENTS_METADATA ${GENERATED_DIR}/api/private/ui_events_metadata.generated.h) set(GENERATED_EX_CMDS_ENUM ${GENERATED_INCLUDES_DIR}/ex_cmds_enum.generated.h) @@ -220,7 +219,6 @@ foreach(sfile ${NVIM_SOURCES} ${GENERATED_API_DISPATCH} "${GENERATED_UI_EVENTS_CALL}" "${GENERATED_UI_EVENTS_REMOTE}" - "${GENERATED_UI_EVENTS_BRIDGE}" "${GENERATED_KEYSETS}" "${GENERATED_UI_EVENTS_CLIENT}" ) @@ -318,7 +316,6 @@ add_custom_command( OUTPUT ${GENERATED_UI_EVENTS} ${GENERATED_UI_EVENTS_CALL} ${GENERATED_UI_EVENTS_REMOTE} - ${GENERATED_UI_EVENTS_BRIDGE} ${GENERATED_UI_EVENTS_METADATA} ${GENERATED_UI_EVENTS_CLIENT} COMMAND ${LUA_PRG} ${API_UI_EVENTS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR} @@ -326,7 +323,6 @@ add_custom_command( ${GENERATED_UI_EVENTS} ${GENERATED_UI_EVENTS_CALL} ${GENERATED_UI_EVENTS_REMOTE} - ${GENERATED_UI_EVENTS_BRIDGE} ${GENERATED_UI_EVENTS_METADATA} ${GENERATED_UI_EVENTS_CLIENT} DEPENDS diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index aeccddb9ea..ef6ced1ee0 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -14,9 +14,6 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/ui.h" #include "nvim/channel.h" -#include "nvim/event/loop.h" -#include "nvim/event/wstream.h" -#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/main.h" @@ -30,6 +27,7 @@ #include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" +#include "nvim/window.h" typedef struct { uint64_t channel_id; @@ -285,6 +283,19 @@ void ui_attach(uint64_t channel_id, Integer width, Integer height, Boolean enabl api_free_dictionary(opts); } +/// Tells the nvim server if focus was gained by the GUI or not +void nvim_ui_set_focus(uint64_t channel_id, Boolean gained, Error *error) + FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY +{ + if (!pmap_has(uint64_t)(&connected_uis, channel_id)) { + api_set_error(error, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); + return; + } + + autocmd_schedule_focusgained((bool)gained); +} + /// Deactivates UI events on the channel. /// /// Removes the client from the list of UIs. |nvim_list_uis()| @@ -404,6 +415,24 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e return; } + if (strequal(name.data, "term_ttyin")) { + if (value.type != kObjectTypeInteger) { + api_set_error(error, kErrorTypeValidation, "term_ttyin must be a Integer"); + return; + } + stdin_isatty = (int)value.data.integer; + return; + } + + if (strequal(name.data, "term_ttyout")) { + if (value.type != kObjectTypeInteger) { + api_set_error(error, kErrorTypeValidation, "term_ttyout must be a Integer"); + return; + } + stdout_isatty = (int)value.data.integer; + return; + } + // LEGACY: Deprecated option, use `ext_cmdline` instead. bool is_popupmenu = strequal(name.data, "popupmenu_external"); diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 21400862b9..d5c79272b7 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -31,7 +31,7 @@ void visual_bell(void) void flush(void) FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL; void suspend(void) - FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(3); void set_title(String title) FUNC_API_SINCE(3); void set_icon(String icon) @@ -39,7 +39,7 @@ void set_icon(String icon) void screenshot(String path) FUNC_API_SINCE(7) FUNC_API_REMOTE_IMPL; void option_set(String name, Object value) - FUNC_API_SINCE(4) FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(4); // Stop event is not exported as such, represented by EOF in the msgpack stream. void stop(void) FUNC_API_NOEXPORT; @@ -73,9 +73,9 @@ void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, Integer Integer cterm_bg) FUNC_API_SINCE(4) FUNC_API_REMOTE_IMPL; void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, Array info) - FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; void hl_group_set(String name, Integer id) - FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(6); void grid_resize(Integer grid, Integer width, Integer height) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL FUNC_API_CLIENT_IMPL; void grid_clear(Integer grid) @@ -112,8 +112,9 @@ void win_hide(Integer grid) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; void win_close(Integer grid) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; + void msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep_char) - FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL; + FUNC_API_SINCE(6) FUNC_API_COMPOSITOR_IMPL; void win_viewport(Integer grid, Window win, Integer topline, Integer botline, Integer curline, Integer curcol, Integer line_count) @@ -149,11 +150,11 @@ void cmdline_block_hide(void) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void wildmenu_show(Array items) - FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL; void wildmenu_select(Integer selected) - FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL; void wildmenu_hide(void) - FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL; void msg_show(String kind, Array content, Boolean replace_last) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c index c5d3b94c95..cf4ff16c4d 100644 --- a/src/nvim/event/libuv_process.c +++ b/src/nvim/event/libuv_process.c @@ -40,11 +40,19 @@ int libuv_process_spawn(LibuvProcess *uvproc) #endif uvproc->uvopts.exit_cb = exit_cb; uvproc->uvopts.cwd = proc->cwd; + uvproc->uvopts.stdio = uvproc->uvstdio; uvproc->uvopts.stdio_count = 3; uvproc->uvstdio[0].flags = UV_IGNORE; 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) { + uvproc->uvopts.stdio_count = 4; + uvproc->uvstdio[3].data.fd = 0; + uvproc->uvstdio[3].flags = UV_INHERIT_FD; + } uvproc->uv.data = proc; if (proc->env) { diff --git a/src/nvim/event/libuv_process.h b/src/nvim/event/libuv_process.h index 8f987847d8..4472839944 100644 --- a/src/nvim/event/libuv_process.h +++ b/src/nvim/event/libuv_process.h @@ -10,7 +10,7 @@ typedef struct libuv_process { Process process; uv_process_t uv; uv_process_options_t uvopts; - uv_stdio_container_t uvstdio[3]; + uv_stdio_container_t uvstdio[4]; } LibuvProcess; static inline LibuvProcess libuv_process_init(Loop *loop, void *data) diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index e74e95669d..52a9394e88 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -404,6 +404,10 @@ 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 + server_process_exit_status = proc->status; + } // Process has terminated, but there could still be data to be read from the // OS. We are still in the libuv loop, so we cannot call code that polls for // more data directly. Instead delay the reading after the libuv loop by diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index d4fe455f82..240b99ca29 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -60,6 +60,12 @@ for i = 6, #arg do if public and not fn.noexport then functions[#functions + 1] = tmp[j] function_names[fn.name] = true + if #fn.parameters >= 2 and fn.parameters[2][1] == 'Array' and fn.parameters[2][2] == 'uidata' then + -- function recieves the "args" as a parameter + fn.receives_array_args = true + -- remove the args parameter + table.remove(fn.parameters, 2) + end if #fn.parameters ~= 0 and fn.parameters[1][2] == 'channel_id' then -- this function should receive the channel id fn.receives_channel_id = true @@ -159,7 +165,7 @@ local exported_attributes = {'name', 'return_type', 'method', 'since', 'deprecated_since'} local exported_functions = {} for _,f in ipairs(functions) do - if not (startswith(f.name, "nvim__") or f.name == "nvim_error_event") then + if not (startswith(f.name, "nvim__") or f.name == "nvim_error_event" or f.name == "redraw") then local f_exported = {} for _,attr in ipairs(exported_attributes) do f_exported[attr] = f[attr] @@ -264,11 +270,13 @@ for i = 1, #functions do output:write('\n '..rt..' '..converted..';') end output:write('\n') - output:write('\n if (args.size != '..#fn.parameters..') {') - output:write('\n api_set_error(error, kErrorTypeException, \ - "Wrong number of arguments: expecting '..#fn.parameters..' but got %zu", args.size);') - output:write('\n goto cleanup;') - output:write('\n }\n') + if not fn.receives_array_args then + output:write('\n if (args.size != '..#fn.parameters..') {') + output:write('\n api_set_error(error, kErrorTypeException, \ + "Wrong number of arguments: expecting '..#fn.parameters..' but got %zu", args.size);') + output:write('\n goto cleanup;') + output:write('\n }\n') + end -- Validation/conversion for each argument for j = 1, #fn.parameters do @@ -350,12 +358,28 @@ for i = 1, #functions do if fn.receives_channel_id then -- if the function receives the channel id, pass it as first argument if #args > 0 or fn.can_fail then - output:write('channel_id, '..call_args) + output:write('channel_id, ') + if fn.receives_array_args then + -- if the function recieves the array args, pass it the second argument + output:write('args, ') + end + output:write(call_args) else output:write('channel_id') + if fn.receives_array_args then + output:write(', args') + end end else - output:write(call_args) + if fn.receives_array_args then + if #args > 0 or fn.call_fail then + output:write('args, '..call_args) + else + output:write('args') + end + else + output:write(call_args) + end end if fn.arena_return then diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua index ea66be7ee8..c9a2e3ff66 100755 --- a/src/nvim/generators/gen_api_ui_events.lua +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -3,14 +3,13 @@ local mpack = require('mpack') local nvimdir = arg[1] package.path = nvimdir .. '/?.lua;' .. package.path -assert(#arg == 8) +assert(#arg == 7) local input = io.open(arg[2], 'rb') local proto_output = io.open(arg[3], 'wb') local call_output = io.open(arg[4], 'wb') local remote_output = io.open(arg[5], 'wb') -local bridge_output = io.open(arg[6], 'wb') -local metadata_output = io.open(arg[7], 'wb') -local client_output = io.open(arg[8], 'wb') +local metadata_output = io.open(arg[6], 'wb') +local client_output = io.open(arg[7], 'wb') local c_grammar = require('generators.c_grammar') local events = c_grammar.grammar:match(input:read('*all')) @@ -119,62 +118,6 @@ for i = 1, #events do remote_output:write(' push_call(ui, "'..ev.name..'", args);\n') remote_output:write('}\n\n') end - - if not ev.bridge_impl and not ev.noexport then - local send, argv, recv, recv_argv, recv_cleanup = '', '', '', '', '' - local argc = 1 - for j = 1, #ev.parameters do - local param = ev.parameters[j] - local copy = 'copy_'..param[2] - if param[1] == 'String' then - send = send..' String copy_'..param[2]..' = copy_string('..param[2]..', NULL);\n' - argv = argv..', '..copy..'.data, INT2PTR('..copy..'.size)' - recv = (recv..' String '..param[2].. - ' = (String){.data = argv['..argc..'],'.. - '.size = (size_t)argv['..(argc+1)..']};\n') - recv_argv = recv_argv..', '..param[2] - recv_cleanup = recv_cleanup..' api_free_string('..param[2]..');\n' - argc = argc+2 - elseif param[1] == 'Array' then - send = send..' Array '..copy..' = copy_array('..param[2]..', NULL);\n' - argv = argv..', '..copy..'.items, INT2PTR('..copy..'.size)' - recv = (recv..' Array '..param[2].. - ' = (Array){.items = argv['..argc..'],'.. - '.size = (size_t)argv['..(argc+1)..']};\n') - recv_argv = recv_argv..', '..param[2] - recv_cleanup = recv_cleanup..' api_free_array('..param[2]..');\n' - argc = argc+2 - elseif param[1] == 'Object' then - send = send..' Object *'..copy..' = xmalloc(sizeof(Object));\n' - send = send..' *'..copy..' = copy_object('..param[2]..', NULL);\n' - argv = argv..', '..copy - recv = recv..' Object '..param[2]..' = *(Object *)argv['..argc..'];\n' - recv_argv = recv_argv..', '..param[2] - recv_cleanup = (recv_cleanup..' api_free_object('..param[2]..');\n'.. - ' xfree(argv['..argc..']);\n') - argc = argc+1 - elseif param[1] == 'Integer' or param[1] == 'Boolean' then - argv = argv..', INT2PTR('..param[2]..')' - recv_argv = recv_argv..', PTR2INT(argv['..argc..'])' - argc = argc+1 - else - assert(false) - end - end - bridge_output:write('static void ui_bridge_'..ev.name.. - '_event(void **argv)\n{\n') - bridge_output:write(' UI *ui = UI(argv[0]);\n') - bridge_output:write(recv) - bridge_output:write(' ui->'..ev.name..'(ui'..recv_argv..');\n') - bridge_output:write(recv_cleanup) - bridge_output:write('}\n\n') - - bridge_output:write('static void ui_bridge_'..ev.name) - write_signature(bridge_output, ev, 'UI *ui') - bridge_output:write('\n{\n') - bridge_output:write(send) - bridge_output:write(' UI_BRIDGE_CALL(ui, '..ev.name..', '..argc..', ui'..argv..');\n}\n\n') - end end if not (ev.remote_only and ev.remote_impl) then diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 52a48a8389..a88360696d 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -850,6 +850,14 @@ 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/main.c b/src/nvim/main.c index 9e14e911ff..422495519a 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -97,6 +97,10 @@ #include "nvim/msgpack_rpc/helpers.h" #include "nvim/msgpack_rpc/server.h" #include "nvim/os/signal.h" +#ifndef MSWIN +# include "nvim/os/pty_process_unix.h" +#endif +#include "nvim/tui/tui.h" // values for "window_layout" enum { @@ -137,6 +141,7 @@ void event_init(void) // early msgpack-rpc initialization msgpack_rpc_helpers_init(); + // Initialize input events input_init(); signal_init(); // finish mspgack-rpc initialization @@ -291,7 +296,13 @@ int main(int argc, char **argv) } } - server_init(params.listen_addr); + 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)) { + server_init(params.listen_addr); + } + if (params.remote) { remote_request(¶ms, params.remote, params.server_addr, argc, argv); } @@ -352,7 +363,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); - bool use_builtin_ui = (!headless_mode && !embedded_mode && !silent_mode); + TUI_process = is_remote_client || use_builtin_ui; if (use_remote_ui || use_builtin_ui) { TIME_MSG("waiting for UI"); if (use_remote_ui) { @@ -376,6 +387,31 @@ int main(int argc, char **argv) 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); + os_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()", @@ -384,6 +420,7 @@ int main(int argc, char **argv) api_clear_error(&err); assert(o.type == kObjectTypeNil); api_free_object(o); + TIME_MSG("init default mappings"); init_default_autocmds(); @@ -624,6 +661,9 @@ void os_exit(int r) free_all_mem(); #endif + if (TUI_process && !is_remote_client) { + r = (int)server_process_exit_status; + } exit(r); } @@ -1376,6 +1416,13 @@ scripterror: // 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"); @@ -2149,6 +2196,7 @@ static void usage(void) os_msg(_(" --embed Use stdin/stdout as a msgpack-rpc channel\n")); os_msg(_(" --headless Don't start a user interface\n")); os_msg(_(" --listen
Serve RPC API from this address\n")); + os_msg(_(" --connect
Specify Nvim server to connect to\n")); os_msg(_(" --noplugin Don't load plugins\n")); os_msg(_(" --remote[-subcommand] Execute commands remotely on a server\n")); os_msg(_(" --server
Specify RPC server to send commands to\n")); diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 9171f79c37..ca1f7c25d7 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -32,6 +32,7 @@ #endif #include "nvim/event/rstream.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/ui.h" #define KEY_BUFFER_SIZE 0xfff @@ -226,8 +227,9 @@ static void tinput_wait_enqueue(void **argv) ADD(args, INTEGER_OBJ(input->paste)); // 'phase' rpc_send_event(ui_client_channel_id, "nvim_paste", args); } else { - multiqueue_put(main_loop.events, tinput_paste_event, 3, - keys.data, keys.size, (intptr_t)input->paste); + // TODO + // multiqueue_put(main_loop.events, tinput_paste_event, 3, + // keys.data, keys.size, (intptr_t)input->paste); } if (input->paste == 1) { // Paste phase: "continue" @@ -248,7 +250,9 @@ static void tinput_wait_enqueue(void **argv) consumed = result.type == kObjectTypeInteger ? (size_t)result.data.integer : 0; arena_mem_free(res_mem); } else { - consumed = input_enqueue(keys); + // TODO + // consumed = input_enqueue(keys); + abort(); } if (consumed) { rbuffer_consumed(input->key_buffer, consumed); @@ -259,38 +263,15 @@ static void tinput_wait_enqueue(void **argv) } } } - uv_mutex_lock(&input->key_buffer_mutex); - input->waiting = false; - uv_cond_signal(&input->key_buffer_cond); - uv_mutex_unlock(&input->key_buffer_mutex); } -static void tinput_paste_event(void **argv) -{ - String keys = { .data = argv[0], .size = (size_t)argv[1] }; - intptr_t phase = (intptr_t)argv[2]; - - Error err = ERROR_INIT; - nvim_paste(keys, true, phase, &err); - if (ERROR_SET(&err)) { - semsg("paste: %s", err.msg); - api_clear_error(&err); - } - - api_free_string(keys); -} static void tinput_flush(TermInput *input, bool wait_until_empty) { size_t drain_boundary = wait_until_empty ? 0 : 0xff; + // TODO: fuuuuuuuuuuuuuuu do { - uv_mutex_lock(&input->key_buffer_mutex); - loop_schedule_fast(&main_loop, event_create(tinput_wait_enqueue, 1, input)); - input->waiting = true; - while (input->waiting) { - uv_cond_wait(&input->key_buffer_cond, &input->key_buffer_mutex); - } - uv_mutex_unlock(&input->key_buffer_mutex); + tinput_wait_enqueue((void**)&input); } while (rbuffer_size(input->key_buffer) > drain_boundary); } @@ -569,8 +550,10 @@ 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); - autocmd_schedule_focusgained(focus_gained); + + Array args = ARRAY_DICT_INIT; + ADD(args, BOOLEAN_OBJ(focus_gained)); + rpc_send_event(ui_client_channel_id, "nvim_ui_set_focus", args); return true; } return false; @@ -617,10 +600,13 @@ static HandleState handle_bracketed_paste(TermInput *input) return kNotApplicable; } -static void set_bg_deferred(void **argv) +static void set_bg(char *bgvalue) { - char *bgvalue = argv[0]; - set_tty_background(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); } // During startup, tui.c requests the background color (see `ext.get_bg`). @@ -704,8 +690,7 @@ static HandleState handle_background_color(TermInput *input) double luminance = (0.299 * r) + (0.587 * g) + (0.114 * b); // CCIR 601 char *bgvalue = luminance < 0.5 ? "dark" : "light"; DLOG("bg response: %s", bgvalue); - loop_schedule_deferred(&main_loop, - event_create(set_bg_deferred, 1, bgvalue)); + set_bg(bgvalue); input->waiting_for_bg_response = 0; } else if (!done && !bad) { // An incomplete sequence was found, waiting for the next input. diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 3010a7b612..4634c77a1f 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1,7 +1,7 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -// Terminal UI functions. Invoked (by ui_bridge.c) on the TUI thread. +// Terminal UI functions. Invoked (by UI_CALL) on the UI process. #include #include @@ -48,7 +48,7 @@ #include "nvim/tui/terminfo.h" #include "nvim/tui/tui.h" #include "nvim/ugrid.h" -#include "nvim/ui_bridge.h" +#include "nvim/msgpack_rpc/channel.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. @@ -91,7 +91,7 @@ typedef struct { } Rect; struct TUIData { - UIBridgeData *bridge; + UI *ui; Loop *loop; unibi_var_t params[9]; char buf[OUTBUF_SIZE]; @@ -159,18 +159,19 @@ struct TUIData { int get_extkeys; } unibi_ext; char *space_buf; + bool stopped; }; 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 UI *tui_start(void) { - UI *ui = xcalloc(1, sizeof(UI)); // Freed by ui_bridge_stop(). + UI *ui = xcalloc(1, sizeof(UI)); // Freed by tui_data_destroy(). ui->stop = tui_stop; ui->grid_resize = tui_grid_resize; ui->grid_clear = tui_grid_clear; @@ -198,15 +199,18 @@ 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); - return ui_bridge_attach(ui, tui_main, tui_scheduler); + return ui; } void tui_enable_extkeys(TUIData *data) { TermInput input = data->input; unibi_term *ut = data->ut; - UI *ui = data->bridge->ui; + UI *ui = data->ui; switch (input.extkeys_type) { case kExtkeysCSIu: @@ -237,13 +241,6 @@ static size_t unibi_pre_fmt_str(TUIData *data, unsigned int unibi_index, char *b return unibi_run(str, data->params, buf, len); } -static void termname_set_event(void **argv) -{ - char *termname = argv[0]; - set_tty_option("term", termname); - // Do not free termname, it is freed by set_tty_option. -} - static void terminfo_start(UI *ui) { TUIData *data = ui->data; @@ -294,22 +291,19 @@ static void terminfo_start(UI *ui) #endif // Set up unibilium/terminfo. - char *termname = NULL; + termname_local = NULL; if (term) { os_env_var_lock(); data->ut = unibi_from_term(term); os_env_var_unlock(); if (data->ut) { - termname = xstrdup(term); + termname_local = xstrdup(term); data->term = xstrdup(term); } } if (!data->ut) { - data->ut = terminfo_from_builtin(term, &termname); + data->ut = terminfo_from_builtin(term, &termname_local); } - // Update 'term' option. - loop_schedule_deferred(&main_loop, - event_create(termname_set_event, 1, termname)); // None of the following work over SSH; see :help TERM . const char *colorterm = os_getenv("COLORTERM"); @@ -467,7 +461,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?)"); - ui->data = NULL; // Flag UI as "stopped". + data->stopped = true; return; } tinput_stop(&data->input); @@ -479,26 +473,27 @@ static void tui_terminal_stop(UI *ui) static void tui_stop(UI *ui) { tui_terminal_stop(ui); - ui->data = NULL; // Flag UI as "stopped". + TUIData *data = ui->data; + data->stopped = true; } /// Returns true if UI `ui` is stopped. static bool tui_is_stopped(UI *ui) { - return ui->data == NULL; + TUIData *data = ui->data; + return data->stopped; } -/// Main function of the TUI thread. -static void tui_main(UIBridgeData *bridge, UI *ui) +// Main function for TUI +static void tui_main(UI *ui) { - Loop tui_loop; - loop_init(&tui_loop, NULL); TUIData *data = xcalloc(1, sizeof(TUIData)); ui->data = data; - data->bridge = bridge; - data->loop = &tui_loop; + data->ui = ui; data->is_starting = true; data->screenshot = NULL; + data->stopped = false; + data->loop = &main_loop; kv_init(data->invalid_regions); signal_watcher_init(data->loop, &data->winch_handle, ui); signal_watcher_init(data->loop, &data->cont_handle, data); @@ -510,43 +505,54 @@ static void tui_main(UIBridgeData *bridge, UI *ui) kv_push(data->attrs, HLATTRS_INIT); data->input.tk_ti_hook_fn = tui_tk_ti_getstr; - tinput_init(&data->input, &tui_loop); + tinput_init(&data->input, &main_loop); tui_terminal_start(ui); + // TODO: borked! + // loop_schedule(&main_loop, event_create(show_termcap_event, 1, data->ut)); - // Allow main thread to continue, we are ready to handle UI callbacks. - CONTINUE(bridge); +} - // "Active" loop: first ~100 ms of startup. - for (size_t ms = 0; ms < 100 && !tui_is_stopped(ui);) { - ms += (loop_poll_events(&tui_loop, 20) ? 20 : 1); - } - if (!tui_is_stopped(ui)) { - tui_terminal_after_startup(ui); - } - // "Passive" (I/O-driven) loop: TUI thread "main loop". +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(&tui_loop, -1); // tui_loop.events is never processed + loop_poll_events(&main_loop, -1); } +} - ui_bridge_stopped(bridge); - tinput_destroy(&data->input); - signal_watcher_stop(&data->cont_handle); - signal_watcher_close(&data->cont_handle, NULL); - signal_watcher_close(&data->winch_handle, NULL); - loop_close(&tui_loop, false); +// TODO: call me when EXITFREE +#if 0 +static void tui_data_destroy(void **argv) { + UI *ui = argv[0]; + TUIData *data = ui->data; kv_destroy(data->invalid_regions); kv_destroy(data->attrs); xfree(data->space_buf); xfree(data->term); xfree(data); + xfree(ui); } +#endif -/// Handoff point between the main (ui_bridge) thread and the TUI thread. -static void tui_scheduler(Event event, void *d) -{ - UI *ui = d; +void tui_exit_safe(UI *ui) { TUIData *data = ui->data; - loop_schedule_fast(data->loop, event); // `tui_loop` local to tui_main(). + 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 @@ -1324,6 +1330,9 @@ static void tui_grid_scroll(UI *ui, Integer g, Integer startrow, // -V751 static void tui_hl_attr_define(UI *ui, Integer id, HlAttrs attrs, HlAttrs cterm_attrs, Array info) { TUIData *data = ui->data; + attrs.cterm_ae_attr = cterm_attrs.cterm_ae_attr; + attrs.cterm_fg_color = cterm_attrs.cterm_fg_color; + attrs.cterm_bg_color = cterm_attrs.cterm_bg_color; kv_a(data->attrs, (size_t)id) = attrs; } @@ -1399,6 +1408,7 @@ static void tui_flush(UI *ui) flush_buf(ui); } +#if 0 /// Dumps termcap info to the messages area, if 'verbose' >= 3. static void show_verbose_terminfo(TUIData *data) { @@ -1448,6 +1458,7 @@ static void verbose_terminfo_event(void **argv) } api_clear_error(&err); } +#endif #ifdef UNIX static void suspend_event(void **argv) @@ -1459,20 +1470,19 @@ static void suspend_event(void **argv) data->cont_received = false; stream_set_blocking(input_global_fd(), true); // normalize stream (#2598) signal_stop(); - kill(0, SIGTSTP); + kill(0, SIGTSTP); // make TUI process run in background signal_start(); while (!data->cont_received) { // poll the event loop until SIGCONT is received loop_poll_events(data->loop, -1); } + tui_terminal_start(ui); tui_terminal_after_startup(ui); if (enable_mouse) { tui_mouse_on(ui); } stream_set_blocking(input_global_fd(), false); // libuv expects this - // resume the main thread - CONTINUE(data->bridge); } #endif @@ -1547,6 +1557,13 @@ static void tui_option_set(UI *ui, String name, Object value) ui->rgb = value.data.boolean; data->print_attr_id = -1; invalidate(ui, 0, data->grid.height, 0, data->grid.width); + + if (ui_client_channel_id) { + Array args = ARRAY_DICT_INIT; + ADD(args, STRING_OBJ(cstr_as_string(xstrdup("rgb")))); + ADD(args, BOOLEAN_OBJ(value.data.boolean)); + rpc_send_event(ui_client_channel_id, "nvim_ui_set_option", args); + } } else if (strequal(name.data, "ttimeout")) { data->input.ttimeout = value.data.boolean; } else if (strequal(name.data, "ttimeoutlen")) { @@ -1595,6 +1612,10 @@ 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) @@ -1659,8 +1680,8 @@ static void tui_guess_size(UI *ui) height = DFLT_ROWS; } - data->bridge->bridge.width = ui->width = width; - data->bridge->bridge.height = ui->height = height; + ui->width = width; + ui->height = height; } static void unibi_goto(UI *ui, int row, int col) diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 7bde8d2f5a..232bfc8b3c 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -35,6 +35,7 @@ #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 @@ -150,6 +151,19 @@ 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) +{ + assert(idx < 16); + return uis[idx]; +} + bool ui_rgb_attached(void) { if (!headless_mode && p_tgc) { @@ -228,6 +242,7 @@ void ui_refresh(void) screen_resize(width, height); p_lz = save_p_lz; } else { + // TODO: not like this Array args = ARRAY_DICT_INIT; ADD(args, INTEGER_OBJ((int)width)); ADD(args, INTEGER_OBJ((int)height)); diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c deleted file mode 100644 index 25c230a941..0000000000 --- a/src/nvim/ui_bridge.c +++ /dev/null @@ -1,223 +0,0 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - -// UI wrapper that sends requests to the UI thread. -// Used by the built-in TUI and libnvim-based UIs. - -#include -#include -#include - -#include "nvim/api/private/defs.h" -#include "nvim/api/private/helpers.h" -#include "nvim/event/loop.h" -#include "nvim/grid_defs.h" -#include "nvim/highlight_defs.h" -#include "nvim/main.h" -#include "nvim/memory.h" -#include "nvim/ui.h" -#include "nvim/ui_bridge.h" - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "ui_bridge.c.generated.h" -#endif - -#define UI(b) (((UIBridgeData *)b)->ui) - -// Schedule a function call on the UI bridge thread. -#define UI_BRIDGE_CALL(ui, name, argc, ...) \ - ((UIBridgeData *)ui)->scheduler(event_create(ui_bridge_##name##_event, argc, __VA_ARGS__), UI(ui)) - -#define INT2PTR(i) ((void *)(intptr_t)i) -#define PTR2INT(p) ((Integer)(intptr_t)p) - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "ui_events_bridge.generated.h" -#endif - -UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) -{ - UIBridgeData *rv = xcalloc(1, sizeof(UIBridgeData)); - rv->ui = ui; - rv->bridge.rgb = ui->rgb; - rv->bridge.width = ui->width; - rv->bridge.height = ui->height; - rv->bridge.stop = ui_bridge_stop; - rv->bridge.grid_resize = ui_bridge_grid_resize; - rv->bridge.grid_clear = ui_bridge_grid_clear; - rv->bridge.grid_cursor_goto = ui_bridge_grid_cursor_goto; - rv->bridge.mode_info_set = ui_bridge_mode_info_set; - rv->bridge.update_menu = ui_bridge_update_menu; - rv->bridge.busy_start = ui_bridge_busy_start; - rv->bridge.busy_stop = ui_bridge_busy_stop; - rv->bridge.mouse_on = ui_bridge_mouse_on; - rv->bridge.mouse_off = ui_bridge_mouse_off; - rv->bridge.mode_change = ui_bridge_mode_change; - rv->bridge.grid_scroll = ui_bridge_grid_scroll; - rv->bridge.hl_attr_define = ui_bridge_hl_attr_define; - rv->bridge.bell = ui_bridge_bell; - rv->bridge.visual_bell = ui_bridge_visual_bell; - rv->bridge.default_colors_set = ui_bridge_default_colors_set; - rv->bridge.flush = ui_bridge_flush; - rv->bridge.suspend = ui_bridge_suspend; - rv->bridge.set_title = ui_bridge_set_title; - rv->bridge.set_icon = ui_bridge_set_icon; - rv->bridge.screenshot = ui_bridge_screenshot; - rv->bridge.option_set = ui_bridge_option_set; - rv->bridge.raw_line = ui_bridge_raw_line; - rv->bridge.inspect = ui_bridge_inspect; - rv->scheduler = scheduler; - - for (UIExtension i = 0; (int)i < kUIExtCount; i++) { - rv->bridge.ui_ext[i] = ui->ui_ext[i]; - } - - rv->ui_main = ui_main; - uv_mutex_init(&rv->mutex); - uv_cond_init(&rv->cond); - uv_mutex_lock(&rv->mutex); - rv->ready = false; - - if (uv_thread_create(&rv->ui_thread, ui_thread_run, rv)) { - abort(); - } - - // Suspend the main thread until CONTINUE is called by the UI thread. - while (!rv->ready) { - uv_cond_wait(&rv->cond, &rv->mutex); - } - uv_mutex_unlock(&rv->mutex); - - ui_attach_impl(&rv->bridge, 0); - - return &rv->bridge; -} - -void ui_bridge_stopped(UIBridgeData *bridge) -{ - uv_mutex_lock(&bridge->mutex); - bridge->stopped = true; - uv_mutex_unlock(&bridge->mutex); -} - -static void ui_thread_run(void *data) -{ - UIBridgeData *bridge = data; - bridge->ui_main(bridge, bridge->ui); -} - -static void ui_bridge_stop(UI *b) -{ - // Detach bridge first, so that "stop" is the last event the TUI loop - // receives from the main thread. #8041 - ui_detach_impl(b, 0); - - UIBridgeData *bridge = (UIBridgeData *)b; - bool stopped = bridge->stopped = false; - UI_BRIDGE_CALL(b, stop, 1, b); - for (;;) { - uv_mutex_lock(&bridge->mutex); - stopped = bridge->stopped; - uv_mutex_unlock(&bridge->mutex); - if (stopped) { // -V547 - break; - } - // TODO(justinmk): Remove this. Use a cond-wait above. #9274 - loop_poll_events(&main_loop, 10); // Process one event. - } - uv_thread_join(&bridge->ui_thread); - uv_mutex_destroy(&bridge->mutex); - uv_cond_destroy(&bridge->cond); - xfree(bridge->ui); // Threads joined, now safe to free UI container. #7922 - xfree(b); -} -static void ui_bridge_stop_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->stop(ui); -} - -static void ui_bridge_hl_attr_define(UI *ui, Integer id, HlAttrs attrs, HlAttrs cterm_attrs, - Array info) -{ - HlAttrs *a = xmalloc(sizeof(HlAttrs)); - *a = attrs; - UI_BRIDGE_CALL(ui, hl_attr_define, 3, ui, INT2PTR(id), a); -} -static void ui_bridge_hl_attr_define_event(void **argv) -{ - UI *ui = UI(argv[0]); - Array info = ARRAY_DICT_INIT; - ui->hl_attr_define(ui, PTR2INT(argv[1]), *((HlAttrs *)argv[2]), - *((HlAttrs *)argv[2]), info); - xfree(argv[2]); -} - -static void ui_bridge_raw_line_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->raw_line(ui, PTR2INT(argv[1]), PTR2INT(argv[2]), PTR2INT(argv[3]), - PTR2INT(argv[4]), PTR2INT(argv[5]), PTR2INT(argv[6]), - (LineFlags)PTR2INT(argv[7]), argv[8], argv[9]); - xfree(argv[8]); - xfree(argv[9]); -} -static void ui_bridge_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol, - Integer clearcol, Integer clearattr, LineFlags flags, - const schar_T *chunk, const sattr_T *attrs) -{ - size_t ncol = (size_t)(endcol - startcol); - schar_T *c = xmemdup(chunk, ncol * sizeof(schar_T)); - sattr_T *hl = xmemdup(attrs, ncol * sizeof(sattr_T)); - UI_BRIDGE_CALL(ui, raw_line, 10, ui, INT2PTR(grid), INT2PTR(row), - INT2PTR(startcol), INT2PTR(endcol), INT2PTR(clearcol), - INT2PTR(clearattr), INT2PTR(flags), c, hl); -} - -static void ui_bridge_suspend(UI *b) -{ - UIBridgeData *data = (UIBridgeData *)b; - uv_mutex_lock(&data->mutex); - UI_BRIDGE_CALL(b, suspend, 1, b); - data->ready = false; - // Suspend the main thread until CONTINUE is called by the UI thread. - while (!data->ready) { - uv_cond_wait(&data->cond, &data->mutex); - } - uv_mutex_unlock(&data->mutex); -} -static void ui_bridge_suspend_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->suspend(ui); -} - -static void ui_bridge_option_set(UI *ui, String name, Object value) -{ - String copy_name = copy_string(name, NULL); - Object *copy_value = xmalloc(sizeof(Object)); - *copy_value = copy_object(value, NULL); - UI_BRIDGE_CALL(ui, option_set, 4, ui, copy_name.data, - INT2PTR(copy_name.size), copy_value); - // TODO(bfredl): when/if TUI/bridge teardown is refactored to use events, the - // commit that introduced this special case can be reverted. - // For now this is needed for nvim_list_uis(). - if (strequal(name.data, "termguicolors")) { - ui->rgb = value.data.boolean; - } -} -static void ui_bridge_option_set_event(void **argv) -{ - UI *ui = UI(argv[0]); - String name = (String){ .data = argv[1], .size = (size_t)argv[2] }; - Object value = *(Object *)argv[3]; - ui->option_set(ui, name, value); - api_free_string(name); - api_free_object(value); - xfree(argv[3]); -} - -static void ui_bridge_inspect(UI *ui, Dictionary *info) -{ - PUT(*info, "chan", INTEGER_OBJ(0)); -} diff --git a/src/nvim/ui_bridge.h b/src/nvim/ui_bridge.h deleted file mode 100644 index 094367126a..0000000000 --- a/src/nvim/ui_bridge.h +++ /dev/null @@ -1,47 +0,0 @@ -// Bridge for communication between a UI thread and nvim core. -// Used by the built-in TUI and libnvim-based UIs. -#ifndef NVIM_UI_BRIDGE_H -#define NVIM_UI_BRIDGE_H - -#include -#include - -#include "nvim/event/defs.h" -#include "nvim/ui.h" - -struct ui_bridge_data; - -typedef struct ui_bridge_data UIBridgeData; -typedef void (*ui_main_fn)(UIBridgeData *bridge, UI *ui); -struct ui_bridge_data { - UI bridge; // actual UI passed to ui_attach - UI *ui; // UI pointer that will have its callback called in - // another thread - event_scheduler scheduler; - uv_thread_t ui_thread; - ui_main_fn ui_main; - uv_mutex_t mutex; - uv_cond_t cond; - // When the UI thread is called, the main thread will suspend until - // the call returns. This flag is used as a condition for the main - // thread to continue. - bool ready; - // When a stop request is sent from the main thread, it must wait until the UI - // thread finishes handling all events. This flag is set by the UI thread as a - // signal that it will no longer send messages to the main thread. - bool stopped; -}; - -#define CONTINUE(b) \ - do { \ - UIBridgeData *d = (UIBridgeData *)b; \ - uv_mutex_lock(&d->mutex); \ - d->ready = true; \ - uv_cond_signal(&d->cond); \ - uv_mutex_unlock(&d->mutex); \ - } while (0) - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "ui_bridge.h.generated.h" -#endif -#endif // NVIM_UI_BRIDGE_H diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c index 27c63433a7..a56513f42f 100644 --- a/src/nvim/ui_client.c +++ b/src/nvim/ui_client.c @@ -9,6 +9,7 @@ #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" @@ -24,6 +25,31 @@ #endif // uncrustify:on +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;; +} + void ui_client_init(uint64_t chan) { Array args = ARRAY_DICT_INIT; @@ -35,6 +61,13 @@ 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))); + PUT(opts, "term_colors", INTEGER_OBJ(t_colors)); + if (!is_remote_client) { + PUT(opts, "term_ttyin", INTEGER_OBJ(stdin_isatty)); + PUT(opts, "term_ttyout", INTEGER_OBJ(stdout_isatty)); + } + ADD(args, INTEGER_OBJ((int)width)); ADD(args, INTEGER_OBJ((int)height)); ADD(args, DICTIONARY_OBJ(opts)); diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 1807001842..0ede6c9978 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -24,6 +24,8 @@ 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 set_session = helpers.set_session if helpers.skip(helpers.is_os('win')) then return end @@ -1365,7 +1367,7 @@ describe('TUI', function() | {4:~ }| {5: }| - [[['chan', 0], ['height', 6], ['override', v:false| + [[['chan', 1], ['height', 6], ['override', v:false| ], ['rgb', v:false], ['width', 50]]] | {10:Press ENTER or type command to continue}{1: } | {3:-- TERMINAL --} | @@ -2164,3 +2166,169 @@ describe('TUI bg color', function() screen:expect{any='new_bg=dark'} end) end) + +-- These tests require `thelpers` because --headless/--embed +-- 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") + + set_session(client_super, true) + screen = thelpers.screen_setup(0, '["'..nvim_prog + ..'", "-u", "NONE", "-i", "NONE", "--connect", "127.0.0.1:7777"]') + + screen.timeout = 1000 + screen:expect([[ + Hello, Worl{1:d} | + {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() + 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) + + 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 --} | + ]]) + + 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"]') + + screen.timeout = 1000 + screen:expect([[ + Hello, Worl{1:d} | + {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() + 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"]') + + screen.timeout = 1000 + screen:expect([[ + Could not establish connection with remote server | + | + [Process exited 1]{1: } | + | + | + | + {3:-- TERMINAL --} | + ]]) + 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([[ + Hello, Worl{1:d} | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] 1,12 All}| + | + {3:-- TERMINAL --} | + ]]) + + -- quitting the server + set_session(server_super, true) + 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) From 43e8ec92de9e0850e7d202cb7ff9051bc408447e Mon Sep 17 00:00:00 2001 From: bfredl Date: Mon, 2 May 2022 21:10:01 +0200 Subject: [PATCH 2/3] fix(tui): more work in the TUI --- CMakeLists.txt | 4 +- runtime/doc/api.txt | 6 + runtime/doc/news.txt | 9 + runtime/doc/remote.txt | 4 + runtime/doc/starting.txt | 4 - runtime/doc/ui.txt | 4 +- src/nvim/api/keysets.lua | 2 + src/nvim/api/ui.c | 22 +- src/nvim/autocmd.c | 17 +- src/nvim/event/libuv_process.c | 4 +- src/nvim/event/process.c | 35 +- src/nvim/ex_docmd.c | 8 +- src/nvim/generators/gen_api_dispatch.lua | 2 +- src/nvim/globals.h | 17 +- src/nvim/highlight.c | 26 +- src/nvim/main.c | 128 ++++---- src/nvim/main.h | 1 + src/nvim/msgpack_rpc/channel.c | 56 ++-- src/nvim/msgpack_rpc/unpacker.c | 25 +- src/nvim/tui/input.c | 66 ++-- src/nvim/tui/tui.c | 198 ++++-------- src/nvim/ui.c | 55 ++-- src/nvim/ui_client.c | 96 +++--- src/nvim/ui_client.h | 20 ++ test/functional/autocmd/focus_spec.lua | 23 +- test/functional/core/main_spec.lua | 2 +- test/functional/core/startup_spec.lua | 2 +- test/functional/helpers.lua | 12 +- test/functional/terminal/api_spec.lua | 10 + test/functional/terminal/tui_spec.lua | 393 ++++++++++++++--------- test/functional/ui/screen.lua | 3 +- test/unit/tui_spec.lua | 2 +- 32 files changed, 662 insertions(+), 594 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a47869a3c6..c3f490fc83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -143,9 +143,9 @@ set(NVIM_VERSION_PATCH 0) set(NVIM_VERSION_PRERELEASE "-dev") # for package maintainers # API level -set(NVIM_API_LEVEL 10) # Bump this after any API change. +set(NVIM_API_LEVEL 11) # Bump this after any API change. set(NVIM_API_LEVEL_COMPAT 0) # Adjust this after a _breaking_ API change. -set(NVIM_API_PRERELEASE false) +set(NVIM_API_PRERELEASE true) set(NVIM_VERSION_BUILD_TYPE "${CMAKE_BUILD_TYPE}") # NVIM_VERSION_CFLAGS set further below. diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 3cd4578750..26679f0330 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -3471,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 or lost by the GUI. + + 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 d6f6464f78..f5ebacdf98 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -101,6 +101,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* @@ -109,6 +114,10 @@ The following changes to existing APIs or features add new behavior. • 'exrc' now supports `.nvim.lua` file. • '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 0c1e3438de..4610088ab0 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 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/runtime/doc/starting.txt b/runtime/doc/starting.txt index 24d22c62f8..1a7b73601e 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -384,10 +384,6 @@ argument. Start |RPC| server on pipe or TCP address {addr}. Sets the primary listen address |v:servername| to {addr}. |serverstart()| ---connect {addr} *--connect* - Connect to the remote nvim server instance which is listening to - {addr}. {addr} can be either a pipe or a TCP address. - ============================================================================== Initialization *initialization* *startup* diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index 1b699cf0a9..a2ae9f22ce 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -56,8 +56,8 @@ with these (optional) keys: - `stdin_fd` Read buffer from `fd` as if it was a stdin pipe This option can only used by |--embed| ui, see |ui-startup-stdin|. - `term_ttyin` Tells if `stdin` is a `tty` or not. - `term_ttyout` Tells if `stdout` is a `tty` or not. + `stdin_tty` Tells if `stdin` is a `tty` or not. + `stdout_tty` Tells if `stdout` is a `tty` or not. Specifying an unknown option is an error; UIs can check the |api-metadata| `ui_options` key for supported options. diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index 8ded9cfa5d..8f909e937f 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"; diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index ef6ced1ee0..e4134133ac 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -283,9 +283,9 @@ void ui_attach(uint64_t channel_id, Integer width, Integer height, Boolean enabl api_free_dictionary(opts); } -/// Tells the nvim server if focus was gained by the GUI or not +/// Tells the nvim server if focus was gained or lost by the GUI void nvim_ui_set_focus(uint64_t channel_id, Boolean gained, Error *error) - FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY + FUNC_API_SINCE(11) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(&connected_uis, channel_id)) { api_set_error(error, kErrorTypeException, @@ -293,7 +293,7 @@ void nvim_ui_set_focus(uint64_t channel_id, Boolean gained, Error *error) return; } - autocmd_schedule_focusgained((bool)gained); + do_autocmd_focusgained((bool)gained); } /// Deactivates UI events on the channel. @@ -415,21 +415,21 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e return; } - if (strequal(name.data, "term_ttyin")) { - if (value.type != kObjectTypeInteger) { - api_set_error(error, kErrorTypeValidation, "term_ttyin must be a Integer"); + if (strequal(name.data, "stdin_tty")) { + if (value.type != kObjectTypeBoolean) { + api_set_error(error, kErrorTypeValidation, "stdin_tty must be a Boolean"); return; } - stdin_isatty = (int)value.data.integer; + stdin_isatty = value.data.boolean; return; } - if (strequal(name.data, "term_ttyout")) { - if (value.type != kObjectTypeInteger) { - api_set_error(error, kErrorTypeValidation, "term_ttyout must be a Integer"); + if (strequal(name.data, "stdout_tty")) { + if (value.type != kObjectTypeBoolean) { + api_set_error(error, kErrorTypeValidation, "stdout_tty must be a Boolean"); return; } - stdout_isatty = (int)value.data.integer; + stdout_isatty = value.data.boolean; return; } diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index ab6c22ff6c..3ed3f7edf2 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -2732,22 +2732,7 @@ void do_autocmd_uienter(uint64_t chanid, bool attached) // FocusGained -static void focusgained_event(void **argv) -{ - bool *gainedp = argv[0]; - do_autocmd_focusgained(*gainedp); - xfree(gainedp); -} - -void autocmd_schedule_focusgained(bool gained) -{ - bool *gainedp = xmalloc(sizeof(*gainedp)); - *gainedp = gained; - loop_schedule_deferred(&main_loop, - event_create(focusgained_event, 1, gainedp)); -} - -static void do_autocmd_focusgained(bool gained) +void do_autocmd_focusgained(bool gained) { static bool recursive = false; static Timestamp last_time = (time_t)0; diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c index cf4ff16c4d..811d96ff93 100644 --- a/src/nvim/event/libuv_process.c +++ b/src/nvim/event/libuv_process.c @@ -47,8 +47,8 @@ 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_forward_stdin) { + assert(UI_CLIENT_STDIN_FD == 3); 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 52a9394e88..2fa789ac9a 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -14,6 +14,7 @@ #include "nvim/globals.h" #include "nvim/log.h" #include "nvim/macros.h" +#include "nvim/main.h" #include "nvim/os/process.h" #include "nvim/os/pty_process.h" #include "nvim/os/shell.h" @@ -35,6 +36,9 @@ void __gcov_flush(void); static bool process_is_tearing_down = false; +// Delay exit until handles are closed, to avoid deadlocks +static int exit_need_delay = 0; + /// @returns zero on success, or negative error code int process_spawn(Process *proc, bool in, bool out, bool err) FUNC_ATTR_NONNULL_ALL @@ -398,16 +402,41 @@ static void process_close_handles(void **argv) exit_need_delay--; } +static void exit_delay_cb(uv_timer_t *handle) +{ + uv_timer_stop(&main_loop.exit_delay_timer); + multiqueue_put(main_loop.fast_events, exit_event, 1, main_loop.exit_delay_timer.data); +} + +static void exit_event(void **argv) +{ + int status = (int)(intptr_t)argv[0]; + if (exit_need_delay) { + main_loop.exit_delay_timer.data = argv[0]; + uv_timer_start(&main_loop.exit_delay_timer, exit_delay_cb, 0, 0); + return; + } + + if (!exiting) { + os_exit(status); + } +} + +void exit_from_channel(int status) +{ + multiqueue_put(main_loop.fast_events, exit_event, 1, status); +} + static void on_process_exit(Process *proc) { Loop *loop = proc->loop; 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 - server_process_exit_status = proc->status; + if (ui_client_channel_id) { + exit_from_channel(proc->status); } + // Process has terminated, but there could still be data to be read from the // OS. We are still in the libuv loop, so we cannot call code that polls for // more data directly. Instead delay the reading after the libuv loop by diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index b99df64a3d..bb03c220b6 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1931,9 +1931,11 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter profile_cmd(&ea, cstack, fgetline, cookie); - // May go to debug mode. If this happens and the ">quit" debug command is - // used, throw an interrupt exception and skip the next command. - dbg_check_breakpoint(&ea); + if (!exiting) { + // May go to debug mode. If this happens and the ">quit" debug command is + // used, throw an interrupt exception and skip the next command. + dbg_check_breakpoint(&ea); + } if (!ea.skip && got_int) { ea.skip = true; (void)do_intthrow(cstack); diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 240b99ca29..55ab9fdf0e 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 a88360696d..cce74df9a8 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -328,9 +328,6 @@ 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 -EXTERN uint64_t ui_client_channel_id INIT(= 0); - EXTERN bool did_source_packages INIT(= false); // Scope information for the code that indirectly triggered the current @@ -494,9 +491,9 @@ EXTERN bool exiting INIT(= false); // internal value of v:dying EXTERN int v_dying INIT(= 0); // is stdin a terminal? -EXTERN int stdin_isatty INIT(= true); +EXTERN bool stdin_isatty INIT(= true); // is stdout a terminal? -EXTERN int stdout_isatty INIT(= true); +EXTERN bool stdout_isatty INIT(= true); /// filedesc set by embedder for reading first buffer like `cmd | nvim -` EXTERN int stdin_fd INIT(= -1); @@ -850,14 +847,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 @@ -1096,8 +1085,6 @@ typedef enum { // Only filled for Win32. EXTERN char windowsVersion[20] INIT(= { 0 }); -EXTERN int exit_need_delay INIT(= 0); - /// While executing a regexp and set to OPTION_MAGIC_ON or OPTION_MAGIC_OFF this /// overrules p_magic. Otherwise set to OPTION_MAGIC_NOT_SET. EXTERN optmagic_T magic_overruled INIT(= OPTION_MAGIC_NOT_SET); diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 774551242f..2c55e840f1 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 422495519a..c20be202e8 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -97,9 +97,6 @@ #include "nvim/msgpack_rpc/helpers.h" #include "nvim/msgpack_rpc/server.h" #include "nvim/os/signal.h" -#ifndef MSWIN -# include "nvim/os/pty_process_unix.h" -#endif #include "nvim/tui/tui.h" // values for "window_layout" @@ -141,7 +138,6 @@ void event_init(void) // early msgpack-rpc initialization msgpack_rpc_helpers_init(); - // Initialize input events input_init(); signal_init(); // finish mspgack-rpc initialization @@ -283,8 +279,6 @@ int main(int argc, char **argv) // argument list "global_alist". command_line_scan(¶ms); - open_script_files(¶ms); - nlua_init(); TIME_MSG("init lua interpreter"); @@ -297,14 +291,28 @@ 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); + } + + bool remote_ui = (ui_client_channel_id != 0); + + if (use_builtin_ui && !remote_ui) { + ui_client_forward_stdin = !params.input_isatty; + uint64_t rv = ui_client_start_server(params.argc, params.argv); + if (!rv) { + os_errmsg("Failed to start Nvim server!\n"); + getout(1); + } + ui_client_channel_id = rv; } if (GARGCOUNT > 0) { @@ -360,17 +368,16 @@ int main(int argc, char **argv) input_start(STDIN_FILENO); } + if (ui_client_channel_id) { + ui_client_run(remote_ui); // NORETURN + } + // 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) { TIME_MSG("waiting for UI"); - if (use_remote_ui) { - remote_ui_wait_for_attach(); - } else { - ui_builtin_start(); - } + remote_ui_wait_for_attach(); TIME_MSG("done waiting for UI"); firstwin->w_prev_height = firstwin->w_height; // may have changed } @@ -381,36 +388,12 @@ int main(int argc, char **argv) win_new_screensize(); TIME_MSG("clear screen"); - if (ui_client_channel_id) { - ui_client_init(ui_client_channel_id); - ui_client_execute(ui_client_channel_id); - 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); - os_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 + // Handle "foo | nvim". EDIT_FILE may be overwritten now. #6299 + if (edit_stdin(¶ms)) { + params.edit_type = EDIT_STDIN; } + open_script_files(¶ms); // Default mappings (incl. menus) Error err = ERROR_INIT; @@ -520,7 +503,7 @@ int main(int argc, char **argv) // writing end of the pipe doesn't like, e.g., in case stdin and stderr // are the same terminal: "cat | vim -". // Using autocommands here may cause trouble... - if ((params.edit_type == EDIT_STDIN || stdin_fd >= 0) && !recoverymode) { + if (params.edit_type == EDIT_STDIN && !recoverymode) { read_stdin(); } @@ -661,9 +644,6 @@ void os_exit(int r) free_all_mem(); #endif - if (TUI_process && !is_remote_client) { - r = (int)server_process_exit_status; - } exit(r); } @@ -877,15 +857,24 @@ 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); + os_errmsg("Remote ui failed to start: "); + os_errmsg(connect_error); + os_errmsg("\n"); exit(1); } @@ -965,14 +954,14 @@ static void remote_request(mparm_T *params, int remote_args, char *server_addr, /// Decides whether text (as opposed to commands) will be read from stdin. /// @see EDIT_STDIN -static bool edit_stdin(bool explicit, mparm_T *parmp) +static bool edit_stdin(mparm_T *parmp) { bool implicit = !headless_mode - && !embedded_mode + && !(embedded_mode && stdin_fd <= 0) && (!exmode_active || parmp->input_neverscript) && !parmp->input_isatty && parmp->scriptin == NULL; // `-s -` was not given. - return explicit || implicit; + return parmp->had_stdin_file || implicit; } /// Scan the command line arguments. @@ -981,7 +970,6 @@ static void command_line_scan(mparm_T *parmp) int argc = parmp->argc; char **argv = parmp->argv; int argv_idx; // index in argv[n][] - bool had_stdin_file = false; // found explicit "-" argument bool had_minmin = false; // found "--" argument int want_argument; // option argument with argument long n; @@ -1018,7 +1006,7 @@ static void command_line_scan(mparm_T *parmp) && parmp->edit_type != EDIT_STDIN) { mainerr(err_too_many_args, argv[0]); } - had_stdin_file = true; + parmp->had_stdin_file = true; parmp->edit_type = EDIT_STDIN; } argv_idx = -1; // skip to next argument @@ -1385,7 +1373,7 @@ scripterror: path_fix_case(p); #endif - int alist_fnum_flag = edit_stdin(had_stdin_file, parmp) + int alist_fnum_flag = edit_stdin(parmp) ? 1 // add buffer nr after exp. : 2; // add buffer number now and use curbuf alist_add(&global_alist, p, alist_fnum_flag); @@ -1413,18 +1401,6 @@ scripterror: xfree(swcmd); } - // 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"); } @@ -1574,11 +1550,16 @@ static void open_script_files(mparm_T *parmp) if (parmp->scriptin) { int error; if (strequal(parmp->scriptin, "-")) { - const int stdin_dup_fd = os_dup(STDIN_FILENO); + int stdin_dup_fd; + if (stdin_fd > 0) { + stdin_dup_fd = stdin_fd; + } else { + stdin_dup_fd = os_dup(STDIN_FILENO); #ifdef MSWIN - // Replace the original stdin with the console input handle. - os_replace_stdin_to_conin(); + // Replace the original stdin with the console input handle. + os_replace_stdin_to_conin(); #endif + } FileDescriptor *const stdin_dup = file_open_fd_new(&error, stdin_dup_fd, kFileReadOnly|kFileNonBlocking); assert(stdin_dup != NULL); @@ -2196,7 +2177,6 @@ static void usage(void) os_msg(_(" --embed Use stdin/stdout as a msgpack-rpc channel\n")); os_msg(_(" --headless Don't start a user interface\n")); os_msg(_(" --listen
Serve RPC API from this address\n")); - os_msg(_(" --connect
Specify Nvim server to connect to\n")); os_msg(_(" --noplugin Don't load plugins\n")); os_msg(_(" --remote[-subcommand] Execute commands remotely on a server\n")); os_msg(_(" --server
Specify RPC server to send commands to\n")); diff --git a/src/nvim/main.h b/src/nvim/main.h index 4cf8dfe026..c39fc1ed4a 100644 --- a/src/nvim/main.h +++ b/src/nvim/main.h @@ -45,6 +45,7 @@ typedef struct { char *scriptin; // -s {filename} char *scriptout; // -w/-W {filename} bool scriptout_append; // append (-w) instead of overwrite (-W) + bool had_stdin_file; // explicit - as a file to edit } mparm_T; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 516af20fe9..4d011dc079 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) { @@ -299,7 +299,7 @@ static void handle_request(Channel *channel, Unpacker *p, Array args) assert(p->type == kMessageTypeRequest || p->type == kMessageTypeNotification); if (!p->handler.fn) { - send_error(channel, p->type, p->request_id, p->unpack_error.msg); + send_error(channel, p->handler, p->type, p->request_id, p->unpack_error.msg); api_clear_error(&p->unpack_error); arena_mem_free(arena_finish(&p->arena)); return; @@ -358,6 +358,7 @@ static void request_event(void **argv) msgpack_packer response; msgpack_packer_init(&response, &out_buffer, msgpack_sbuffer_write); channel_write(channel, serialize_response(channel->id, + e->handler, e->type, e->request_id, &error, @@ -440,11 +441,13 @@ static void internal_read_event(void **argv) wstream_release_wbuffer(buffer); } -static void send_error(Channel *chan, MessageType type, uint32_t id, char *err) +static void send_error(Channel *chan, MsgpackRpcRequestHandler handler, MessageType type, + uint32_t id, char *err) { Error e = ERROR_INIT; api_set_error(&e, kErrorTypeException, "%s", err); channel_write(chan, serialize_response(chan->id, + handler, type, id, &e, @@ -543,26 +546,8 @@ void rpc_close(Channel *channel) channel_decref(channel); if (channel->streamtype == kChannelStreamStdio - || channel->id == ui_client_channel_id) { - multiqueue_put(main_loop.fast_events, exit_event, 0); - } -} - -static void exit_delay_cb(uv_timer_t *handle) -{ - uv_timer_stop(&main_loop.exit_delay_timer); - multiqueue_put(main_loop.fast_events, exit_event, 0); -} - -static void exit_event(void **argv) -{ - if (exit_need_delay) { - uv_timer_start(&main_loop.exit_delay_timer, exit_delay_cb, 0, 0); - return; - } - - if (!exiting) { - os_exit(0); + || (channel->id == ui_client_channel_id && channel->streamtype != kChannelStreamProc)) { + exit_from_channel(0); } } @@ -612,18 +597,27 @@ static WBuffer *serialize_request(uint64_t channel_id, uint32_t request_id, cons return rv; } -static WBuffer *serialize_response(uint64_t channel_id, MessageType type, uint32_t response_id, - Error *err, Object arg, msgpack_sbuffer *sbuffer) +static WBuffer *serialize_response(uint64_t channel_id, MsgpackRpcRequestHandler handler, + MessageType type, uint32_t response_id, Error *err, Object arg, + msgpack_sbuffer *sbuffer) { msgpack_packer pac; msgpack_packer_init(&pac, sbuffer, msgpack_sbuffer_write); if (ERROR_SET(err) && type == kMessageTypeNotification) { - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(err->type)); - ADD(args, STRING_OBJ(cstr_to_string(err->msg))); - msgpack_rpc_serialize_request(0, cstr_as_string("nvim_error_event"), - args, &pac); - api_free_array(args); + if (handler.fn == handle_nvim_paste) { + // TODO(bfredl): this is pretty much ad-hoc. maybe TUI and UI:s should be + // allowed to ask nvim to just scream directly in the users face + // instead of sending nvim_error_event, in general. + semsg("paste: %s", err->msg); + api_clear_error(err); + } else { + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(err->type)); + ADD(args, STRING_OBJ(cstr_to_string(err->msg))); + msgpack_rpc_serialize_request(0, cstr_as_string("nvim_error_event"), + args, &pac); + api_free_array(args); + } } else { msgpack_rpc_serialize_response(response_id, err, arg, &pac); } diff --git a/src/nvim/msgpack_rpc/unpacker.c b/src/nvim/msgpack_rpc/unpacker.c index 897ea1f768..44a16beb48 100644 --- a/src/nvim/msgpack_rpc/unpacker.c +++ b/src/nvim/msgpack_rpc/unpacker.c @@ -298,7 +298,7 @@ error: // // 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 @@ done: 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 @@ redo: } 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 @@ redo: 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 @@ redo: } goto redo; + case 12: + return true; + default: abort(); } diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index ca1f7c25d7..c6066597f0 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); @@ -602,11 +580,13 @@ static HandleState handle_bracketed_paste(TermInput *input) 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); + if (ui_client_attached) { + 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); + } } // During startup, tui.c requests the background color (see `ext.get_bg`). @@ -688,8 +668,10 @@ static HandleState handle_background_color(TermInput *input) double g = (double)rgb[1] / (double)rgb_max[1]; double b = (double)rgb[2] / (double)rgb_max[2]; double luminance = (0.299 * r) + (0.587 * g) + (0.114 * b); // CCIR 601 - char *bgvalue = luminance < 0.5 ? "dark" : "light"; + bool is_dark = luminance < 0.5; + char *bgvalue = is_dark ? "dark" : "light"; DLOG("bg response: %s", bgvalue); + ui_client_bg_respose = is_dark ? kTrue : kFalse; set_bg(bgvalue); input->waiting_for_bg_response = 0; } else if (!done && !bad) { diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 4634c77a1f..89ca77a09a 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -18,6 +18,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/vim.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" @@ -37,18 +38,15 @@ #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. @@ -108,8 +106,8 @@ struct TUIData { uv_pipe_t pipe; } output_handle; bool out_isatty; - SignalWatcher winch_handle, cont_handle; - bool cont_received; + SignalWatcher winch_handle; + uv_timer_t startup_delay_timer; UGrid grid; kvec_t(Rect) invalid_regions; int row, col; @@ -164,7 +162,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 @@ -199,8 +196,30 @@ UI *tui_start(void) CLEAR_FIELD(ui->ui_ext); ui->ui_ext[kUILinegrid] = true; ui->ui_ext[kUITermColors] = true; - - tui_main(ui); + + TUIData *data = xcalloc(1, sizeof(TUIData)); + ui->data = data; + data->ui = ui; + data->is_starting = true; + data->screenshot = NULL; + data->stopped = false; + data->loop = &main_loop; + kv_init(data->invalid_regions); + signal_watcher_init(data->loop, &data->winch_handle, ui); + + // TODO(bfredl): zero hl is empty, send this explicitly? + kv_push(data->attrs, HLATTRS_INIT); + + data->input.tk_ti_hook_fn = tui_tk_ti_getstr; + tinput_init(&data->input, &main_loop); + ugrid_init(&data->grid); + tui_terminal_start(ui); + + uv_timer_init(&data->loop->uv, &data->startup_delay_timer); + data->startup_delay_timer.data = ui; + uv_timer_start(&data->startup_delay_timer, after_startup_cb, + 100, 0); + ui_attach_impl(ui, 0); return ui; @@ -291,18 +310,18 @@ static void terminfo_start(UI *ui) #endif // Set up unibilium/terminfo. - termname_local = NULL; + ui_client_termname = NULL; if (term) { - os_env_var_lock(); data->ut = unibi_from_term(term); - os_env_var_unlock(); if (data->ut) { - termname_local = xstrdup(term); - data->term = xstrdup(term); + ui_client_termname = xstrdup(term); + if (!data->term) { + data->term = xstrdup(term); + } } } if (!data->ut) { - data->ut = terminfo_from_builtin(term, &termname_local); + data->ut = terminfo_from_builtin(term, &ui_client_termname); } // None of the following work over SSH; see :help TERM . @@ -437,13 +456,18 @@ static void tui_terminal_start(UI *ui) { TUIData *data = ui->data; data->print_attr_id = -1; - ugrid_init(&data->grid); terminfo_start(ui); tui_guess_size(ui); signal_watcher_start(&data->winch_handle, sigwinch_cb, SIGWINCH); tinput_start(&data->input); } +static void after_startup_cb(uv_timer_t *handle) +{ + UI *ui = handle->data; + tui_terminal_after_startup(ui); +} + static void tui_terminal_after_startup(UI *ui) FUNC_ATTR_NONNULL_ALL { @@ -455,26 +479,30 @@ static void tui_terminal_after_startup(UI *ui) flush_buf(ui); } +/// stop the terminal but allow it to restart later (like after suspend) static void tui_terminal_stop(UI *ui) { TUIData *data = ui->data; 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); signal_watcher_stop(&data->winch_handle); terminfo_stop(ui); - ugrid_free(&data->grid); } 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_close(&data->winch_handle, NULL); + uv_close((uv_handle_t *)&data->startup_delay_timer, NULL); + ui_detach_impl(ui, 0); } /// Returns true if UI `ui` is stopped. @@ -484,57 +512,11 @@ static bool tui_is_stopped(UI *ui) return data->stopped; } -// Main function for TUI -static void tui_main(UI *ui) +#ifdef EXITFREE +void tui_free_all_mem(UI *ui) { - TUIData *data = xcalloc(1, sizeof(TUIData)); - ui->data = data; - data->ui = ui; - data->is_starting = true; - data->screenshot = NULL; - data->stopped = false; - data->loop = &main_loop; - kv_init(data->invalid_regions); - signal_watcher_init(data->loop, &data->winch_handle, ui); - signal_watcher_init(data->loop, &data->cont_handle, data); -#ifdef UNIX - signal_watcher_start(&data->cont_handle, sigcont_cb, SIGCONT); -#endif - - // TODO(bfredl): zero hl is empty, send this explicitly? - kv_push(data->attrs, HLATTRS_INIT); - - 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 -#if 0 -static void tui_data_destroy(void **argv) { - UI *ui = argv[0]; TUIData *data = ui->data; + ugrid_free(&data->grid); kv_destroy(data->invalid_regions); kv_destroy(data->attrs); xfree(data->space_buf); @@ -544,24 +526,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) -{ - ((TUIData *)data)->cont_received = true; -} -#endif - static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data) { got_winch++; @@ -571,7 +535,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) @@ -1408,7 +1371,6 @@ static void tui_flush(UI *ui) flush_buf(ui); } -#if 0 /// Dumps termcap info to the messages area, if 'verbose' >= 3. static void show_verbose_terminfo(TUIData *data) { @@ -1431,35 +1393,15 @@ static void show_verbose_terminfo(TUIData *data) ADD(end_fold, STRING_OBJ(cstr_to_string("Title"))); ADD(chunks, ARRAY_OBJ(end_fold)); - if (ui_client_channel_id) { - Array args = ARRAY_DICT_INIT; - ADD(args, ARRAY_OBJ(chunks)); - 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); - } else { - loop_schedule_deferred(&main_loop, event_create(verbose_terminfo_event, 2, - chunks.items, chunks.size)); - } + Array args = ARRAY_DICT_INIT; + ADD(args, ARRAY_OBJ(chunks)); + 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); } -static void verbose_terminfo_event(void **argv) -{ - Array chunks = { .items = argv[0], .size = (size_t)argv[1] }; - Dict(echo_opts) opts = { .verbose = BOOLEAN_OBJ(true) }; - Error err = ERROR_INIT; - nvim_echo(chunks, true, &opts, &err); - api_free_array(chunks); - if (ERROR_SET(&err)) { - fprintf(stderr, "TUI bought the farm: %s\n", err.msg); - exit(1); - } - api_clear_error(&err); -} -#endif - #ifdef UNIX static void suspend_event(void **argv) { @@ -1467,15 +1409,9 @@ static void suspend_event(void **argv) TUIData *data = ui->data; bool enable_mouse = data->mouse_enabled; tui_terminal_stop(ui); - data->cont_received = false; stream_set_blocking(input_global_fd(), true); // normalize stream (#2598) - signal_stop(); - kill(0, SIGTSTP); // make TUI process run in background - signal_start(); - while (!data->cont_received) { - // poll the event loop until SIGCONT is received - loop_poll_events(data->loop, -1); - } + + kill(0, SIGTSTP); tui_terminal_start(ui); tui_terminal_after_startup(ui); @@ -1488,16 +1424,13 @@ static void suspend_event(void **argv) static void tui_suspend(UI *ui) { - TUIData *data = ui->data; +// on a non-UNIX system, this is a no-op #ifdef UNIX // kill(0, SIGTSTP) won't stop the UI thread, so we must poll for SIGCONT // before continuing. This is done in another callback to avoid // loop_poll_events recursion - multiqueue_put_event(data->loop->fast_events, + multiqueue_put_event(resize_events, event_create(suspend_event, 1, ui)); -#else - // Resume the main thread as suspending isn't implemented. - CONTINUE(data->bridge); #endif } @@ -1612,10 +1545,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) @@ -1682,6 +1611,9 @@ 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 232bfc8b3c..f40d8b52ca 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -10,6 +10,7 @@ #include "auto/config.h" #include "klib/kvec.h" +#include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" #include "nvim/buffer_defs.h" @@ -31,17 +32,11 @@ #include "nvim/option.h" #include "nvim/os/time.h" #include "nvim/strings.h" +#include "nvim/tui/tui.h" #include "nvim/ui.h" #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 -# include "nvim/msgpack_rpc/server.h" -#endif -#include "nvim/api/private/helpers.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ui.c.generated.h" @@ -127,15 +122,25 @@ void ui_init(void) kv_ensure_space(call_buf, 16); } +static UI *builtin_ui = NULL; + +#ifdef EXITFREE void ui_free_all_mem(void) { kv_destroy(call_buf); +# ifdef FEAT_TUI + if (builtin_ui) { + tui_free_all_mem(builtin_ui); + builtin_ui = NULL; + } +# endif } +#endif void ui_builtin_start(void) { #ifdef FEAT_TUI - tui_start(); + builtin_ui = tui_start(); #else fprintf(stderr, "Nvim headless-mode started.\n"); size_t len; @@ -151,14 +156,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]; @@ -200,7 +198,7 @@ void ui_refresh(void) } if (updating_screen) { - deferred_refresh_event(NULL); + ui_schedule_refresh(); return; } @@ -242,11 +240,20 @@ void ui_refresh(void) screen_resize(width, height); p_lz = save_p_lz; } else { - // TODO: not like this - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ((int)width)); - ADD(args, INTEGER_OBJ((int)height)); - rpc_send_event(ui_client_channel_id, "nvim_ui_try_resize", args); + if (ui_client_attached) { + // 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)); + rpc_send_event(ui_client_channel_id, "nvim_ui_try_resize", args); + } else { + /// TODO(bfredl): Messy! The screen does not yet exist, but we need to + /// communicate its size from the TUI to the client. Clean this up + /// in The UI Devirtualization Project. + Rows = height; + Columns = width; + } } if (ext_widgets[kUIMessages]) { @@ -293,10 +300,6 @@ static void ui_refresh_event(void **argv) } void ui_schedule_refresh(void) -{ - loop_schedule_fast(&main_loop, event_create(deferred_refresh_event, 0)); -} -static void deferred_refresh_event(void **argv) { multiqueue_put(resize_events, ui_refresh_event, 0); } diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c index a56513f42f..6fc3a05e96 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" @@ -25,55 +25,71 @@ #endif // uncrustify:on -uint64_t ui_client_start_server(int argc, char **argv, bool pass_stdin) +uint64_t ui_client_start_server(int argc, char **argv) { - 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 + 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); - } + 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 (ui_client_forward_stdin) { + close(0); + dup(2); + } - ui_client_init(channel->id); - return channel->id;; + return channel->id; } -void ui_client_init(uint64_t chan) +void ui_client_run(bool remote_ui) + FUNC_ATTR_NORETURN { + ui_builtin_start(); + + loop_poll_events(&main_loop, 1); + Array args = ARRAY_DICT_INIT; - int width = Columns; - int height = Rows; Dictionary opts = ARRAY_DICT_INIT; PUT(opts, "rgb", BOOLEAN_OBJ(true)); 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 (ui_client_termname) { + PUT(opts, "term_name", STRING_OBJ(cstr_as_string(ui_client_termname))); + } + if (ui_client_bg_respose != kNone) { + bool is_dark = (ui_client_bg_respose == kTrue); + PUT(opts, "term_background", STRING_OBJ(cstr_as_string(is_dark ? "dark" : "light"))); + } PUT(opts, "term_colors", INTEGER_OBJ(t_colors)); - if (!is_remote_client) { - PUT(opts, "term_ttyin", INTEGER_OBJ(stdin_isatty)); - PUT(opts, "term_ttyout", INTEGER_OBJ(stdout_isatty)); + if (!remote_ui) { + PUT(opts, "stdin_tty", BOOLEAN_OBJ(stdin_isatty)); + PUT(opts, "stdout_tty", BOOLEAN_OBJ(stdout_isatty)); + if (ui_client_forward_stdin) { + PUT(opts, "stdin_fd", INTEGER_OBJ(UI_CLIENT_STDIN_FD)); + } } - ADD(args, INTEGER_OBJ((int)width)); - ADD(args, INTEGER_OBJ((int)height)); + ADD(args, INTEGER_OBJ(Columns)); + ADD(args, INTEGER_OBJ(Rows)); 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); + ui_client_attached = true; + + // os_exit() will be invoked when the client channel detaches + while (true) { + LOOP_PROCESS_EVENTS(&main_loop, resize_events, -1); + } } UIClientHandler ui_client_get_redraw_handler(const char *name, size_t name_len, Error *error) @@ -96,20 +112,6 @@ Object handle_ui_client_redraw(uint64_t channel_id, Array args, Arena *arena, Er return NIL; } -/// 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) - FUNC_ATTR_NORETURN -{ - while (true) { - loop_poll_events(&main_loop, -1); - multiqueue_process_events(resize_events); - } - - getout(0); -} - static HlAttrs ui_client_dict2hlattrs(Dictionary d, bool rgb) { Error err = ERROR_INIT; @@ -118,7 +120,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/src/nvim/ui_client.h b/src/nvim/ui_client.h index bed73d83d3..0b1f1ecb29 100644 --- a/src/nvim/ui_client.h +++ b/src/nvim/ui_client.h @@ -17,6 +17,26 @@ EXTERN size_t grid_line_buf_size INIT(= 0); EXTERN schar_T *grid_line_buf_char INIT(= NULL); EXTERN sattr_T *grid_line_buf_attr INIT(= NULL); +// ID of the ui client channel. If zero, the client is not running. +EXTERN uint64_t ui_client_channel_id INIT(= 0); + +// TODO(bfredl): the current structure for how tui and ui_client.c communicate is a bit awkward. +// This will be restructured as part of The UI Devirtualization Project. + +/// Whether ui client has sent nvim_ui_attach yet +EXTERN bool ui_client_attached INIT(= false); + +/// Whether ui client has gotten a response about the bg color of the terminal, +/// kTrue=dark, kFalse=light, kNone=no response yet +EXTERN TriState ui_client_bg_respose INIT(= kNone); + +/// The ui client should forward its stdin to the nvim process +/// by convention, this uses fd=3 (next free number after stdio) +EXTERN bool ui_client_forward_stdin INIT(= false); + +EXTERN char *ui_client_termname INIT(= "null"); + +#define UI_CLIENT_STDIN_FD 3 #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ui_client.h.generated.h" diff --git a/test/functional/autocmd/focus_spec.lua b/test/functional/autocmd/focus_spec.lua index 024dccbe8e..d7a87e17ed 100644 --- a/test/functional/autocmd/focus_spec.lua +++ b/test/functional/autocmd/focus_spec.lua @@ -33,9 +33,17 @@ describe('autoread TUI FocusGained/FocusLost', function() helpers.write_file(path, '') lfs.touch(path, os.time() - 10) - feed_command('edit '..path) - feed_data('\027[O') + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + | + {3:-- TERMINAL --} | + ]]} + feed_command('edit '..path) screen:expect{grid=[[ {1: } | {4:~ }| @@ -45,6 +53,17 @@ describe('autoread TUI FocusGained/FocusLost', function() :edit xtest-foo | {3:-- TERMINAL --} | ]]} + feed_data('\027[O') + feed_data('\027[O') + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:xtest-foo }| + :edit xtest-foo | + {3:-- TERMINAL --} | + ]], unchanged=true} helpers.write_file(path, expected_addition) diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua index 53461f505a..ab11e14a67 100644 --- a/test/functional/core/main_spec.lua +++ b/test/functional/core/main_spec.lua @@ -56,7 +56,7 @@ describe('Command-line option', function() screen:attach() local args = { nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', - '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', + '--cmd', '"set noswapfile shortmess+=IFW fileformats=unix"', '-s', '-' } diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index 9e22efa545..72d8f3a103 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -544,7 +544,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 ca59eb3182..a14bedbbbd 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/api_spec.lua b/test/functional/terminal/api_spec.lua index 38ef7c2a61..724791343d 100644 --- a/test/functional/terminal/api_spec.lua +++ b/test/functional/terminal/api_spec.lua @@ -19,6 +19,16 @@ describe('api', function() end) it("qa! RPC request during insert-mode", function() + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + | + {3:-- TERMINAL --} | + ]]} + -- Start the socket from the child nvim. child_session.feed_data(":echo serverstart('"..socket_name.."')\n") diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 0ede6c9978..964fc8941d 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -24,8 +24,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 @@ -36,7 +39,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)) @@ -1193,6 +1196,15 @@ describe('TUI', function() it('paste: split "start paste" code', function() feed_data('i') + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} -- Send split "start paste" sequence. feed_data('\027[2') feed_data('00~pasted from terminal\027[201~') @@ -1209,6 +1221,15 @@ describe('TUI', function() it('paste: split "stop paste" code', function() feed_data('i') + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} -- Send split "stop paste" sequence. feed_data('\027[200~pasted from terminal\027[20') feed_data('1~') @@ -1234,6 +1255,15 @@ describe('TUI', function() end)(vim.paste) ]], {}) feed_data('i') + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]} feed_data('\027[200~pasted') -- phase 1 screen:expect([[ pasted{1: } | @@ -1469,6 +1499,15 @@ describe('TUI', function() it(' #10134', function() local screen = thelpers.screen_setup(0, '["'..nvim_prog ..[[", "-u", "NONE", "-i", "NONE", "--cmd", "set noruler", "--cmd", ':nnoremap :echomsg "\"']]..']') + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + | + {3:-- TERMINAL --} | + ]]} command([[call chansend(b:terminal_job_id, "\")]]) screen:expect([[ @@ -1495,6 +1534,15 @@ describe('TUI UIEnter/UILeave', function() ..[[, "--cmd", "autocmd VimEnter * :call add(g:evs, 'VimEnter')"]] ..']' ) + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + | + {3:-- TERMINAL --} | + ]]} feed_data(":echo g:evs\n") screen:expect{grid=[[ {1: } | @@ -1515,61 +1563,88 @@ describe('TUI FocusGained/FocusLost', function() clear() screen = thelpers.screen_setup(0, '["'..nvim_prog ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile noshowcmd noruler"]') + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + | + {3:-- TERMINAL --} | + ]]} feed_data(":autocmd FocusGained * echo 'gained'\n") feed_data(":autocmd FocusLost * echo 'lost'\n") feed_data("\034\016") -- CTRL-\ CTRL-N end) it('in normal-mode', function() + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + :autocmd FocusLost * echo 'lost' | + {3:-- TERMINAL --} | + ]]} retry(2, 3 * screen.timeout, function() - feed_data('\027[I') - screen:expect([[ - {1: } | - {4:~ }| - {4:~ }| - {4:~ }| - {5:[No Name] }| - gained | - {3:-- TERMINAL --} | - ]]) + feed_data('\027[I') + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + gained | + {3:-- TERMINAL --} | + ]]) - feed_data('\027[O') - screen:expect([[ - {1: } | - {4:~ }| - {4:~ }| - {4:~ }| - {5:[No Name] }| - lost | - {3:-- TERMINAL --} | - ]]) + feed_data('\027[O') + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + lost | + {3:-- TERMINAL --} | + ]]) end) end) it('in insert-mode', function() feed_command('set noshowmode') feed_data('i') + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + :set noshowmode | + {3:-- TERMINAL --} | + ]]} retry(2, 3 * screen.timeout, function() - feed_data('\027[I') - screen:expect([[ - {1: } | - {4:~ }| - {4:~ }| - {4:~ }| - {5:[No Name] }| - gained | - {3:-- TERMINAL --} | - ]]) - feed_data('\027[O') - screen:expect([[ - {1: } | - {4:~ }| - {4:~ }| - {4:~ }| - {5:[No Name] }| - lost | - {3:-- TERMINAL --} | - ]]) + feed_data('\027[I') + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + gained | + {3:-- TERMINAL --} | + ]]) + feed_data('\027[O') + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + lost | + {3:-- TERMINAL --} | + ]]) end) end) @@ -1606,6 +1681,15 @@ describe('TUI FocusGained/FocusLost', function() feed_data(":autocmd!\n") feed_data(":autocmd FocusLost * call append(line('$'), 'lost')\n") feed_data(":autocmd FocusGained * call append(line('$'), 'gained')\n") + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] }| + | + {3:-- TERMINAL --} | + ]]} retry(2, 3 * screen.timeout, function() -- Enter cmdline-mode. feed_data(':') @@ -1664,9 +1748,7 @@ describe('TUI FocusGained/FocusLost', function() feed_data(":echom 'msg1'|echom 'msg2'|echom 'msg3'|echom 'msg4'|echom 'msg5'\n") -- Execute :messages to provoke the press-enter prompt. feed_data(":messages\n") - feed_data('\027[I') - feed_data('\027[I') - screen:expect([[ + screen:expect{grid=[[ msg1 | msg2 | msg3 | @@ -1674,7 +1756,18 @@ describe('TUI FocusGained/FocusLost', function() msg5 | {10:Press ENTER or type command to continue}{1: } | {3:-- TERMINAL --} | - ]]) + ]]} + feed_data('\027[I') + feed_data('\027[I') + screen:expect{grid=[[ + msg1 | + msg2 | + msg3 | + msg4 | + msg5 | + {10:Press ENTER or type command to continue}{1: } | + {3:-- TERMINAL --} | + ]], unchanged=true} end) end) @@ -2042,7 +2135,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) @@ -2171,164 +2264,160 @@ 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) + 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(server_super, true) - screen_server = thelpers.screen_setup(0, '["'..nvim_prog - ..'", "-u", "NONE", "-i", "NONE", "--listen", "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)) - helpers.feed("iHello, World") - - set_session(client_super, true) - screen = thelpers.screen_setup(0, '["'..nvim_prog - ..'", "-u", "NONE", "-i", "NONE", "--connect", "127.0.0.1:7777"]') - - 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"]') + set_session(server) + local server_pipe = eval'v:servername' + feed'iHalloj!' - screen.timeout = 1000 - screen:expect([[ - {1:H}ello, World | + 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:expect{grid=[[ + Halloj{1:!} | + {4:~ }| {4:~ }| {4:~ }| {4:~ }| - {5:[No Name] [+] 1,1 All}| | {3:-- TERMINAL --} | - ]]) + ]]} - feed_data(":q!\n") + client_super:close() 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"]') - - screen.timeout = 1000 - screen:expect([[ - Hello, Worl{1:d} | - {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() - 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:try_resize(60, 7) - screen.timeout = 1000 screen:expect([[ - Could not establish connection with remote server | - | - [Process exited 1]{1: } | - | - | - | - {3:-- TERMINAL --} | + Remote ui failed to start: {MATCH:.*}| + | + [Process exited 1]{1: } | + | + | + | + {3:-- TERMINAL --} | ]]) end) it("exits when server quits", function() - clear() - local server_super = spawn(helpers.nvim_argv) - local client_super = spawn(helpers.nvim_argv) + local server_super = spawn_argv(false) -- equivalent to clear() + local client_super = spawn_argv(true) - set_session(server_super, true) - screen_server = thelpers.screen_setup(0, '["'..nvim_prog - ..'", "-u", "NONE", "-i", "NONE", "--listen", "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)) - 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([[ + 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/screen.lua b/test/functional/ui/screen.lua index 79927273a6..3b9cce0e6f 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 diff --git a/test/unit/tui_spec.lua b/test/unit/tui_spec.lua index 36ce4a1493..15b019edd1 100644 --- a/test/unit/tui_spec.lua +++ b/test/unit/tui_spec.lua @@ -33,7 +33,7 @@ itp('handle_background_color', function() term_input.waiting_for_bg_response = 1 eq(kComplete, handle_background_color(term_input)) eq(0, term_input.waiting_for_bg_response) - eq(1, multiqueue.multiqueue_size(events)) + eq(0, multiqueue.multiqueue_size(events)) local event = multiqueue.multiqueue_get(events) local bg_event = ffi.cast("Event*", event.argv[1]) From 9fdcbbb4063daa125e420e0ffe9dae6801c264bc Mon Sep 17 00:00:00 2001 From: bfredl Date: Tue, 27 Dec 2022 14:43:03 +0100 Subject: [PATCH 3/3] feat(tui): graduate the +tui feature This was previously disabled due to build issues on windows. Any reasonable platform can now be expected to have the necessary interfaces to build and run the TUI subsystem. Runtime quality issues of using the TUI (on any new platform) are not relevant here. Just run Nvim in an external UI instead of the TUI as always. --- CMakeLists.txt | 44 ++++++++++++++++++---------------------- cmake.config/config.h.in | 2 -- runtime/doc/news.txt | 4 ++++ src/nvim/CMakeLists.txt | 3 --- src/nvim/ui.c | 16 --------------- src/nvim/version.c | 4 ---- 6 files changed, 24 insertions(+), 49 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c3f490fc83..cb5cb09595 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -498,34 +498,30 @@ if(MSGPACK_HAS_FLOAT32) add_definitions(-DNVIM_MSGPACK_HAS_FLOAT32) endif() -option(FEAT_TUI "Enable the Terminal UI" ON) +find_package(UNIBILIUM 2.0 REQUIRED) +include_directories(SYSTEM ${UNIBILIUM_INCLUDE_DIRS}) -if(FEAT_TUI) - find_package(UNIBILIUM 2.0 REQUIRED) - include_directories(SYSTEM ${UNIBILIUM_INCLUDE_DIRS}) +list(APPEND CMAKE_REQUIRED_INCLUDES "${UNIBILIUM_INCLUDE_DIRS}") +list(APPEND CMAKE_REQUIRED_LIBRARIES "${UNIBILIUM_LIBRARIES}") +check_c_source_compiles(" +#include - list(APPEND CMAKE_REQUIRED_INCLUDES "${UNIBILIUM_INCLUDE_DIRS}") - list(APPEND CMAKE_REQUIRED_LIBRARIES "${UNIBILIUM_LIBRARIES}") - check_c_source_compiles(" - #include - - int - main(void) - { - unibi_str_from_var(unibi_var_from_str(\"\")); - return unibi_num_from_var(unibi_var_from_num(0)); - } - " UNIBI_HAS_VAR_FROM) - list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES "${UNIBILIUM_INCLUDE_DIRS}") - list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES "${UNIBILIUM_LIBRARIES}") - if(UNIBI_HAS_VAR_FROM) - add_definitions(-DNVIM_UNIBI_HAS_VAR_FROM) - endif() - - find_package(LibTermkey 0.22 REQUIRED) - include_directories(SYSTEM ${LIBTERMKEY_INCLUDE_DIRS}) +int +main(void) +{ + unibi_str_from_var(unibi_var_from_str(\"\")); + return unibi_num_from_var(unibi_var_from_num(0)); +} +" UNIBI_HAS_VAR_FROM) +list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES "${UNIBILIUM_INCLUDE_DIRS}") +list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES "${UNIBILIUM_LIBRARIES}") +if(UNIBI_HAS_VAR_FROM) + add_definitions(-DNVIM_UNIBI_HAS_VAR_FROM) endif() +find_package(LibTermkey 0.22 REQUIRED) +include_directories(SYSTEM ${LIBTERMKEY_INCLUDE_DIRS}) + find_package(LIBVTERM 0.3 REQUIRED) include_directories(SYSTEM ${LIBVTERM_INCLUDE_DIRS}) diff --git a/cmake.config/config.h.in b/cmake.config/config.h.in index 59be83fb5e..283a1d0c47 100644 --- a/cmake.config/config.h.in +++ b/cmake.config/config.h.in @@ -56,8 +56,6 @@ #endif #cmakedefine HAVE_FORKPTY -#cmakedefine FEAT_TUI - #ifndef UNIT_TESTING #cmakedefine LOG_LIST_ACTIONS #endif diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index f5ebacdf98..25e753aaed 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -118,6 +118,10 @@ The following changes to existing APIs or features add new behavior. 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. + Previously, the TUI could be disabled as a build time feature (+tui/-tui), + resulting in a nvim binary which only could be run headless or embedded + in an external process. As of this version, TUI is always avalibale. + ============================================================================== REMOVED FEATURES *news-removed* diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 92f75dbf75..6b92d0caa8 100755 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -110,9 +110,6 @@ foreach(subdir viml viml/parser ) - if(${subdir} MATCHES "tui" AND NOT FEAT_TUI) - continue() - endif() file(MAKE_DIRECTORY ${GENERATED_DIR}/${subdir}) file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/${subdir}) diff --git a/src/nvim/ui.c b/src/nvim/ui.c index f40d8b52ca..33c7d0c1fe 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -128,32 +128,16 @@ static UI *builtin_ui = NULL; void ui_free_all_mem(void) { kv_destroy(call_buf); -# ifdef FEAT_TUI if (builtin_ui) { tui_free_all_mem(builtin_ui); builtin_ui = NULL; } -# endif } #endif void ui_builtin_start(void) { -#ifdef FEAT_TUI builtin_ui = tui_start(); -#else - fprintf(stderr, "Nvim headless-mode started.\n"); - size_t len; - char **addrs = server_address_list(&len); - if (addrs != NULL) { - fprintf(stderr, "Listening on:\n"); - for (size_t i = 0; i < len; i++) { - fprintf(stderr, "\t%s\n", addrs[i]); - } - xfree(addrs); - } - fprintf(stderr, "Press CTRL+C to exit.\n"); -#endif } UI *ui_get_by_index(int idx) diff --git a/src/nvim/version.c b/src/nvim/version.c index 98719fce4a..92122b2523 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -69,11 +69,7 @@ static char *features[] = { "-iconv", #endif -#ifdef FEAT_TUI "+tui", -#else - "-tui", -#endif NULL };