Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): broadcast events to ALL channels #28487

Merged
merged 1 commit into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 0 additions & 18 deletions runtime/doc/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1461,24 +1461,6 @@ nvim_strwidth({text}) *nvim_strwidth()*
Return: ~
Number of cells

nvim_subscribe({event}) *nvim_subscribe()*
Subscribes to event broadcasts.

Attributes: ~
|RPC| only

Parameters: ~
{event} Event type string

nvim_unsubscribe({event}) *nvim_unsubscribe()*
Unsubscribes to event broadcasts.

Attributes: ~
|RPC| only

Parameters: ~
{event} Event type string

nvim__complete_set({index}, {opts}) *nvim__complete_set()*
EXPERIMENTAL: this API may change in the future.

Expand Down
114 changes: 61 additions & 53 deletions runtime/doc/deprecated.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,76 +12,82 @@ They should not be used in new scripts, and old scripts should be updated.
==============================================================================
Deprecated features

DEPRECATED IN 0.11 *deprecated-0.11*
------------------------------------------------------------------------------
DEPRECATED IN 0.11 *deprecated-0.11*

API
- nvim_subscribe() Plugins must maintain their own "multicast" channels list.
- nvim_unsubscribe() Plugins must maintain their own "multicast" channels list.

• N/A

DEPRECATED IN 0.10 *deprecated-0.10*
------------------------------------------------------------------------------
DEPRECATED IN 0.10 *deprecated-0.10*

API
*nvim_buf_get_option()* Use |nvim_get_option_value()| instead.
*nvim_buf_set_option()* Use |nvim_set_option_value()| instead.
*nvim_call_atomic()* Use |nvim_exec_lua()| instead.
*nvim_get_option()* Use |nvim_get_option_value()| instead.
*nvim_set_option()* Use |nvim_set_option_value()| instead.
*nvim_win_get_option()* Use |nvim_get_option_value()| instead.
*nvim_win_set_option()* Use |nvim_set_option_value()| instead.

CHECKHEALTH
*health#report_error* *vim.health.report_error()* Use |vim.health.error()| instead.
*health#report_info* *vim.health.report_info()* Use |vim.health.info()| instead.
*health#report_ok* *vim.health.report_ok()* Use |vim.health.ok()| instead.
*health#report_start* *vim.health.report_start()* Use |vim.health.start()| instead.
*health#report_warn* *vim.health.report_warn()* Use |vim.health.warn()| instead.

DIAGNOSTICS
• Configuring |diagnostic-signs| using |:sign-define| or |sign_define()|. Use
the "signs" key of |vim.diagnostic.config()| instead.

• Checkhealth functions:
- *health#report_error* *vim.health.report_error()* Use |vim.health.error()| instead.
- *health#report_info* *vim.health.report_info()* Use |vim.health.info()| instead.
- *health#report_ok* *vim.health.report_ok()* Use |vim.health.ok()| instead.
- *health#report_start* *vim.health.report_start()* Use |vim.health.start()| instead.
- *health#report_warn* *vim.health.report_warn()* Use |vim.health.warn()| instead.

|API| functions:
- *nvim_buf_get_option()* Use |nvim_get_option_value()| instead.
- *nvim_buf_set_option()* Use |nvim_set_option_value()| instead.
- *nvim_call_atomic()* Use |nvim_exec_lua()| instead.
- *nvim_get_option()* Use |nvim_get_option_value()| instead.
- *nvim_set_option()* Use |nvim_set_option_value()| instead.
- *nvim_win_get_option()* Use |nvim_get_option_value()| instead.
- *nvim_win_set_option()* Use |nvim_set_option_value()| instead.

• vim.diagnostic functions:
- *vim.diagnostic.disable()* Use |vim.diagnostic.enable()|
- *vim.diagnostic.is_disabled()* Use |vim.diagnostic.is_enabled()|
- Legacy signature: `vim.diagnostic.enable(buf:number, namespace:number)`

