mirror of
https://github.com/neovim/neovim.git
synced 2025-09-25 04:28:33 +00:00
provider: New module used to expose extension points for core services
Introducing the concept of providers: co-processes that talk with the editor through the remote API and provide implementation for one or more core services. The `provider_register` function and it's API wrapper can be used by channels that want to self-register as a service provider. Some old builtin vim features will be re-implemented as providers. The `provider_has_feature` function is used to check if a provider implementing a certain feature is available(It will be called by the `has` vimscript function to check for features in a vim-compatible way) This implements the provider module without exposing any extension points, which will be done in future commits.
This commit is contained in:
@@ -51,6 +51,7 @@ set(CONV_SRCS
|
|||||||
os/rstream.c
|
os/rstream.c
|
||||||
os/signal.c
|
os/signal.c
|
||||||
os/users.c
|
os/users.c
|
||||||
|
os/provider.c
|
||||||
os/uv_helpers.c
|
os/uv_helpers.c
|
||||||
os/wstream.c
|
os/wstream.c
|
||||||
os/msgpack_rpc.c
|
os/msgpack_rpc.c
|
||||||
|
@@ -10,6 +10,7 @@
|
|||||||
#include "nvim/api/private/defs.h"
|
#include "nvim/api/private/defs.h"
|
||||||
#include "nvim/api/buffer.h"
|
#include "nvim/api/buffer.h"
|
||||||
#include "nvim/os/channel.h"
|
#include "nvim/os/channel.h"
|
||||||
|
#include "nvim/os/provider.h"
|
||||||
#include "nvim/vim.h"
|
#include "nvim/vim.h"
|
||||||
#include "nvim/buffer.h"
|
#include "nvim/buffer.h"
|
||||||
#include "nvim/window.h"
|
#include "nvim/window.h"
|
||||||
@@ -503,6 +504,22 @@ void vim_unsubscribe(uint64_t channel_id, String event)
|
|||||||
channel_unsubscribe(channel_id, e);
|
channel_unsubscribe(channel_id, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Registers the channel as the provider for `method`. This fails if
|
||||||
|
/// a provider for `method` is already registered.
|
||||||
|
///
|
||||||
|
/// @param channel_id The channel id
|
||||||
|
/// @param method The method name
|
||||||
|
/// @param[out] err Details of an error that may have occurred
|
||||||
|
void vim_register_provider(uint64_t channel_id, String method, Error *err)
|
||||||
|
{
|
||||||
|
char buf[METHOD_MAXLEN];
|
||||||
|
xstrlcpy(buf, method.data, sizeof(buf));
|
||||||
|
|
||||||
|
if (!provider_register(buf, channel_id)) {
|
||||||
|
set_api_error("Provider already registered", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Writes a message to vim output or error buffer. The string is split
|
/// Writes a message to vim output or error buffer. The string is split
|
||||||
/// and flushed after each newline. Incomplete lines are kept for writing
|
/// and flushed after each newline. Incomplete lines are kept for writing
|
||||||
/// later.
|
/// later.
|
||||||
|
@@ -85,6 +85,7 @@
|
|||||||
#include "nvim/api/private/helpers.h"
|
#include "nvim/api/private/helpers.h"
|
||||||
#include "nvim/os/msgpack_rpc_helpers.h"
|
#include "nvim/os/msgpack_rpc_helpers.h"
|
||||||
#include "nvim/os/dl.h"
|
#include "nvim/os/dl.h"
|
||||||
|
#include "nvim/os/provider.h"
|
||||||
|
|
||||||
#define DICT_MAXNEST 100 /* maximum nesting of lists and dicts */
|
#define DICT_MAXNEST 100 /* maximum nesting of lists and dicts */
|
||||||
|
|
||||||
@@ -9807,6 +9808,10 @@ static void f_has(typval_T *argvars, typval_T *rettv)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (n == FALSE && provider_has_feature((char *)name)) {
|
||||||
|
n = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
rettv->vval.v_number = n;
|
rettv->vval.v_number = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -87,6 +87,7 @@
|
|||||||
return rv; \
|
return rv; \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MAP_IMPL(cstr_t, uint64_t, DEFAULT_INITIALIZER)
|
||||||
MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER)
|
MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER)
|
||||||
MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER)
|
MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER)
|
||||||
MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER)
|
MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER)
|
||||||
|
@@ -19,6 +19,7 @@
|
|||||||
U map_##T##_##U##_put(Map(T, U) *map, T key, U value); \
|
U map_##T##_##U##_put(Map(T, U) *map, T key, U value); \
|
||||||
U map_##T##_##U##_del(Map(T, U) *map, T key);
|
U map_##T##_##U##_del(Map(T, U) *map, T key);
|
||||||
|
|
||||||
|
MAP_DECLS(cstr_t, uint64_t)
|
||||||
MAP_DECLS(cstr_t, ptr_t)
|
MAP_DECLS(cstr_t, ptr_t)
|
||||||
MAP_DECLS(ptr_t, ptr_t)
|
MAP_DECLS(ptr_t, ptr_t)
|
||||||
MAP_DECLS(uint64_t, ptr_t)
|
MAP_DECLS(uint64_t, ptr_t)
|
||||||
|
@@ -6,6 +6,7 @@
|
|||||||
#include <msgpack.h>
|
#include <msgpack.h>
|
||||||
|
|
||||||
#include "nvim/api/private/helpers.h"
|
#include "nvim/api/private/helpers.h"
|
||||||
|
#include "nvim/api/vim.h"
|
||||||
#include "nvim/os/channel.h"
|
#include "nvim/os/channel.h"
|
||||||
#include "nvim/os/event.h"
|
#include "nvim/os/event.h"
|
||||||
#include "nvim/os/rstream.h"
|
#include "nvim/os/rstream.h"
|
||||||
@@ -17,9 +18,11 @@
|
|||||||
#include "nvim/os/msgpack_rpc.h"
|
#include "nvim/os/msgpack_rpc.h"
|
||||||
#include "nvim/os/msgpack_rpc_helpers.h"
|
#include "nvim/os/msgpack_rpc_helpers.h"
|
||||||
#include "nvim/vim.h"
|
#include "nvim/vim.h"
|
||||||
|
#include "nvim/ascii.h"
|
||||||
#include "nvim/memory.h"
|
#include "nvim/memory.h"
|
||||||
#include "nvim/message.h"
|
#include "nvim/message.h"
|
||||||
#include "nvim/map.h"
|
#include "nvim/map.h"
|
||||||
|
#include "nvim/log.h"
|
||||||
#include "nvim/lib/kvec.h"
|
#include "nvim/lib/kvec.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -274,7 +277,15 @@ static void job_out(RStream *rstream, void *data, bool eof)
|
|||||||
|
|
||||||
static void job_err(RStream *rstream, void *data, bool eof)
|
static void job_err(RStream *rstream, void *data, bool eof)
|
||||||
{
|
{
|
||||||
// TODO(tarruda): plugin error messages should be sent to the error buffer
|
size_t count;
|
||||||
|
char buf[256];
|
||||||
|
Channel *channel = job_data(data);
|
||||||
|
|
||||||
|
while ((count = rstream_available(rstream))) {
|
||||||
|
size_t read = rstream_read(rstream, buf, sizeof(buf) - 1);
|
||||||
|
buf[read] = NUL;
|
||||||
|
ELOG("Channel %" PRIu64 " stderr: %s", channel->id, buf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void parse_msgpack(RStream *rstream, void *data, bool eof)
|
static void parse_msgpack(RStream *rstream, void *data, bool eof)
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
#include "nvim/os/input.h"
|
#include "nvim/os/input.h"
|
||||||
#include "nvim/os/channel.h"
|
#include "nvim/os/channel.h"
|
||||||
#include "nvim/os/server.h"
|
#include "nvim/os/server.h"
|
||||||
|
#include "nvim/os/provider.h"
|
||||||
#include "nvim/os/signal.h"
|
#include "nvim/os/signal.h"
|
||||||
#include "nvim/os/rstream.h"
|
#include "nvim/os/rstream.h"
|
||||||
#include "nvim/os/job.h"
|
#include "nvim/os/job.h"
|
||||||
@@ -50,6 +51,8 @@ void event_init(void)
|
|||||||
channel_init();
|
channel_init();
|
||||||
// Servers
|
// Servers
|
||||||
server_init();
|
server_init();
|
||||||
|
// Providers
|
||||||
|
provider_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
void event_teardown(void)
|
void event_teardown(void)
|
||||||
|
204
src/nvim/os/provider.c
Normal file
204
src/nvim/os/provider.c
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "nvim/os/provider.h"
|
||||||
|
#include "nvim/memory.h"
|
||||||
|
#include "nvim/api/vim.h"
|
||||||
|
#include "nvim/api/private/helpers.h"
|
||||||
|
#include "nvim/api/private/defs.h"
|
||||||
|
#include "nvim/os/channel.h"
|
||||||
|
#include "nvim/os/shell.h"
|
||||||
|
#include "nvim/os/os.h"
|
||||||
|
#include "nvim/log.h"
|
||||||
|
#include "nvim/map.h"
|
||||||
|
#include "nvim/message.h"
|
||||||
|
#include "nvim/os/msgpack_rpc_helpers.h"
|
||||||
|
|
||||||
|
#define FEATURE_COUNT (sizeof(features) / sizeof(features[0]))
|
||||||
|
|
||||||
|
#define FEATURE(feature_name, provider_bootstrap_command, ...) { \
|
||||||
|
.name = feature_name, \
|
||||||
|
.bootstrap_command = provider_bootstrap_command, \
|
||||||
|
.argv = NULL, \
|
||||||
|
.channel_id = 0, \
|
||||||
|
.methods = (char *[]){__VA_ARGS__, NULL} \
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct feature {
|
||||||
|
char *name, **bootstrap_command, **argv, **methods;
|
||||||
|
size_t name_length;
|
||||||
|
uint64_t channel_id;
|
||||||
|
} features[] = {
|
||||||
|
};
|
||||||
|
|
||||||
|
static Map(cstr_t, uint64_t) *registered_providers = NULL;
|
||||||
|
|
||||||
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
|
# include "os/provider.c.generated.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
void provider_init(void)
|
||||||
|
{
|
||||||
|
registered_providers = map_new(cstr_t, uint64_t)();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool provider_has_feature(char *name)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < FEATURE_COUNT; i++) {
|
||||||
|
struct feature *f = &features[i];
|
||||||
|
if (!STRICMP(name, f->name)) {
|
||||||
|
return f->channel_id || can_execute(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool provider_available(char *method)
|
||||||
|
{
|
||||||
|
return map_has(cstr_t, uint64_t)(registered_providers, method);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool provider_register(char *method, uint64_t channel_id)
|
||||||
|
{
|
||||||
|
if (map_has(cstr_t, uint64_t)(registered_providers, method)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First check if this method is part of a feature, and if so, update
|
||||||
|
// the feature structure with the channel id
|
||||||
|
struct feature *f = get_feature_for(method);
|
||||||
|
if (f) {
|
||||||
|
DLOG("Registering provider for \"%s\" "
|
||||||
|
"which is part of the \"%s\" feature",
|
||||||
|
method,
|
||||||
|
f->name);
|
||||||
|
f->channel_id = channel_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
map_put(cstr_t, uint64_t)(registered_providers, xstrdup(method), channel_id);
|
||||||
|
ILOG("Registered channel %" PRIu64 " as the provider for \"%s\"",
|
||||||
|
channel_id,
|
||||||
|
method);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object provider_call(char *method, Object arg)
|
||||||
|
{
|
||||||
|
uint64_t channel_id = get_provider_for(method);
|
||||||
|
|
||||||
|
if (!channel_id) {
|
||||||
|
char buf[256];
|
||||||
|
snprintf(buf,
|
||||||
|
sizeof(buf),
|
||||||
|
"Provider for \"%s\" is not available",
|
||||||
|
method);
|
||||||
|
report_error(buf);
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool error = false;
|
||||||
|
Object result = NIL;
|
||||||
|
channel_send_call(channel_id, method, arg, &result, &error);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
report_error(result.data.string.data);
|
||||||
|
msgpack_rpc_free_object(result);
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t get_provider_for(char *method)
|
||||||
|
{
|
||||||
|
uint64_t channel_id = map_get(cstr_t, uint64_t)(registered_providers, method);
|
||||||
|
|
||||||
|
if (channel_id) {
|
||||||
|
return channel_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to bootstrap if the method is part of a feature
|
||||||
|
struct feature *f = get_feature_for(method);
|
||||||
|
|
||||||
|
if (!f || !can_execute(f)) {
|
||||||
|
ELOG("Cannot bootstrap provider for \"%s\"", method);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f->channel_id) {
|
||||||
|
ELOG("Already bootstrapped provider for \"%s\"", f->name);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
f->channel_id = channel_from_job(f->argv);
|
||||||
|
|
||||||
|
if (!f->channel_id) {
|
||||||
|
ELOG("The provider for \"%s\" failed to bootstrap", f->name);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return f->channel_id;
|
||||||
|
|
||||||
|
err:
|
||||||
|
// Ensure we won't try to restart the provider
|
||||||
|
f->bootstrap_command = NULL;
|
||||||
|
f->channel_id = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool can_execute(struct feature *f)
|
||||||
|
{
|
||||||
|
if (!f->bootstrap_command) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *cmd = *f->bootstrap_command;
|
||||||
|
|
||||||
|
if (!cmd || !strlen(cmd)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!f->argv) {
|
||||||
|
f->argv = shell_build_argv((uint8_t *)cmd, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return os_can_exe((uint8_t *)f->argv[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void report_error(char *str)
|
||||||
|
{
|
||||||
|
vim_err_write((String) {.data = str, .size = strlen(str)});
|
||||||
|
vim_err_write((String) {.data = "\n", .size = 1});
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool feature_has_method(struct feature *f, char *method)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
char *m;
|
||||||
|
|
||||||
|
for (m = f->methods[i = 0]; m; m = f->methods[++i]) {
|
||||||
|
if (!STRCMP(method, m)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static struct feature *get_feature_for(char *method)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < FEATURE_COUNT; i++) {
|
||||||
|
struct feature *f = &features[i];
|
||||||
|
if (feature_has_method(f, method)) {
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
11
src/nvim/os/provider.h
Normal file
11
src/nvim/os/provider.h
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#ifndef NVIM_OS_PROVIDER_H
|
||||||
|
#define NVIM_OS_PROVIDER_H
|
||||||
|
|
||||||
|
#include "nvim/api/private/defs.h"
|
||||||
|
|
||||||
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
|
# include "os/provider.h.generated.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // NVIM_OS_PROVIDER_H
|
||||||
|
|
Reference in New Issue
Block a user