aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorThiago de Arruda <tpadilha84@gmail.com>2014-06-26 18:10:05 -0300
committerThiago de Arruda <tpadilha84@gmail.com>2014-07-17 11:37:08 -0300
commit887d32e54672cc3957bd2977df92fc3e9de10a52 (patch)
treea092cb21c31fc27e9b3c2ca652aadc73dbcc17c3 /src
parent0b2b1da0e809e529a25530d80a77284d618390cf (diff)
downloadrneovim-887d32e54672cc3957bd2977df92fc3e9de10a52.tar.gz
rneovim-887d32e54672cc3957bd2977df92fc3e9de10a52.tar.bz2
rneovim-887d32e54672cc3957bd2977df92fc3e9de10a52.zip
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.
Diffstat (limited to 'src')
-rw-r--r--src/nvim/CMakeLists.txt1
-rw-r--r--src/nvim/api/vim.c17
-rw-r--r--src/nvim/eval.c5
-rw-r--r--src/nvim/map.c1
-rw-r--r--src/nvim/map.h1
-rw-r--r--src/nvim/os/channel.c13
-rw-r--r--src/nvim/os/event.c3
-rw-r--r--src/nvim/os/provider.c204
-rw-r--r--src/nvim/os/provider.h11
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
+