• vim.lsp functions:
- *vim.lsp.util.get_progress_messages()* Use |vim.lsp.status()| instead.
- *vim.lsp.get_active_clients()* Use |vim.lsp.get_clients()| instead.
- *vim.lsp.for_each_buffer_client()* Use |vim.lsp.get_clients()| instead.
- *vim.lsp.util.trim_empty_lines()* Use |vim.split()| with `trimempty` instead.
- *vim.lsp.util.try_trim_markdown_code_blocks()*
- *vim.lsp.util.set_lines()*
- *vim.lsp.util.extract_completion_items()*
- *vim.lsp.util.parse_snippet()*
- *vim.lsp.util.text_document_completion_list_to_complete_items()*
- *vim.lsp.util.lookup_section()* Use |vim.tbl_get()| instead: >
*vim.diagnostic.disable()* Use |vim.diagnostic.enable()|
*vim.diagnostic.is_disabled()* Use |vim.diagnostic.is_enabled()|
Legacy signature: `vim.diagnostic.enable(buf:number, namespace:number)`

LSP
*vim.lsp.util.get_progress_messages()* Use |vim.lsp.status()| instead.
*vim.lsp.get_active_clients()* Use |vim.lsp.get_clients()| instead.
*vim.lsp.for_each_buffer_client()* Use |vim.lsp.get_clients()| instead.
*vim.lsp.util.trim_empty_lines()* Use |vim.split()| with `trimempty` instead.
*vim.lsp.util.try_trim_markdown_code_blocks()*
*vim.lsp.util.set_lines()*
*vim.lsp.util.extract_completion_items()*
*vim.lsp.util.parse_snippet()*
*vim.lsp.util.text_document_completion_list_to_complete_items()*
*vim.lsp.util.lookup_section()* Use |vim.tbl_get()| instead: >
local keys = vim.split(section, '.', { plain = true })
local vim.tbl_get(table, unpack(keys))
LUA
*vim.loop* Use |vim.uv| instead.
*vim.tbl_add_reverse_lookup()*
*vim.tbl_flatten()* Use |Iter:flatten()| instead.
*vim.tbl_islist()* Use |vim.islist()| instead.

• vim.treesitter functions:
- *LanguageTree:for_each_child()* Use |LanguageTree:children()| (non-recursive) instead.

OPTIONS
• The "term_background" UI option |ui-ext-options| is deprecated and no longer
populated. Background color detection is now performed in Lua by the Nvim
core, not the TUI.

• Lua stdlib:
- *vim.tbl_add_reverse_lookup()*
- *vim.tbl_flatten()* Use |Iter:flatten()| instead.
- *vim.tbl_islist()* Use |vim.islist()| instead.

DEPRECATED IN 0.9 *deprecated-0.9*
TREESITTER
*LanguageTree:for_each_child()* Use |LanguageTree:children()| (non-recursive) instead.

• vim.treesitter functions
- *vim.treesitter.language.require_language()* Use |vim.treesitter.language.add()| instead.
- *vim.treesitter.get_node_at_pos()* Use |vim.treesitter.get_node()| instead.
- *vim.treesitter.get_node_at_cursor()* Use |vim.treesitter.get_node()|
and |TSNode:type()| instead.

|API| functions:
- *nvim_get_hl_by_name()* Use |nvim_get_hl()| instead.
- *nvim_get_hl_by_id()* Use |nvim_get_hl()| instead.
------------------------------------------------------------------------------
DEPRECATED IN 0.9 *deprecated-0.9*

API
- *nvim_get_hl_by_name()* Use |nvim_get_hl()| instead.
- *nvim_get_hl_by_id()* Use |nvim_get_hl()| instead.

