diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 17 | ||||
-rw-r--r-- | src/nvim/eval.c | 5 | ||||
-rw-r--r-- | src/nvim/map.c | 1 | ||||
-rw-r--r-- | src/nvim/map.h | 1 | ||||
-rw-r--r-- | src/nvim/os/channel.c | 13 | ||||
-rw-r--r-- | src/nvim/os/event.c | 3 | ||||
-rw-r--r-- | src/nvim/os/provider.c | 204 | ||||
-rw-r--r-- | src/nvim/os/provider.h | 11 |
9 files changed, 255 insertions, 1 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index b3d11eeba0..951cd9ded1 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -51,6 +51,7 @@ set(CONV_SRCS os/rstream.c os/signal.c os/users.c + os/provider.c os/uv_helpers.c os/wstream.c os/msgpack_rpc.c diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 9834633813..6c793cbc54 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -10,6 +10,7 @@ #include "nvim/api/private/defs.h" #include "nvim/api/buffer.h" #include "nvim/os/channel.h" +#include "nvim/os/provider.h" #include "nvim/vim.h" #include "nvim/buffer.h" #include "nvim/window.h" @@ -503,6 +504,22 @@ void vim_unsubscribe(uint64_t channel_id, String event) 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 /// and flushed after each newline. Incomplete lines are kept for writing /// later. diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 428f1430b3..a3d07d8c4f 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -85,6 +85,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/os/msgpack_rpc_helpers.h" #include "nvim/os/dl.h" +#include "nvim/os/provider.h" #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; } diff --git a/src/nvim/map.c b/src/nvim/map.c index 46eca8e6f5..2e47e8b249 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -87,6 +87,7 @@ return rv; \ } +MAP_IMPL(cstr_t, uint64_t, DEFAULT_INITIALIZER) MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER) diff --git a/src/nvim/map.h b/src/nvim/map.h index 61f56821b8..73698cba22 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -19,6 +19,7 @@ U map_##T##_##U##_put(Map(T, U) *map, T key, U value); \ 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(ptr_t, ptr_t) MAP_DECLS(uint64_t, ptr_t) diff --git a/src/nvim/os/channel.c b/src/nvim/os/channel.c index efe628098c..c12779e794 100644 --- a/src/nvim/os/channel.c +++ b/src/nvim/os/channel.c @@ -6,6 +6,7 @@ #include <msgpack.h> #include "nvim/api/private/helpers.h" +#include "nvim/api/vim.h" #include "nvim/os/channel.h" #include "nvim/os/event.h" #include "nvim/os/rstream.h" @@ -17,9 +18,11 @@ #include "nvim/os/msgpack_rpc.h" #include "nvim/os/msgpack_rpc_helpers.h" #include "nvim/vim.h" +#include "nvim/ascii.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/map.h" +#include "nvim/log.h" #include "nvim/lib/kvec.h" 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) { - // 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) diff --git a/src/nvim/os/event.c b/src/nvim/os/event.c index 4e091716b2..0528339865 100644 --- a/src/nvim/os/event.c +++ b/src/nvim/os/event.c @@ -9,6 +9,7 @@ #include "nvim/os/input.h" #include "nvim/os/channel.h" #include "nvim/os/server.h" +#include "nvim/os/provider.h" #include "nvim/os/signal.h" #include "nvim/os/rstream.h" #include "nvim/os/job.h" @@ -50,6 +51,8 @@ void event_init(void) channel_init(); // Servers server_init(); + // Providers + provider_init(); } void event_teardown(void) diff --git a/src/nvim/os/provider.c b/src/nvim/os/provider.c new file mode 100644 index 0000000000..967314eee4 --- /dev/null +++ b/src/nvim/os/provider.c @@ -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; +} diff --git a/src/nvim/os/provider.h b/src/nvim/os/provider.h new file mode 100644 index 0000000000..c6f12e02dd --- /dev/null +++ b/src/nvim/os/provider.h @@ -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 + |