aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/api/private/helpers.c17
-rw-r--r--src/nvim/api/vim.c112
-rw-r--r--src/nvim/auevents.lua2
-rw-r--r--src/nvim/channel.c148
-rw-r--r--src/nvim/msgpack_rpc/channel.c19
-rw-r--r--src/nvim/msgpack_rpc/channel_defs.h1
-rw-r--r--src/nvim/os/pty_process_unix.c5
-rw-r--r--src/nvim/os/pty_process_win.c5
-rw-r--r--src/nvim/terminal.c5
9 files changed, 269 insertions, 45 deletions
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 17ee3ed711..692a0b51fd 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1030,6 +1030,16 @@ Array copy_array(Array array)
return rv;
}
+Dictionary copy_dictionary(Dictionary dict)
+{
+ Dictionary rv = ARRAY_DICT_INIT;
+ for (size_t i = 0; i < dict.size; i++) {
+ KeyValuePair item = dict.items[i];
+ PUT(rv, item.key.data, copy_object(item.value));
+ }
+ return rv;
+}
+
/// Creates a deep clone of an object
Object copy_object(Object obj)
{
@@ -1047,12 +1057,7 @@ Object copy_object(Object obj)
return ARRAY_OBJ(copy_array(obj.data.array));
case kObjectTypeDictionary: {
- Dictionary rv = ARRAY_DICT_INIT;
- for (size_t i = 0; i < obj.data.dictionary.size; i++) {
- KeyValuePair item = obj.data.dictionary.items[i];
- PUT(rv, item.key.data, copy_object(item.value));
- }
- return DICTIONARY_OBJ(rv);
+ return DICTIONARY_OBJ(copy_dictionary(obj.data.dictionary));
}
default:
abort();
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 7a951d4e67..f587948cf0 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -990,6 +990,118 @@ Array nvim_get_api_info(uint64_t channel_id)
return rv;
}
+/// Identify the client for nvim. Can be called more than once, but subsequent
+/// calls will remove earlier info, which should be resent if it is still
+/// valid. (This could happen if a library first identifies the channel, and a
+/// plugin using that library later overrides that info)
+///
+/// @param name short name for the connected client
+/// @param version Dictionary describing the version, with the following
+/// possible keys (all optional)
+/// - "major" major version (defaults to 0 if not set, for no release yet)
+/// - "minor" minor version
+/// - "patch" patch number
+/// - "prerelease" string describing a prerelease, like "dev" or "beta1"
+/// - "commit" hash or similar identifier of commit
+/// @param type Must be one of the following values. A client library should
+/// use "remote" if the library user hasn't specified other value.
+/// - "remote" remote client that connected to nvim.
+/// - "ui" gui frontend
+/// - "embedder" application using nvim as a component, for instance
+/// IDE/editor implementing a vim mode.
+/// - "host" plugin host, typically started by nvim
+/// - "plugin" single plugin, started by nvim
+/// @param methods Builtin methods in the client. For a host, this does not
+/// include plugin methods which will be discovered later.
+/// The key should be the method name, the values are dicts with
+/// the following (optional) keys:
+/// - "async" if true, send as a notification. If false or unspecified,
+/// use a blocking request
+/// - "nargs" Number of arguments. Could be a single integer or an array
+/// two integers, minimum and maximum inclusive.
+/// Further keys might be added in later versions of nvim and unknown keys
+/// are thus ignored. Clients must only use keys defined in this or later
+/// versions of nvim!
+///
+/// @param attributes Informal attributes describing the client. Clients might
+/// define their own keys, but the following are suggested:
+/// - "website" Website of client (for instance github repository)
+/// - "license" Informal descripton of the license, such as "Apache 2",
+/// "GPLv3" or "MIT"
+/// - "logo" URI or path to image, preferably small logo or icon.
+/// .png or .svg format is preferred.
+///
+void nvim_set_client_info(uint64_t channel_id, String name,
+ Dictionary version, String type,
+ Dictionary methods, Dictionary attributes,
+ Error *err)
+ FUNC_API_SINCE(4) FUNC_API_REMOTE_ONLY
+{
+ Dictionary info = ARRAY_DICT_INIT;
+ PUT(info, "name", copy_object(STRING_OBJ(name)));
+
+ version = copy_dictionary(version);
+ bool has_major = false;
+ for (size_t i = 0; i < version.size; i++) {
+ if (strequal(version.items[i].key.data, "major")) {
+ has_major = true;
+ break;
+ }
+ }
+ if (!has_major) {
+ PUT(version, "major", INTEGER_OBJ(0));
+ }
+ PUT(info, "version", DICTIONARY_OBJ(version));
+
+ PUT(info, "type", copy_object(STRING_OBJ(type)));
+ PUT(info, "methods", DICTIONARY_OBJ(copy_dictionary(methods)));
+ PUT(info, "attributes", DICTIONARY_OBJ(copy_dictionary(attributes)));
+
+ rpc_set_client_info(channel_id, info);
+}
+
+/// Get information about a channel.
+///
+/// @returns a Dictionary, describing a channel with the
+/// following keys:
+/// - "stream" the stream underlying the channel
+/// - "stdio" stdin and stdout of this Nvim instance
+/// - "stderr" stderr of this Nvim instance
+/// - "socket" TCP/IP socket or named pipe
+/// - "job" job with communication over its stdio
+/// - "mode" how data received on the channel is interpreted
+/// - "bytes" send and recieve raw bytes
+/// - "terminal" a |terminal| instance interprets ASCII sequences
+/// - "rpc" |RPC| communication on the channel is active
+/// - "pty" Name of pseudoterminal, if one is used (optional).
+/// On a POSIX system, this will be a device path like
+/// /dev/pts/1. Even if the name is unknown, the key will
+/// still be present to indicate a pty is used. This is
+/// currently the case when using winpty on windows.
+/// - "buffer" buffer with connected |terminal| instance (optional)
+/// - "client" information about the client on the other end of the
+/// RPC channel, if it has added it using
+/// |nvim_set_client_info|. (optional)
+///
+Dictionary nvim_get_chan_info(Integer chan, Error *err)
+ FUNC_API_SINCE(4)
+{
+ if (chan < 0) {
+ return (Dictionary)ARRAY_DICT_INIT;
+ }
+ return channel_info((uint64_t)chan);
+}
+
+/// Get information about all open channels.
+///
+/// @returns Array of Dictionaries, each describing a channel with
+/// the format specified at |nvim_get_chan_info|.
+Array nvim_list_chans(void)
+ FUNC_API_SINCE(4)
+{
+ return channel_all_info();
+}
+
/// Calls many API methods atomically.
///
/// This has two main usages:
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index f07a92ab87..d0a3f38c6b 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -19,6 +19,8 @@ return {
'BufWriteCmd', -- write buffer using command
'BufWritePost', -- after writing a buffer
'BufWritePre', -- before writing a buffer
+ 'ChanOpen', -- channel was opened
+ 'ChanInfo', -- info was received about channel
'CmdLineEnter', -- after entering cmdline mode
'CmdLineLeave', -- before leaving cmdline mode
'CmdUndefined', -- command undefined
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index b37fa10b12..64d743891b 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -1,11 +1,13 @@
// 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
+#include "nvim/api/private/helpers.h"
#include "nvim/api/ui.h"
#include "nvim/channel.h"
#include "nvim/eval.h"
#include "nvim/eval/encode.h"
#include "nvim/event/socket.h"
+#include "nvim/fileio.h"
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/msgpack_rpc/server.h"
#include "nvim/os/shell.h"
@@ -145,6 +147,9 @@ bool channel_close(uint64_t id, ChannelPart part, const char **error)
return false;
}
break;
+
+ default:
+ abort();
}
return true;
@@ -180,47 +185,11 @@ static Channel *channel_alloc(ChannelStreamType type)
return chan;
}
-/// Not implemented, only logging for now
void channel_create_event(Channel *chan, const char *ext_source)
{
#if MIN_LOG_LEVEL <= INFO_LOG_LEVEL
- const char *stream_desc;
- const char *mode_desc;
const char *source;
- switch (chan->streamtype) {
- case kChannelStreamProc:
- if (chan->stream.proc.type == kProcessTypePty) {
- stream_desc = "pty job";
- } else {
- stream_desc = "job";
- }
- break;
-
- case kChannelStreamStdio:
- stream_desc = "stdio";
- break;
-
- case kChannelStreamSocket:
- stream_desc = "socket";
- break;
-
- case kChannelStreamInternal:
- stream_desc = "socket (internal)";
- break;
-
- default:
- stream_desc = "?";
- }
-
- if (chan->is_rpc) {
- mode_desc = ", rpc";
- } else if (chan->term) {
- mode_desc = ", terminal";
- } else {
- mode_desc = "";
- }
-
if (ext_source) {
// TODO(bfredl): in a future improved traceback solution,
// external events should be included.
@@ -230,12 +199,21 @@ void channel_create_event(Channel *chan, const char *ext_source)
source = (const char *)IObuff;
}
- ILOG("new channel %" PRIu64 " (%s%s): %s", chan->id, stream_desc,
- mode_desc, source);
+ Dictionary info = channel_info(chan->id);
+ typval_T tv = TV_INITIAL_VALUE;
+ // TODO(bfredl): do the conversion in one step. Also would be nice
+ // to pretty print top level dict in defined order
+ (void)object_to_vim(DICTIONARY_OBJ(info), &tv, NULL);
+ char *str = encode_tv2json(&tv, NULL);
+ ILOG("new channel %" PRIu64 " (%s) : %s", chan->id, source, str);
+ xfree(str);
+ api_free_dictionary(info);
+
#else
- (void)chan;
(void)ext_source;
#endif
+
+ channel_info_changed(chan, true);
}
void channel_incref(Channel *chan)
@@ -755,3 +733,95 @@ static void term_close(void *data)
multiqueue_put(chan->events, term_delayed_free, 1, data);
}
+void channel_info_changed(Channel *chan, bool new)
+{
+ event_T event = new ? EVENT_CHANOPEN : EVENT_CHANINFO;
+ if (has_event(event)) {
+ channel_incref(chan);
+ multiqueue_put(main_loop.events, set_info_event,
+ 2, chan, event);
+ }
+}
+
+static void set_info_event(void **argv)
+{
+ Channel *chan = argv[0];
+ event_T event = (event_T)(ptrdiff_t)argv[1];
+
+ dict_T *dict = get_vim_var_dict(VV_EVENT);
+ Dictionary info = channel_info(chan->id);
+ typval_T retval;
+ (void)object_to_vim(DICTIONARY_OBJ(info), &retval, NULL);
+ tv_dict_add_dict(dict, S_LEN("info"), retval.vval.v_dict);
+
+ apply_autocmds(event, NULL, NULL, false, curbuf);
+
+ tv_dict_clear(dict);
+ api_free_dictionary(info);
+ channel_decref(chan);
+}
+
+Dictionary channel_info(uint64_t id)
+{
+ Channel *chan = find_channel(id);
+ if (!chan) {
+ return (Dictionary)ARRAY_DICT_INIT;
+ }
+
+ Dictionary info = ARRAY_DICT_INIT;
+ PUT(info, "id", INTEGER_OBJ((Integer)chan->id));
+
+ const char *stream_desc, *mode_desc;
+ switch (chan->streamtype) {
+ case kChannelStreamProc:
+ stream_desc = "job";
+ if (chan->stream.proc.type == kProcessTypePty) {
+ const char *name = pty_process_tty_name(&chan->stream.pty);
+ PUT(info, "pty", STRING_OBJ(cstr_to_string(name)));
+ }
+ break;
+
+ case kChannelStreamStdio:
+ stream_desc = "stdio";
+ break;
+
+ case kChannelStreamStderr:
+ stream_desc = "stderr";
+ break;
+
+ case kChannelStreamInternal:
+ PUT(info, "internal", BOOLEAN_OBJ(true));
+ // FALLTHROUGH
+
+ case kChannelStreamSocket:
+ stream_desc = "socket";
+ break;
+
+ default:
+ abort();
+ }
+ PUT(info, "stream", STRING_OBJ(cstr_to_string(stream_desc)));
+
+ if (chan->is_rpc) {
+ mode_desc = "rpc";
+ PUT(info, "client", DICTIONARY_OBJ(rpc_client_info(chan)));
+ } else if (chan->term) {
+ mode_desc = "terminal";
+ PUT(info, "buffer", BUFFER_OBJ(terminal_buf(chan->term)));
+ } else {
+ mode_desc = "bytes";
+ }
+ PUT(info, "mode", STRING_OBJ(cstr_to_string(mode_desc)));
+
+ return info;
+}
+
+Array channel_all_info(void)
+{
+ Channel *channel;
+ Array ret = ARRAY_DICT_INIT;
+ map_foreach_value(channels, channel, {
+ ADD(ret, DICTIONARY_OBJ(channel_info(channel->id)));
+ });
+ return ret;
+}
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index 32781cf4d9..26b84b7cc7 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -61,6 +61,7 @@ void rpc_start(Channel *channel)
rpc->unpacker = msgpack_unpacker_new(MSGPACK_UNPACKER_INIT_BUFFER_SIZE);
rpc->subscribed_events = pmap_new(cstr_t)();
rpc->next_request_id = 1;
+ rpc->info = (Dictionary)ARRAY_DICT_INIT;
kv_init(rpc->call_stack);
if (channel->streamtype != kChannelStreamInternal) {
@@ -553,6 +554,7 @@ void rpc_free(Channel *channel)
pmap_free(cstr_t)(channel->rpc.subscribed_events);
kv_destroy(channel->rpc.call_stack);
+ api_free_dictionary(channel->rpc.info);
}
static bool is_rpc_response(msgpack_object *obj)
@@ -642,6 +644,23 @@ static WBuffer *serialize_response(uint64_t channel_id,
return rv;
}
+void rpc_set_client_info(uint64_t id, Dictionary info)
+{
+ Channel *chan = find_rpc_channel(id);
+ if (!chan) {
+ abort();
+ }
+
+ api_free_dictionary(chan->rpc.info);
+ chan->rpc.info = info;
+ channel_info_changed(chan, false);
+}
+
+Dictionary rpc_client_info(Channel *chan)
+{
+ return copy_dictionary(chan->rpc.info);
+}
+
#if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL
#define REQ "[request] "
#define RES "[response] "
diff --git a/src/nvim/msgpack_rpc/channel_defs.h b/src/nvim/msgpack_rpc/channel_defs.h
index 6d8362e8b7..bfa7f7b87c 100644
--- a/src/nvim/msgpack_rpc/channel_defs.h
+++ b/src/nvim/msgpack_rpc/channel_defs.h
@@ -31,6 +31,7 @@ typedef struct {
msgpack_unpacker *unpacker;
uint64_t next_request_id;
kvec_t(ChannelCallFrame *) call_stack;
+ Dictionary info;
} RpcState;
#endif // NVIM_MSGPACK_RPC_CHANNEL_DEFS_H
diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c
index dfe2cfbb8d..71bb2d8a5e 100644
--- a/src/nvim/os/pty_process_unix.c
+++ b/src/nvim/os/pty_process_unix.c
@@ -115,6 +115,11 @@ error:
return status;
}
+const char *pty_process_tty_name(PtyProcess *ptyproc)
+{
+ return ptsname(ptyproc->tty_fd);
+}
+
void pty_process_resize(PtyProcess *ptyproc, uint16_t width, uint16_t height)
FUNC_ATTR_NONNULL_ALL
{
diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c
index a6774a6275..c5f8efadff 100644
--- a/src/nvim/os/pty_process_win.c
+++ b/src/nvim/os/pty_process_win.c
@@ -183,6 +183,11 @@ cleanup:
return status;
}
+const char *pty_process_tty_name(PtyProcess *ptyproc)
+{
+ return "?";
+}
+
void pty_process_resize(PtyProcess *ptyproc, uint16_t width,
uint16_t height)
FUNC_ATTR_NONNULL_ALL
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index 31875fac31..39cb2b6372 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -619,6 +619,11 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr,
}
}
+Buffer terminal_buf(const Terminal *term)
+{
+ return term->buf_handle;
+}
+
// }}}
// libvterm callbacks {{{