mirror of
https://github.com/neovim/neovim.git
synced 2024-09-17 20:58:20 -04:00
Compare commits
4 Commits
b6b9b71c43
...
4f042ad3bc
Author | SHA1 | Date | |
---|---|---|---|
|
4f042ad3bc | ||
|
5799ee2c60 | ||
|
1d9a003616 | ||
|
2258679314 |
@ -359,11 +359,11 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_ks)
|
||||
/// @param keys to be typed
|
||||
/// @return Number of bytes actually written (can be fewer than
|
||||
/// requested if the buffer becomes full).
|
||||
Integer nvim_input(String keys)
|
||||
Integer nvim_input(uint64_t channel_id, String keys)
|
||||
FUNC_API_SINCE(1) FUNC_API_FAST
|
||||
{
|
||||
may_trigger_vim_suspend_resume(false);
|
||||
return (Integer)input_enqueue(keys);
|
||||
return (Integer)input_enqueue(channel_id, keys);
|
||||
}
|
||||
|
||||
/// Send mouse event from GUI.
|
||||
@ -1586,6 +1586,7 @@ Array nvim_get_api_info(uint64_t channel_id, Arena *arena)
|
||||
///
|
||||
/// @param attributes Arbitrary string:string map of informal client properties.
|
||||
/// Suggested keys:
|
||||
/// - "pid": Process id.
|
||||
/// - "website": Client homepage URL (e.g. GitHub repository)
|
||||
/// - "license": License description ("Apache 2", "GPLv3", "MIT", …)
|
||||
/// - "logo": URI or path to image, preferably small logo or icon.
|
||||
@ -1644,9 +1645,8 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dictionary version,
|
||||
/// the key will still be present if a pty is used (e.g. for
|
||||
/// conpty on Windows).
|
||||
/// - "buffer" (optional) Buffer with connected |terminal| instance.
|
||||
/// - "client" (optional) Info about the peer (client on the other end of
|
||||
/// the RPC channel), if provided by it via
|
||||
/// |nvim_set_client_info()|.
|
||||
/// - "client" (optional) Info about the peer (client on the other end of the RPC channel),
|
||||
/// which it provided via |nvim_set_client_info()|.
|
||||
///
|
||||
Dictionary nvim_get_chan_info(uint64_t channel_id, Integer chan, Arena *arena, Error *err)
|
||||
FUNC_API_SINCE(4)
|
||||
|
@ -416,13 +416,15 @@ static void exit_event(void **argv)
|
||||
os_exit(status);
|
||||
} else {
|
||||
assert(status == 0); // Called from rpc_close(), which passes 0 as status.
|
||||
preserve_exit(NULL);
|
||||
// preserve_exit(NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs self-exit when the RPC channel is closed.
|
||||
void exit_from_channel(int status)
|
||||
{
|
||||
DLOG("self-exit triggered by closed RPC channel...");
|
||||
multiqueue_put(main_loop.fast_events, exit_event, (void *)(intptr_t)status);
|
||||
}
|
||||
|
||||
|
@ -94,14 +94,17 @@ void stream_init(Loop *loop, Stream *stream, int fd, uv_stream_t *uvstream)
|
||||
stream->events = NULL;
|
||||
}
|
||||
|
||||
void stream_close(Stream *stream, stream_close_cb on_stream_close, void *data, bool rstream)
|
||||
void stream_may_close(Stream *stream, bool rstream)
|
||||
FUNC_ATTR_NONNULL_ARG(1)
|
||||
{
|
||||
if (stream->closed) {
|
||||
return;
|
||||
}
|
||||
assert(!stream->closed);
|
||||
DLOG("closing Stream: %p", (void *)stream);
|
||||
stream->closed = true;
|
||||
stream->close_cb = on_stream_close;
|
||||
stream->close_cb_data = data;
|
||||
stream->close_cb = NULL;
|
||||
stream->close_cb_data = NULL;
|
||||
|
||||
#ifdef MSWIN
|
||||
if (UV_TTY == uv_guess_handle(stream->fd)) {
|
||||
@ -115,13 +118,6 @@ void stream_close(Stream *stream, stream_close_cb on_stream_close, void *data, b
|
||||
}
|
||||
}
|
||||
|
||||
void stream_may_close(Stream *stream, bool rstream)
|
||||
{
|
||||
if (!stream->closed) {
|
||||
stream_close(stream, NULL, NULL, rstream);
|
||||
}
|
||||
}
|
||||
|
||||
void stream_close_handle(Stream *stream, bool rstream)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
|
@ -2207,7 +2207,7 @@ static void usage(void)
|
||||
printf(_(" --headless Don't start a user interface\n"));
|
||||
printf(_(" --listen <address> Serve RPC API from this address\n"));
|
||||
printf(_(" --remote[-subcommand] Execute commands remotely on a server\n"));
|
||||
printf(_(" --server <address> Specify RPC server to send commands to\n"));
|
||||
printf(_(" --server <address> Connect to this Nvim server\n"));
|
||||
printf(_(" --startuptime <file> Write startup timing messages to <file>\n"));
|
||||
printf(_("\nSee \":help startup-options\" for all options.\n"));
|
||||
}
|
||||
|
@ -220,8 +220,7 @@ static size_t receive_msgpack(RStream *stream, const char *rbuf, size_t c, void
|
||||
if (eof) {
|
||||
channel_close(channel->id, kChannelPartRpc, NULL);
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf), "ch %" PRIu64 " was closed by the client",
|
||||
channel->id);
|
||||
snprintf(buf, sizeof(buf), "ch %" PRIu64 " was closed by the peer", channel->id);
|
||||
chan_close_with_error(channel, buf, LOGLVL_INF);
|
||||
}
|
||||
|
||||
|
@ -14,13 +14,16 @@
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
#include "nvim/api/ui.h"
|
||||
#include "nvim/ascii_defs.h"
|
||||
#include "nvim/autocmd.h"
|
||||
#include "nvim/autocmd_defs.h"
|
||||
#include "nvim/buffer.h"
|
||||
#include "nvim/buffer_defs.h"
|
||||
#include "nvim/change.h"
|
||||
#include "nvim/channel.h"
|
||||
#include "nvim/charset.h"
|
||||
#include "nvim/cmdhist.h"
|
||||
#include "nvim/cursor.h"
|
||||
@ -58,11 +61,13 @@
|
||||
#include "nvim/message.h"
|
||||
#include "nvim/mouse.h"
|
||||
#include "nvim/move.h"
|
||||
#include "nvim/msgpack_rpc/channel.h"
|
||||
#include "nvim/normal.h"
|
||||
#include "nvim/ops.h"
|
||||
#include "nvim/option.h"
|
||||
#include "nvim/option_vars.h"
|
||||
#include "nvim/os/input.h"
|
||||
#include "nvim/os/os.h"
|
||||
#include "nvim/os/time.h"
|
||||
#include "nvim/plines.h"
|
||||
#include "nvim/profile.h"
|
||||
@ -5121,14 +5126,55 @@ static void nv_window(cmdarg_T *cap)
|
||||
}
|
||||
}
|
||||
|
||||
/// CTRL-Z: Suspend
|
||||
/// CTRL-Z: Suspend, or Detach UI
|
||||
static void nv_suspend(cmdarg_T *cap)
|
||||
{
|
||||
clearop(cap->oap);
|
||||
if (VIsual_active) {
|
||||
end_visual_mode(); // stop Visual mode
|
||||
}
|
||||
do_cmdline_cmd("st");
|
||||
|
||||
if (input_chan_id == 0 || /*input_chan_id == 1 ||*/ is_internal_call(input_chan_id)) {
|
||||
if (input_chan_id == 1) {
|
||||
ILOG("xxx ui_channel=%" PRId64, input_chan_id);
|
||||
}
|
||||
// TODO(justinmk): show a confirm dialog if multiple UIs are attached?
|
||||
if (ui_active() > 1) {
|
||||
emsg("UIs are connected, no sussy baka");
|
||||
} else {
|
||||
do_cmdline_cmd("suspend");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// come on pooky let's burn this burn this mf down
|
||||
ILOG("xxx DETACHED ui_channel=%" PRId64, input_chan_id);
|
||||
|
||||
// MAXSIZE_TEMP_ARRAY(args2, 1);
|
||||
// ADD_C(args2, INTEGER_OBJ((Integer)ui_client_channel_id));
|
||||
// MAXSIZE_TEMP_ARRAY(args, 2);
|
||||
// ADD_C(args, CSTR_TO_OBJ("chanclose")); // CSTR_AS_OBJ
|
||||
// ADD_C(args, ARRAY_OBJ(args2));
|
||||
// rpc_send_event(ui_client_channel_id, "nvim_call_function", args);
|
||||
|
||||
// Only detaches UI, but we want to exit the client fully.
|
||||
// rpc_send_event(input_chan_id, "nvim_ui_detach", (Array)ARRAY_DICT_INIT);
|
||||
// {
|
||||
// Error err;
|
||||
// nvim_ui_detach(input_chan_id, &err);
|
||||
// if (ERROR_SET(&err)) {
|
||||
// emsg(err.msg); // UI may have disappeared already.
|
||||
// }
|
||||
// api_clear_error(&err);
|
||||
// }
|
||||
|
||||
{
|
||||
const char *err;
|
||||
bool rv = channel_close(input_chan_id, kChannelPartAll, &err);
|
||||
if (!rv) {
|
||||
emsg(err); // UI may have disappeared already.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// "gv": Reselect the previous Visual area. If Visual already active,
|
||||
|
@ -273,7 +273,7 @@ void input_enqueue_raw(const char *data, size_t size)
|
||||
input_write_pos += to_write;
|
||||
}
|
||||
|
||||
size_t input_enqueue(String keys)
|
||||
size_t input_enqueue(uint64_t chan_id, String keys)
|
||||
{
|
||||
const char *ptr = keys.data;
|
||||
const char *end = ptr + keys.size;
|
||||
@ -323,6 +323,8 @@ size_t input_enqueue(String keys)
|
||||
|
||||
size_t rv = (size_t)(ptr - keys.data);
|
||||
process_ctrl_c();
|
||||
input_chan_id = chan_id;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,9 @@
|
||||
#include "nvim/macros_defs.h"
|
||||
|
||||
EXTERN bool used_stdin INIT( = false);
|
||||
/// Last channel that invoked 'nvim_input`.
|
||||
/// TODO(justinmk): race condition if multiple UIs/scripts send input?
|
||||
EXTERN uint64_t input_chan_id INIT( = 0);
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "os/input.h.generated.h"
|
||||
|
@ -1292,7 +1292,7 @@ static void shell_write_cb(Stream *stream, void *data, int status)
|
||||
msg_schedule_semsg(_("E5677: Error writing input to shell-command: %s"),
|
||||
uv_err_name(status));
|
||||
}
|
||||
stream_close(stream, NULL, NULL, false);
|
||||
stream_may_close(stream, false);
|
||||
}
|
||||
|
||||
/// Applies 'shellxescape' (p_sxe) and 'shellxquote' (p_sxq) to a command.
|
||||
|
@ -464,7 +464,7 @@ static void tinput_timer_cb(uv_timer_t *handle)
|
||||
{
|
||||
TermInput *input = handle->data;
|
||||
// If the raw buffer is not empty, process the raw buffer first because it is
|
||||
// processing an incomplete bracketed paster sequence.
|
||||
// processing an incomplete bracketed paste sequence.
|
||||
size_t size = rstream_available(&input->read_stream);
|
||||
if (size) {
|
||||
size_t consumed = handle_raw_buffer(input, true, input->read_stream.read_pos, size);
|
||||
|
@ -182,9 +182,10 @@ bool ui_override(void)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ui_active(void)
|
||||
/// Gets the number of UIs connected to this server.
|
||||
size_t ui_active(void)
|
||||
{
|
||||
return ui_count > 0;
|
||||
return ui_count;
|
||||
}
|
||||
|
||||
void ui_refresh(void)
|
||||
@ -197,7 +198,7 @@ void ui_refresh(void)
|
||||
int height = INT_MAX;
|
||||
bool ext_widgets[kUIExtCount];
|
||||
bool inclusive = ui_override();
|
||||
memset(ext_widgets, ui_active(), ARRAY_SIZE(ext_widgets));
|
||||
memset(ext_widgets, !!ui_active(), ARRAY_SIZE(ext_widgets));
|
||||
|
||||
for (size_t i = 0; i < ui_count; i++) {
|
||||
RemoteUI *ui = uis[i];
|
||||
|
@ -61,12 +61,17 @@ uint64_t ui_client_start_server(int argc, char **argv)
|
||||
|
||||
Channel *channel = channel_job_start(args, get_vim_var_str(VV_PROGPATH),
|
||||
CALLBACK_READER_INIT, on_err, CALLBACK_NONE,
|
||||
false, true, true, false, kChannelStdinPipe,
|
||||
false, true, true, true, kChannelStdinPipe,
|
||||
NULL, 0, 0, NULL, &exit_status);
|
||||
if (!channel) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// channel->stream.proc.out.s.close_cb = on_stdio_close;
|
||||
// channel->stream.proc.in.close_cb = on_stdio_close;
|
||||
channel->stream.proc.in.internal_close_cb = on_stdio_close;
|
||||
channel->stream.proc.in.internal_data = channel;
|
||||
|
||||
// If stdin is not a pty, it is forwarded to the client.
|
||||
// Replace stdin in the TUI process with the tty fd.
|
||||
if (ui_client_forward_stdin) {
|
||||
@ -81,12 +86,23 @@ uint64_t ui_client_start_server(int argc, char **argv)
|
||||
return channel->id;
|
||||
}
|
||||
|
||||
static void on_stdio_close(Stream *stream, void *data)
|
||||
FUNC_ATTR_NORETURN
|
||||
{
|
||||
// Channel *chan = data;
|
||||
ELOG("xxxxxxxxxxxxxxxxxxxxxxxxxxx");
|
||||
os_exit(0);
|
||||
}
|
||||
|
||||
/// Attaches this client to the UI channel, and sets its client info.
|
||||
void ui_client_attach(int width, int height, char *term, bool rgb)
|
||||
{
|
||||
//
|
||||
// nvim_ui_attach
|
||||
//
|
||||
MAXSIZE_TEMP_ARRAY(args, 3);
|
||||
ADD_C(args, INTEGER_OBJ(width));
|
||||
ADD_C(args, INTEGER_OBJ(height));
|
||||
|
||||
MAXSIZE_TEMP_DICT(opts, 9);
|
||||
PUT_C(opts, "rgb", BOOLEAN_OBJ(rgb));
|
||||
PUT_C(opts, "ext_linegrid", BOOLEAN_OBJ(true));
|
||||
@ -94,7 +110,6 @@ void ui_client_attach(int width, int height, char *term, bool rgb)
|
||||
if (term) {
|
||||
PUT_C(opts, "term_name", CSTR_AS_OBJ(term));
|
||||
}
|
||||
|
||||
PUT_C(opts, "term_colors", INTEGER_OBJ(t_colors));
|
||||
if (!ui_client_is_remote) {
|
||||
PUT_C(opts, "stdin_tty", BOOLEAN_OBJ(stdin_isatty));
|
||||
@ -108,6 +123,36 @@ void ui_client_attach(int width, int height, char *term, bool rgb)
|
||||
|
||||
rpc_send_event(ui_client_channel_id, "nvim_ui_attach", args);
|
||||
ui_client_attached = true;
|
||||
|
||||
//
|
||||
// nvim_set_client_info
|
||||
//
|
||||
MAXSIZE_TEMP_ARRAY(args2, 5);
|
||||
ADD_C(args2, CSTR_AS_OBJ("nvim-tui")); // name
|
||||
Object m = api_metadata();
|
||||
Dictionary version = { 0 };
|
||||
assert(m.data.dictionary.size > 0);
|
||||
for (size_t i = 0; i < m.data.dictionary.size; i++) {
|
||||
if (strequal(m.data.dictionary.items[i].key.data, "version")) {
|
||||
version = m.data.dictionary.items[i].value.data.dictionary;
|
||||
break;
|
||||
} else if (i + 1 == m.data.dictionary.size) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
ADD_C(args2, DICTIONARY_OBJ(version)); // version
|
||||
ADD_C(args2, CSTR_AS_OBJ("ui")); // type
|
||||
// We don't send api_metadata.functions as the "methods" because:
|
||||
// 1. it consumes memory.
|
||||
// 2. it is unlikely to be useful, since the peer can just call `nvim_get_api`.
|
||||
// 3. nvim_set_client_info expects a dict instead of an array.
|
||||
ADD_C(args2, ARRAY_OBJ((Array)ARRAY_DICT_INIT)); // methods
|
||||
MAXSIZE_TEMP_DICT(info, 9); // attributes
|
||||
PUT_C(info, "website", CSTR_AS_OBJ("https://neovim.io"));
|
||||
PUT_C(info, "license", CSTR_AS_OBJ("Apache 2"));
|
||||
PUT_C(info, "pid", INTEGER_OBJ(os_get_pid()));
|
||||
ADD_C(args2, DICTIONARY_OBJ(info)); // attributes
|
||||
rpc_send_event(ui_client_channel_id, "nvim_set_client_info", args2);
|
||||
}
|
||||
|
||||
void ui_client_detach(void)
|
||||
|
@ -1,12 +1,16 @@
|
||||
local uv = vim.uv
|
||||
local MsgpackRpcStream = require('test.client.msgpack_rpc_stream')
|
||||
|
||||
--- Nvim msgpack RPC session.
|
||||
---
|
||||
--- @class test.Session
|
||||
--- @field private _pending_messages string[]
|
||||
--- @field private _msgpack_rpc_stream test.MsgpackRpcStream
|
||||
--- @field private _prepare uv.uv_prepare_t
|
||||
--- @field private _timer uv.uv_timer_t
|
||||
--- @field private _is_running boolean
|
||||
--- @field private _stdout_buffer string[] Stores stdout chunks
|
||||
--- @field public stdout string Full stdout after the process exits
|
||||
local Session = {}
|
||||
Session.__index = Session
|
||||
if package.loaded['jit'] then
|
||||
@ -50,6 +54,7 @@ local function coroutine_exec(func, ...)
|
||||
end))
|
||||
end
|
||||
|
||||
--- Creates a new msgpack RPC session.
|
||||
function Session.new(stream)
|
||||
return setmetatable({
|
||||
_msgpack_rpc_stream = MsgpackRpcStream.new(stream),
|
||||
@ -202,8 +207,8 @@ function Session:_run(request_cb, notification_cb, timeout)
|
||||
end)
|
||||
end
|
||||
self._msgpack_rpc_stream:read_start(request_cb, notification_cb, function()
|
||||
uv.stop()
|
||||
self.eof_err = { 1, 'EOF was received from Nvim. Likely the Nvim process crashed.' }
|
||||
uv.stop()
|
||||
self.eof_err = { 1, 'EOF was received from Nvim. Likely the Nvim process crashed.' }
|
||||
end)
|
||||
uv.run()
|
||||
self._prepare:stop()
|
||||
@ -211,4 +216,5 @@ function Session:_run(request_cb, notification_cb, timeout)
|
||||
self._msgpack_rpc_stream:read_stop()
|
||||
end
|
||||
|
||||
--- Nvim msgpack RPC session.
|
||||
return Session
|
||||
|
@ -119,6 +119,36 @@ end
|
||||
local ChildProcessStream = {}
|
||||
ChildProcessStream.__index = ChildProcessStream
|
||||
|
||||
function ChildProcessStream:get_stdout_sync()
|
||||
local output_buffer = {}
|
||||
local done = false
|
||||
local error_message = nil
|
||||
|
||||
self._child_stdout:read_start(function(err, chunk)
|
||||
if err then
|
||||
error_message = err
|
||||
done = true
|
||||
elseif chunk then
|
||||
table.insert(output_buffer, chunk)
|
||||
else
|
||||
-- End of stdout
|
||||
self._child_stdout:read_stop()
|
||||
done = true
|
||||
end
|
||||
end)
|
||||
|
||||
-- Run the libuv event loop until we're done reading
|
||||
while not done do
|
||||
uv.run("once")
|
||||
end
|
||||
|
||||
if error_message then
|
||||
error(error_message)
|
||||
end
|
||||
|
||||
return table.concat(output_buffer)
|
||||
end
|
||||
|
||||
--- @param argv string[]
|
||||
--- @param env string[]?
|
||||
--- @param io_extra uv.uv_pipe_t?
|
||||
|
@ -38,6 +38,12 @@ local testlog = 'Xtest-startupspec-log'
|
||||
|
||||
describe('startup', function()
|
||||
it('--clean', function()
|
||||
local s = clear({
|
||||
args = { '--listen' },
|
||||
args_rm = { '--embed' },
|
||||
})
|
||||
eq('x', s)
|
||||
|
||||
clear()
|
||||
matches(
|
||||
vim.pesc(t.fix_slashes(fn.stdpath('config'))),
|
||||
|
@ -455,7 +455,7 @@ end
|
||||
--- @param argv string[]
|
||||
--- @param merge boolean?
|
||||
--- @param env string[]?
|
||||
--- @param keep boolean
|
||||
--- @param keep boolean Don't close the current global session.
|
||||
--- @param io_extra uv.uv_pipe_t? used for stdin_fd, see :help ui-option
|
||||
--- @return test.Session
|
||||
function M.spawn(argv, merge, env, keep, io_extra)
|
||||
@ -465,7 +465,17 @@ function M.spawn(argv, merge, env, keep, io_extra)
|
||||
|
||||
local child_stream =
|
||||
ChildProcessStream.spawn(merge and M.merge_args(prepend_argv, argv) or argv, env, io_extra)
|
||||
return Session.new(child_stream)
|
||||
|
||||
-- local session = Session.new(child_stream)
|
||||
--
|
||||
-- -- Run the session (synchronously waits for the process to complete)
|
||||
-- session:run(function(method, args) end, function(method, args) end)
|
||||
--
|
||||
-- -- Access the stdout after the process completes
|
||||
-- print("Process stdout: " .. (session.stdout or ""))
|
||||
-- return session
|
||||
|
||||
return child_stream:get_stdout_sync()
|
||||
end
|
||||
|
||||
-- Creates a new Session connected by domain socket (named pipe) or TCP.
|
||||
@ -478,6 +488,8 @@ end
|
||||
|
||||
-- Starts (and returns) a new global Nvim session.
|
||||
--
|
||||
-- Use `spawn_argv()` to get a new session without replacing the current global session.
|
||||
--
|
||||
-- Parameters are interpreted as startup args, OR a map with these keys:
|
||||
-- args: List: Args appended to the default `nvim_argv` set.
|
||||
-- args_rm: List: Args removed from the default set. All cases are
|
||||
@ -493,8 +505,10 @@ function M.clear(...)
|
||||
return M.get_session()
|
||||
end
|
||||
|
||||
--- same params as clear, but does returns the session instead
|
||||
--- of replacing the default session
|
||||
--- Same as clear(), but only returns the session instead of replacing the current global session.
|
||||
---
|
||||
--- @param keep boolean Don't close the current global session.
|
||||
---
|
||||
--- @return test.Session
|
||||
function M.spawn_argv(keep, ...)
|
||||
local argv, env, io_extra = M.new_argv(...)
|
||||
|
Loading…
Reference in New Issue
Block a user