TREESITTER
- *vim.treesitter.language.require_language()* Use |vim.treesitter.language.add()| instead.
- *vim.treesitter.get_node_at_pos()* Use |vim.treesitter.get_node()| instead.
- *vim.treesitter.get_node_at_cursor()* Use |vim.treesitter.get_node()|
and |TSNode:type()| instead.
• The following top level Treesitter functions have been moved:
- *vim.treesitter.inspect_language()* -> |vim.treesitter.language.inspect()|
- *vim.treesitter.get_query_files()* -> |vim.treesitter.query.get_files()|
Expand All @@ -98,10 +104,12 @@ DEPRECATED IN 0.9 *deprecated-0.9*
- *vim.treesitter.query.get_range()* -> |vim.treesitter.get_range()|
- *vim.treesitter.query.get_node_text()* -> |vim.treesitter.get_node_text()|

• Lua stdlib:
LUA
- *nvim_exec()* Use |nvim_exec2()| instead.
- *vim.pretty_print()* Use |vim.print()| instead.


------------------------------------------------------------------------------
DEPRECATED IN 0.8 OR EARLIER

API
Expand Down
7 changes: 6 additions & 1 deletion runtime/doc/news.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ These changes may require adaptations in your config or plugins.

API

• TODO
`vim.rpcnotify(0)` and `rpcnotify(0)` broadcast to ALL channels. Previously
they would "multicast" only to subscribed channels (controlled by
`nvim_subscribe()`). Plugins and clients that want "multicast" behavior must
now maintain their own list of channels.
• In the future, |vim.rpcnotify()| may accept a list of channels, if there
is demand for this use-case.

DEFAULTS

Expand Down
20 changes: 20 additions & 0 deletions src/nvim/api/deprecated.c
Original file line number Diff line number Diff line change
Expand Up @@ -786,3 +786,23 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Arena *arena, Error *er
api_clear_error(&nested_error);
return rv;
}

/// @deprecated
///
/// @param channel_id Channel id (passed automatically by the dispatcher)
/// @param event Event type string
void nvim_subscribe(uint64_t channel_id, String event)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
// Does nothing. `rpcnotify(0,…)` broadcasts to all channels, there are no "subscriptions".
}

/// @deprecated
///
/// @param channel_id Channel id (passed automatically by the dispatcher)
/// @param event Event type string
void nvim_unsubscribe(uint64_t channel_id, String event)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
// Does nothing. `rpcnotify(0,…)` broadcasts to all channels, there are no "subscriptions".
}
30 changes: 0 additions & 30 deletions src/nvim/api/vim.c
Original file line number Diff line number Diff line change
Expand Up @@ -1334,36 +1334,6 @@ void nvim_put(ArrayOf(String) lines, String type, Boolean after, Boolean follow,
});
}

/// Subscribes to event broadcasts.
///
/// @param channel_id Channel id (passed automatically by the dispatcher)
/// @param event Event type string
void nvim_subscribe(uint64_t channel_id, String event)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
size_t length = (event.size < METHOD_MAXLEN ? event.size : METHOD_MAXLEN);
char e[METHOD_MAXLEN + 1];
memcpy(e, event.data, length);
e[length] = NUL;
rpc_subscribe(channel_id, e);
}

/// Unsubscribes to event broadcasts.
///
/// @param channel_id Channel id (passed automatically by the dispatcher)
/// @param event Event type string
void nvim_unsubscribe(uint64_t channel_id, String event)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
size_t length = (event.size < METHOD_MAXLEN
? event.size
: METHOD_MAXLEN);
char e[METHOD_MAXLEN + 1];
memcpy(e, event.data, length);
e[length] = NUL;
rpc_unsubscribe(channel_id, e);
}

/// Returns the 24-bit RGB value of a |nvim_get_color_map()| color name or
/// "#rrggbb" hexadecimal string.
///
Expand Down
74 changes: 10 additions & 64 deletions src/nvim/msgpack_rpc/channel.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@ static void log_notify(char *dir, uint64_t channel_id, const char *name)
# define log_notify(...)
#endif

static Set(cstr_t) event_strings = SET_INIT;

#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "msgpack_rpc/channel.c.generated.h"
#endif
Expand Down Expand Up @@ -111,9 +109,9 @@ static Channel *find_rpc_channel(uint64_t id)
return chan;
}

