mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
feat(tui): run TUI as external process
This commit is contained in:
parent
4ace9e7e41
commit
2448816956
@ -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*
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 <address> Serve RPC API from this address\n"));
|
||||
os_msg(_(" --connect <address> 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 <address> Specify RPC server to send commands to\n"));
|
||||
|
@ -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.
|
||||
|
@ -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 <assert.h>
|
||||
#include <signal.h>
|
||||
@ -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)
|
||||
|
@ -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));
|
||||
|
@ -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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#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));
|
||||
}
|
@ -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 <stdbool.h>
|
||||
#include <uv.h>
|
||||
|
||||
#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
|
@ -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));
|
||||
|
@ -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<esc>")
|
||||
|
||||
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("<esc>:q!<CR>")
|
||||
set_session(server_super, true)
|
||||
helpers.feed("<esc>:q!<CR>")
|
||||
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<esc>")
|
||||
|
||||
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("<esc>:q!<CR>")
|
||||
set_session(server_super, true)
|
||||
helpers.feed("<esc>:q!<CR>")
|
||||
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<esc>")
|
||||
|
||||
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("<esc>:q!<CR>")
|
||||
set_session(server_super, true)
|
||||
helpers.feed("<esc>:q!<CR>")
|
||||
server_super:close()
|
||||
client_super:close()
|
||||
|
||||
-- Restore the original session
|
||||
set_session(spawn(helpers.nvim_argv), true)
|
||||
end)
|
||||
end)
|
||||
|
Loading…
Reference in New Issue
Block a user