fix(tui): more work in the TUI

This commit is contained in:
bfredl
2022-05-02 21:10:01 +02:00
parent 2448816956
commit 43e8ec92de
32 changed files with 662 additions and 594 deletions

View File

@ -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.

View File

@ -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

View File

@ -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*

View File

@ -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.

View File

@ -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*

View File

@ -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.

View File

@ -126,6 +126,8 @@ return {
"global_link";
"fallback";
"blend";
"fg_indexed";
"bg_indexed";
};
highlight_cterm = {
"bold";

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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(&params);
open_script_files(&params);
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(&params, params.remote, params.server_addr, argc, argv);
remote_request(&params, 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(&params)) {
params.edit_type = EDIT_STDIN;
}
open_script_files(&params);
// 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 <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"));

View File

@ -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

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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) {

View File

@ -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)

View File

@ -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);
}

View File

@ -6,10 +6,10 @@
#include <stdlib.h>
#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)

View File

@ -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"

View File

@ -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)

View File

@ -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', '-'
}

View File

@ -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")]])

View File

@ -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().

View File

@ -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")

View File

@ -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('<C-h> #10134', function()
local screen = thelpers.screen_setup(0, '["'..nvim_prog
..[[", "-u", "NONE", "-i", "NONE", "--cmd", "set noruler", "--cmd", ':nnoremap <C-h> :echomsg "\<C-h\>"<CR>']]..']')
screen:expect{grid=[[
{1: } |
{4:~ }|
{4:~ }|
{4:~ }|
{5:[No Name] }|
|
{3:-- TERMINAL --} |
]]}
command([[call chansend(b:terminal_job_id, "\<C-h>")]])
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<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([[
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("<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)
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!<esc>'
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<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"]')
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<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([[
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("<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)

View File

@ -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

View File

@ -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])