/// Publishes an event to a channel.
/// Publishes an event to a channel (emits a notification to method `name`).
///
/// @param id Channel id. 0 means "broadcast to all subscribed channels"
/// @param id Channel id, or 0 to broadcast to all RPC channels.
/// @param name Event name (application-defined)
/// @param args Array of event arguments
/// @return True if the event was sent successfully, false otherwise.
Expand Down Expand Up @@ -204,41 +202,6 @@ Object rpc_send_call(uint64_t id, const char *method_name, Array args, ArenaMem
return frame.errored ? NIL : frame.result;
}

/// Subscribes to event broadcasts
///
/// @param id The channel id
/// @param event The event type string
void rpc_subscribe(uint64_t id, char *event)
{
Channel *channel;

if (!(channel = find_rpc_channel(id))) {
abort();
}

const char **key_alloc = NULL;
if (set_put_ref(cstr_t, &event_strings, event, &key_alloc)) {
*key_alloc = xstrdup(event);
}

set_put(cstr_t, channel->rpc.subscribed_events, *key_alloc);
}

/// Unsubscribes to event broadcasts
///
/// @param id The channel id
/// @param event The event type string
void rpc_unsubscribe(uint64_t id, char *event)
{
Channel *channel;

if (!(channel = find_rpc_channel(id))) {
abort();
}

unsubscribe(channel, event);
}

static void receive_msgpack(Stream *stream, RBuffer *rbuf, size_t c, void *data, bool eof)
{
Channel *channel = data;
Expand Down Expand Up @@ -494,34 +457,24 @@ static void send_error(Channel *chan, MsgpackRpcRequestHandler handler, MessageT
api_clear_error(&e);
}

/// Broadcasts a notification to all RPC channels.
static void broadcast_event(const char *name, Array args)
{
kvec_withinit_t(Channel *, 4) subscribed = KV_INITIAL_VALUE;
kvi_init(subscribed);
kvec_withinit_t(Channel *, 4) chans = KV_INITIAL_VALUE;
kvi_init(chans);
Channel *channel;

map_foreach_value(&channels, channel, {
if (channel->is_rpc
&& set_has(cstr_t, channel->rpc.subscribed_events, name)) {
kv_push(subscribed, channel);
if (channel->is_rpc) {
kv_push(chans, channel);
}
});

if (kv_size(subscribed)) {
serialize_request(subscribed.items, kv_size(subscribed), 0, name, args);
if (kv_size(chans)) {
serialize_request(chans.items, kv_size(chans), 0, name, args);
}

kvi_destroy(subscribed);
}

static void unsubscribe(Channel *channel, char *event)
{
if (!set_has(cstr_t, &event_strings, event)) {
WLOG("RPC: ch %" PRIu64 ": tried to unsubscribe unknown event '%s'",
channel->id, event);
return;
}
set_del(cstr_t, channel->rpc.subscribed_events, event);
kvi_destroy(chans);
}

/// Mark rpc state as closed, and release its reference to the channel.
Expand Down Expand Up @@ -551,7 +504,6 @@ void rpc_free(Channel *channel)
unpacker_teardown(channel->rpc.unpacker);
xfree(channel->rpc.unpacker);

set_destroy(cstr_t, channel->rpc.subscribed_events);
kv_destroy(channel->rpc.call_stack);
api_free_dictionary(channel->rpc.info);
}
Expand Down Expand Up @@ -719,12 +671,6 @@ const char *get_client_info(Channel *chan, const char *key)
#ifdef EXITFREE
void rpc_free_all_mem(void)
{
cstr_t key;
set_foreach(&event_strings, key, {
xfree((void *)key);
});
set_destroy(cstr_t, &event_strings);

multiqueue_free(ch_before_blocking_events);
}
#endif
1 change: 0 additions & 1 deletion src/nvim/msgpack_rpc/channel_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ typedef struct {
} RequestEvent;

typedef struct {
Set(cstr_t) subscribed_events[1];
bool closed;
Unpacker *unpacker;
uint32_t next_request_id;
Expand Down