Skip to content

Instantly share code, notes, and snippets.

@equalsraf equalsraf/notes.md
Last active Feb 6, 2019

Embed
What would you like to do?
Why cant I use clipboard with remote nvim or why is 298 still open?

So we still have some issues with the GUI clipboard.

My hypothesis is that because nvim is caching the has_clipboard static variable then the clipboard will always be considered as invalid and can NEVER be enabled.

Test setup

First I'm going to start nvim in a way that prevents any clipboard for working by changing the PATH.

PATH=/bin nvim

By default nvim needs a provider, often a binary in your system to be able to use the clipboard. If you run 'checkhealth' you should see an error saying that no clipboard tool was available.

To make things slightly more usefuil for later I'm going to run an headless nvim and attach the UI.

PATH=/bin NVIM_LISTEN_ADDRESS=/tmp/test-nvim.sock nvim --headless

and to attach I use

nvim-qt --server /tmp/test-nvim.sock

that is it now to do some work.

Debugging has_clipboard

Because I'm feeling really lazy I'll start debugging with printf statements. nvim will be in headless mode so this should work ok.

diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index df02a5ba1..4dbce2406 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -22772,6 +22772,7 @@ bool eval_has_provider(const char *name)
 
   if (strequal(name, "clipboard")) {
     CHECK_PROVIDER(clipboard);
+       printf("has_clipboard -> %d\n", has_clipboard);
     return has_clipboard;
   } else if (strequal(name, "python3")) {
     CHECK_PROVIDER(python3);

If I apply this patch and run nvim as stated earlier I should see the printf lines in the console. I'm getting an additional error message because my plugins but otherwise we can see that has_clipboard is 0, meaning not clipboard for us

PATH=/bin NVIM_LISTEN_ADDRESS=/tmp/test-nvim.sock bin/nvim --headless
[vim-plug] `git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.has_clipboard -> 0
has_clipboard -> 0
has_clipboard -> 0
has_clipboard -> 0
has_clipboard -> 0
has_clipboard -> 0

You can call :call GuiClipboard() or any other commands but the result will always be 0. You may also notice that line is actually printed a significant number of times - you pretty much only have to move the cursor around.

An even more explicit way to test is to call getreg('+'), that should print a clear error message - there is not clipboard provider.

Removing static

Now lets try removing static from the has_clipboard variable.

-  static int has_clipboard = -1;
+  int has_clipboard = -1;
   static int has_python = -1;
   static int has_python3 = -1;
   static int has_ruby = -1;
 
   if (strequal(name, "clipboard")) {
     CHECK_PROVIDER(clipboard);
+       printf("has_clipboard -> %d\n", has_clipboard);
     return has_clipboard;
   } else if (strequal(name, "python3")) {
     CHECK_PROVIDER(python3);

Now start nvim, attach the UI and call :call GuiClipboard, voila we see the output

PATH=/bin NVIM_LISTEN_ADDRESS=/tmp/test-nvim.sock bin/nvim --headless
[vim-plug] `git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.has_clipboard -> 0
has_clipboard -> 0
has_clipboard -> 0
has_clipboard -> 1
has_clipboard -> 1
has_clipboard -> 1
has_clipboard -> 1
has_clipboard -> 1

and the clipboard work now. So my hypothesis seems correct

Now what?

Obviously removing the static is a terrible idea since this function is executed all the time. That function is used 10 places in the nvim source, to check for all the providers (python, clipboard, etc). We need a way to clear that cache if necessary

diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index df02a5ba1..af66c8803 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -22752,10 +22752,10 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
   return rettv;
 }
 
-bool eval_has_provider(const char *name)
+bool eval_has_provider_cached(const char *name, bool clear_cache)
 {
 #define CHECK_PROVIDER(name) \
-  if (has_##name == -1) { \
+  if (has_##name == -1 || clear_cache) { \
     has_##name = !!find_func((char_u *)"provider#" #name "#Call"); \
     if (!has_##name) { \
       script_autoload("provider#" #name "#Call", \
@@ -22798,6 +22798,18 @@ bool eval_has_provider(const char *name)
   return false;
 }
 
+bool eval_has_provider(const char *name)
+{
+  return eval_has_provider_cached(name, false);
+}
+
+static void f_reloadprovider(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+  const char *const name = tv_get_string(&argvars[0]);
+  bool h = eval_has_provider_cached(name, true);
+  rettv->vval.v_number = h;
+}
+
 /// Writes "<sourcing_name>:<sourcing_lnum>" to `buf[bufsize]`.
 void eval_fmt_source_name_line(char *buf, size_t bufsize)
 {
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 78cac4878..54ec31953 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -229,6 +229,7 @@ return {
     pyxeval={args=1},
     range={args={1, 3}},
     readfile={args={1, 3}},
+    reloadprovider={args=1},
     reltime={args={0, 2}},
     reltimefloat={args=1},
     reltimestr={args=1},

Now this provides us with a little fuction called reloadprovider (the name is not exactly accurate). If you call it after calling GuiClipboard() you will now have a clipboard.

The name is not entirelly accurate because it is the GuiClipboard() function that actually calls runtime to reload the provider, reloadprovider just clears the cached result. Maybe it shoud also call source_runtime()?

Implications

... no idea. reloadprovider() could affect any provider.

In practice:

  • the python provider sets a guard g:loaded_python_provider
  • so does node, python and ruby
  • so does pythonx (script local)

so it seems the only provider that can be reloaded is the clipboard.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.