Commit: patch 9.1.1972: No way to access the clipboard without X11/Wayland

4 views
Skip to first unread message

Christian Brabandt

unread,
Dec 11, 2025, 3:15:52 PM (14 hours ago) Dec 11
to vim...@googlegroups.com
patch 9.1.1972: No way to access the clipboard without X11/Wayland

Commit: https://github.com/vim/vim/commit/fcd3958dcb62011e865204f9c82da648f3635eae
Author: Foxe Chen <chen...@gmail.com>
Date: Thu Dec 11 21:11:03 2025 +0100

patch 9.1.1972: No way to access the clipboard without X11/Wayland

Problem: No way to access the clipboard without X11/Wayland.
Solution: Add the clipboard provider feature (Foxe Chen).

closes: #18781

Signed-off-by: Foxe Chen <chen...@gmail.com>
Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 4f1b0b42f..ebb6d5ba7 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1,4 +1,4 @@
-*eval.txt* For Vim version 9.1. Last change: 2025 Dec 01
+*eval.txt* For Vim version 9.1. Last change: 2025 Dec 11


VIM REFERENCE MANUAL by Bram Moolenaar
@@ -38,6 +38,7 @@ a remark is given.
12. The sandbox |eval-sandbox|
13. Textlock |textlock|
14. Vim script library |vim-script-library|
+15. Clipboard providers |clipboard-providers|

Testing support is documented in |testing.txt|.
Profiling is documented at |profiling|.
@@ -2247,7 +2248,14 @@ v:clipmethod The current method of accessing the clipboard that is being
x11 X11 selections are being used.
none The above methods are unavailable or
cannot be used.
- See 'clipmethod' for more details.
+ If it is set to a value not in the above list, then a
+ clipboard provider with the given name is being used for the
+ clipboard functionality. See 'clipmethod' for more details.
+
+ *v:clipproviders*
+v:clipproviders
+ A dictionary containing clipboard providers, see
+ |clipboard-providers| for more information.

*v:cmdarg* *cmdarg-variable*
v:cmdarg This variable is used for two purposes:
@@ -5266,5 +5274,111 @@ Usage: >vim
:call dist#vim9#Launch(<args>)
:Launch <app> <args>.
<
-
+==============================================================================
+15. Clipboard providers *clipboard-providers*
+
+The clipboard provider feature allows the "+" |quoteplus| and "*" |quotestar|
+registers to be overridden by custom Vimscript functions. There can be
+multiple providers, and Vim chooses which one to use based on 'clipmethod'.
+Despite the name, it does not use the 'clipboard' option and should be treated
+separate from the clipboard functionality. It essentially overrides the
+existing behaviour of the clipboard registers.
+
+ *clipboard-providers-no-clipboard*
+If the |+clipboard| feature is not enabled, then the "+" and "*" registers
+will not be enabled/available unless |v:clipmethod| is set to a provider. If
+it is set to a provider, then the clipboard registers will be exposed despite
+not having the |+clipboard| feature.
+
+ *clipboard-providers-plus*
+If on a platform that only has the "*" register, then the "+" register will
+only be available when |v:clipmethod| is set to a provider. If you want to
+check if the "+" is available for use, it can be checked with: >
+ if has('unnamedplus')
+<
+ *clipboard-providers-clipmethod*
+To integrate the providers with Vim's clipboard functionality, the
+'clipmethod' option is used on all platforms. The names of clipboard
+providers should be put inside the option, and if Vim chooses it, then it
+overrides the "+" and "*" registers. Note that the "+" and "*" will not be
+saved in the viminfo at all.
+
+ *clipboard-providers-define*
+To define a clipboard provider, the |v:clipproviders| vim variable is used. It
+is a |dict| where each key is the clipboard provider name, and the value is
+another |dict| declaring the "available", "copy", and "paste" callbacks: >vim
+ let v:clipproviders["myprovider"] = {
+ \ "available": function("Available"),
+ \ "paste": {
+ \ "+": function("Paste"),
+ \ "*": function("Paste")
+ \ },
+ \ "copy": {
+ \ "+": function("Copy"),
+ \ "*": function("Copy")
+ \ }
+ \ }
+ set clipmethod^=myprovider
+<
+Each callback can either be a name of a function in a string, a |Funcref|, or
+a |lambda| expression.
+
+With the exception of the "available" callback if a callback is not provided,
+Vim will not invoke anything, and this is not an error.
+
+ *clipboard-providers-textlock*
+In both the "paste" and "copy" callbacks, it is not allowed to change the
+buffer text, see |textlock|.
+
+ *clipboard-providers-available*
+The "available" callback is optional, does not take any arguments and should
+return a |boolean| or non-zero number, which tells Vim if it is available
+for use. If it is not, then Vim skips over it and tries the next 'clipmethod'
+value. If the "available" callback is not provided, Vim assumes the provider
+is always available for use (true).
+
+ *clipboard-providers-paste*
+The "paste" callback takes the following arguments in the following order:
+ 1. Name of the register being accessed, either "+" or "*".
+
+It should return a |list| or |tuple| containing the following elements in
+order:
+ 1. Register type (and optional width) conforming to |setreg()|
+ 2. A |list| of strings to return to Vim, each representing a line.
+
+ *clipboard-providers-copy*
+The "copy" callback returns nothing and takes the following arguments in the
+following order:
+ 1. Name of the register being accessed, either "+" or "*".
+ 2. Register type conforming to |getregtype()|
+ 3. List of strings to use, each representing a line.
+
+Below is a sample script that makes use of the clipboard provider feature: >vim
+ func Available()
+ return v:true
+ endfunc
+
+ func Copy(reg, type, str)
+ echom "Register: " .. a:reg
+ echom "Register type: " .. a:type
+ echom "Contents: " .. string(a:str)
+ endfunc
+
+ func Paste(reg)
+ return ("b40", ["this", "is", "the", a:reg, "register!"])
+ endfunc
+
+ let v:clipproviders["test"] = {
+ \ "available": function("Available"),
+ \ "copy": {
+ \ "+": function("Copy"),
+ \ "*": function("Copy")
+ \ },
+ \ "paste": {
+ \ "+": function("Paste"),
+ \ "*": function("Paste")
+ \ }
+ \ }
+ set clipmethod^=test
+<
vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 001786456..a67864475 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -1,4 +1,4 @@
-*options.txt* For Vim version 9.1. Last change: 2025 Dec 09
+*options.txt* For Vim version 9.1. Last change: 2025 Dec 11


VIM REFERENCE MANUAL by Bram Moolenaar
@@ -1912,18 +1912,22 @@ A jump table for the options with a short description can be found at |Q_op|.
for VMS: "x11",
otherwise: "")
global
- {only when the |+xterm_clipboard| or
- |+wayland_clipboard| features are included}
- Specifies which method of accessing the system clipboard is used,
- depending on which method works first or is available. Supported
- methods are:
+ {only when the |+xterm_clipboard|, |+wayland_clipboard|,
+ or |+eval| features are included}
+ Specifies which method of accessing the system clipboard (or clipboard
+ provider) is used. Methods are tried in the order given; the first
+ working method is used. Supported methods are:
wayland Wayland selections
x11 X11 selections
+ <name> Use a clipboard provider with the given name

Note: This option is ignored when either the GUI is running or if Vim
is run on a system without Wayland or X11 support, such as Windows or
- macOS. The GUI or system way of accessing the clipboard is always
- used instead.
+ macOS. The GUI or system way of accessing the clipboard is used
+ instead, meaning |v:clipmethod| will be set to "none". The
+ exception to this is the |clipboard-providers| feature, in which if
+ a clipboard provider is being used, then it will override the existing
+ clipboard functionality.

The option value is a list of comma separated items. The list is
parsed left to right in order, and the first method that Vim
diff --git a/runtime/doc/tags b/runtime/doc/tags
index bc900fdf2..79c3a6fe0 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -1410,6 +1410,7 @@ $quote eval.txt /*$quote*
+cindent various.txt /*+cindent*
+clientserver various.txt /*+clientserver*
+clipboard various.txt /*+clipboard*
++clipboard_provider various.txt /*+clipboard_provider*
+clipboard_working various.txt /*+clipboard_working*
+cmd editing.txt /*+cmd*
+cmdline_compl various.txt /*+cmdline_compl*
@@ -6692,6 +6693,15 @@ clipboard-autoselectml options.txt /*clipboard-autoselectml*
clipboard-autoselectplus options.txt /*clipboard-autoselectplus*
clipboard-exclude options.txt /*clipboard-exclude*
clipboard-html options.txt /*clipboard-html*
+clipboard-providers eval.txt /*clipboard-providers*
+clipboard-providers-available eval.txt /*clipboard-providers-available*
+clipboard-providers-clipmethod eval.txt /*clipboard-providers-clipmethod*
+clipboard-providers-copy eval.txt /*clipboard-providers-copy*
+clipboard-providers-define eval.txt /*clipboard-providers-define*
+clipboard-providers-no-clipboard eval.txt /*clipboard-providers-no-clipboard*
+clipboard-providers-paste eval.txt /*clipboard-providers-paste*
+clipboard-providers-plus eval.txt /*clipboard-providers-plus*
+clipboard-providers-textlock eval.txt /*clipboard-providers-textlock*
clipboard-unnamed options.txt /*clipboard-unnamed*
clipboard-unnamedplus options.txt /*clipboard-unnamedplus*
clojure-indent indent.txt /*clojure-indent*
@@ -11258,6 +11268,7 @@ v:char eval.txt /*v:char*
v:charconvert_from eval.txt /*v:charconvert_from*
v:charconvert_to eval.txt /*v:charconvert_to*
v:clipmethod eval.txt /*v:clipmethod*
+v:clipproviders eval.txt /*v:clipproviders*
v:cmdarg eval.txt /*v:cmdarg*
v:cmdbang eval.txt /*v:cmdbang*
v:collate eval.txt /*v:collate*
diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt
index 42b0b6657..89fce0d93 100644
--- a/runtime/doc/various.txt
+++ b/runtime/doc/various.txt
@@ -1,4 +1,4 @@
-*various.txt* For Vim version 9.1. Last change: 2025 Nov 09
+*various.txt* For Vim version 9.1. Last change: 2025 Dec 11


VIM REFERENCE MANUAL by Bram Moolenaar
@@ -379,6 +379,7 @@ T *+cindent* 'cindent', C indenting; Always enabled
N *+clientserver* Unix and Win32: Remote invocation |clientserver|
*+clipboard* |clipboard| support compiled-in
*+clipboard_working* |clipboard| support compiled-in and working
+ *+clipboard_provider* |clipboard-providers| support compiled-in
T *+cmdline_compl* command line completion |cmdline-completion|
T *+cmdline_hist* command line history |cmdline-history|
T *+cmdline_info* 'showcmd' and 'ruler'; Always enabled since
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index a736370f0..ff6de1dc5 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -1,4 +1,4 @@
-*version9.txt* For Vim version 9.1. Last change: 2025 Dec 10
+*version9.txt* For Vim version 9.1. Last change: 2025 Dec 11


VIM REFERENCE MANUAL by Bram Moolenaar
@@ -41653,6 +41653,8 @@ Other new features ~

- |items()| function now supports Blob.

+- The clipboard provider feature has been added |clipboard-providers|.
+
*changed-9.2*
Changed~
-------
@@ -41907,6 +41909,8 @@ Options: ~

Vim Variables: ~
|v:clipmethod| The current 'clipmethod'.
+|v:clipproviders| A dictionary containing clipboard providers
+ configuration |clipboard-providers|.
|v:stacktrace| The most recent caught exception.
|v:t_enumvalue| Value of |enumvalue|.
|v:t_enum| Value of |enum| type.
diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim
index 050880215..17161fad4 100644
--- a/runtime/syntax/vim.vim
+++ b/runtime/syntax/vim.vim
@@ -2,7 +2,7 @@
" Language: Vim script
" Maintainer: Hirohito Higashi <h.east.727 ATMARK gmail.com>
" Doug Kearns <dougk...@gmail.com>
-" Last Change: 2025 Dec 04
+" Last Change: 2025 Dec 11
" Former Maintainer: Charles E. Campbell

" DO NOT CHANGE DIRECTLY.
@@ -166,7 +166,7 @@ syn keyword vimFuncName contained win_findbuf win_getid win_gettype win_gotoid w
" Predefined variable names {{{2
" GEN_SYN_VIM: vimVarName, START_STR='syn keyword vimVimVarName contained', END_STR=''
syn keyword vimVimVarName contained count count1 prevcount errmsg warningmsg statusmsg shell_error this_session version lnum termresponse fname lang lc_time ctype charconvert_from charconvert_to fname_in fname_out fname_new fname_diff cmdarg foldstart foldend folddashes foldlevel progname servername dying exception throwpoint register cmdbang insertmode val key profiling fcs_reason fcs_choice beval_bufnr beval_winnr beval_winid beval_lnum beval_col beval_text scrollstart swapname swapchoice swapcommand char mouse_win mouse_winid mouse_lnum mouse_col operator searchforward hlsearch oldfiles windowid progpath completed_item option_new option_old option_oldlocal option_oldglobal option_command option_type errors false true none null numbermax numbermin numbersize
-syn keyword vimVimVarName contained vim_did_enter testing t_number t_string t_func t_list t_dict t_float t_bool t_none t_job t_channel t_blob t_class t_object termrfgresp termrbgresp termu7resp termstyleresp termblinkresp event versionlong echospace argv collate exiting colornames sizeofint sizeoflong sizeofpointer maxcol python3_version t_typealias t_enum t_enumvalue stacktrace t_tuple wayland_display clipmethod termda1 termosc vim_did_init
+syn keyword vimVimVarName contained vim_did_enter testing t_number t_string t_func t_list t_dict t_float t_bool t_none t_job t_channel t_blob t_class t_object termrfgresp termrbgresp termu7resp termstyleresp termblinkresp event versionlong echospace argv collate exiting colornames sizeofint sizeoflong sizeofpointer maxcol python3_version t_typealias t_enum t_enumvalue stacktrace t_tuple wayland_display clipmethod termda1 termosc vim_did_init clipproviders

"--- syntax here and above generated by runtime/syntax/generator/gen_syntax_vim.vim ---

diff --git a/src/clipboard.c b/src/clipboard.c
index af3fee308..51f705430 100644
--- a/src/clipboard.c
+++ b/src/clipboard.c
@@ -8,7 +8,9 @@
*/

/*
- * clipboard.c: Functions to handle the clipboard
+ * clipboard.c: Functions to handle the clipboard. Additionally contains the
+ * clipboard provider code, which is separate from the main
+ * clipboard code.
*/

#include "vim.h"
@@ -29,6 +31,11 @@
// versions of these for the 'clipboard' selection, as Visual mode has no use
// for them.

+
+#ifdef FEAT_CLIPBOARD_PROVIDER
+static int clip_provider_is_available(char_u *provider);
+#endif
+
#if defined(FEAT_CLIPBOARD)

#if defined(FEAT_WAYLAND_CLIPBOARD)
@@ -3415,6 +3422,9 @@ clip_wl_owner_exists(Clipboard_T *cbd)

#endif // FEAT_WAYLAND_CLIPBOARD

+#endif // FEAT_CLIPBOARD
+
+#ifdef HAVE_CLIPMETHOD

/*
* Returns the first method for accessing the clipboard that is available/works,
@@ -3473,8 +3483,30 @@ get_clipmethod(char_u *str)
}
else
{
- ret = CLIPMETHOD_FAIL;
- goto exit;
+#ifdef FEAT_EVAL
+ // Check if name matches a clipboard provider
+ int r = clip_provider_is_available(buf);
+
+ if (r == 1)
+ {
+ method = CLIPMETHOD_PROVIDER;
+ if (ret == CLIPMETHOD_FAIL)
+ {
+ vim_free(clip_provider);
+ clip_provider = vim_strsave(buf);
+ if (clip_provider == NULL)
+ goto fail;
+ }
+ }
+ else if (r == -1)
+#endif
+ {
+#ifdef FEAT_EVAL
+fail:
+#endif
+ ret = CLIPMETHOD_FAIL;
+ goto exit;
+ }
}

// Keep on going in order to catch errors
@@ -3494,17 +3526,21 @@ exit:
/*
* Returns name of clipmethod in a statically allocated string.
*/
- static char *
+ static char_u *
clipmethod_to_str(clipmethod_T method)
{
switch(method)
{
case CLIPMETHOD_WAYLAND:
- return "wayland";
+ return (char_u *)"wayland";
case CLIPMETHOD_X11:
- return "x11";
+ return (char_u *)"x11";
+ case CLIPMETHOD_PROVIDER:
+#ifdef FEAT_EVAL
+ return clip_provider;
+#endif
default:
- return "none";
+ return (char_u *)"none";
}
}

@@ -3522,9 +3558,10 @@ choose_clipmethod(void)

// If GUI is running or we are not on a system with Wayland or X11, then always
// return CLIPMETHOD_NONE. System or GUI clipboard handling always overrides.
+// This is unless a provider is being used.
#if defined(FEAT_XCLIPBOARD) || defined(FEAT_WAYLAND_CLIPBOARD)
# if defined(FEAT_GUI)
- if (gui.in_use)
+ if (method != CLIPMETHOD_PROVIDER && gui.in_use)
{
# ifdef FEAT_WAYLAND
// We only interact with Wayland for the clipboard, we can just deinit
@@ -3538,19 +3575,26 @@ choose_clipmethod(void)
# endif
#else
// If on a system like windows or macos, then clipmethod is irrelevant, we
- // use their way of accessing the clipboard.
- method = CLIPMETHOD_NONE;
- goto exit;
+ // use their way of accessing the clipboard. This is unless we are using the
+ // clipboard provider
+#ifdef FEAT_CLIPBOARD_PROVIDER
+ if (method != CLIPMETHOD_PROVIDER)
+#endif
+ {
+ method = CLIPMETHOD_NONE;
+ goto exit;
+ }
#endif

+#ifdef FEAT_CLIPBOARD
// Deinitialize clipboard if there is no way to access clipboard
if (method == CLIPMETHOD_NONE)
clip_init(FALSE);
// If we have a clipmethod that works now, then initialize clipboard
else if (clipmethod == CLIPMETHOD_NONE && method != CLIPMETHOD_NONE)
{
- clip_init(TRUE);
- did_warn_clipboard = false;
+ clip_init(TRUE);
+ did_warn_clipboard = false;
}
// Disown clipboard if we are switching to a new method
else if (clipmethod != CLIPMETHOD_NONE && method != clipmethod)
@@ -3572,6 +3616,7 @@ lose_sel_exit:
did_warn_clipboard = false;
}
}
+#endif // FEAT_CLIPBOARD

#if !defined(FEAT_XCLIPBOARD) && !defined(FEAT_WAYLAND_CLIPBOARD)
exit:
@@ -3603,4 +3648,403 @@ ex_clipreset(exarg_T *eap UNUSED)
clipmethod_to_str(clipmethod));
}

-#endif // FEAT_CLIPBOARD
+#endif // HAVE_CLIPMETHOD
+
+#ifdef FEAT_CLIPBOARD_PROVIDER
+
+/*
+ * Check if a clipboard provider with given name is available. Returns 1 if available,
+ * 0 if not available, and -1 on error
+ */
+ static int
+clip_provider_is_available(char_u *provider)
+{
+ dict_T *providers = get_vim_var_dict(VV_CLIPPROVIDERS);
+ typval_T provider_tv = {0};
+ callback_T callback = {0};
+ typval_T rettv = {0};
+ typval_T func_tv = {0};
+ int res = 0;
+
+ if (dict_get_tv(providers, (char *)provider, &provider_tv) == FAIL
+ || provider_tv.v_type != VAR_DICT)
+ // clipboard provider not defined
+ return -1;
+
+ if (dict_get_tv(provider_tv.vval.v_dict, "available", &func_tv) == FAIL)
+ {
+ clear_tv(&provider_tv);
+ // If "available" function not specified assume always TRUE
+ return 1;
+ }
+
+ if ((callback = get_callback(&func_tv)).cb_name == NULL)
+ goto fail;
+
+ if (call_callback(&callback, -1, &rettv, 0, NULL) == FAIL ||
+ (rettv.v_type != VAR_BOOL && rettv.v_type != VAR_NUMBER))
+ goto fail;
+
+ if (rettv.vval.v_number)
+ res = 1;
+
+ if (FALSE)
+fail:
+ res = -1;
+
+ free_callback(&callback);
+ clear_tv(&func_tv);
+ clear_tv(&rettv);
+ clear_tv(&provider_tv);
+
+ return res;
+}
+
+/*
+ * Get the specified callback "function" from the provider dictionary for
+ * register "reg".
+ */
+ static int
+clip_provider_get_callback(
+ char_u *reg,
+ char_u *provider,
+ char_u *function,
+ callback_T *callback)
+{
+ dict_T *providers = get_vim_var_dict(VV_CLIPPROVIDERS);
+ typval_T provider_tv;
+ typval_T action_tv;
+ typval_T func_tv;
+ callback_T cb;
+
+ if (dict_get_tv(providers, (char *)provider, &provider_tv) == FAIL)
+ return FAIL;
+ else if (provider_tv.v_type != VAR_DICT)
+ {
+ clear_tv(&provider_tv);
+ return FAIL;
+ }
+ else if (dict_get_tv(
+ provider_tv.vval.v_dict,
+ (char *)function,
+ &action_tv) == FAIL)
+ {
+ clear_tv(&provider_tv);
+ return FAIL;
+ }
+ else if (action_tv.v_type != VAR_DICT)
+ {
+ clear_tv(&provider_tv);
+ clear_tv(&action_tv);
+ return FAIL;
+ }
+ else if (dict_get_tv(action_tv.vval.v_dict, (char *)reg, &func_tv) == FAIL)
+ {
+ clear_tv(&provider_tv);
+ clear_tv(&action_tv);
+ return FAIL;
+ }
+ else if ((cb = get_callback(&func_tv)).cb_name == NULL)
+ {
+ clear_tv(&provider_tv);
+ clear_tv(&action_tv);
+ clear_tv(&func_tv);
+ return FAIL;
+ }
+ clear_tv(&provider_tv);
+ clear_tv(&action_tv);
+
+ // func_tv owns the function name, so we must make a copy for the callback
+ set_callback(callback, &cb);
+ free_callback(&cb);
+ clear_tv(&func_tv);
+ return OK;
+}
+
+ static void
+clip_provider_copy(char_u *reg, char_u *provider)
+{
+ callback_T callback;
+ typval_T rettv;
+ typval_T argvars[4];
+ yankreg_T *y_ptr;
+ char_u type[2 + NUMBUFLEN] = {0};
+ list_T *list = NULL;
+
+ if (clip_provider_get_callback(
+ reg,
+ provider,
+ (char_u *)"copy",
+ &callback) == FAIL)
+ return;
+
+ // Convert register type into a string
+ if (*reg == '+')
+ y_ptr = get_y_register(REAL_PLUS_REGISTER);
+ else
+ y_ptr = get_y_register(STAR_REGISTER);
+
+ switch (y_ptr->y_type)
+ {
+ case MCHAR:
+ type[0] = 'v';
+ break;
+ case MLINE:
+ type[0] = 'V';
+ break;
+ case MBLOCK:
+ sprintf((char *)type, "%c%d", Ctrl_V, y_ptr->y_width + 1);
+ break;
+ default:
+ type[0] = 0;
+ break;
+ }
+
+ argvars[0].v_type = VAR_STRING;
+ argvars[0].vval.v_string = reg;
+
+ argvars[1].v_type = VAR_STRING;
+ argvars[1].vval.v_string = type;
+
+ // Get register contents by creating a list of lines
+ list = list_alloc();
+
+ if (list == NULL)
+ {
+ free_callback(&callback);
+ return;
+ }
+
+ for (int i = 0; i < y_ptr->y_size; i++)
+ if (list_append_string(list, y_ptr->y_array[i].string, -1) == FAIL)
+ {
+ free_callback(&callback);
+ list_unref(list);
+ return;
+ }
+
+ list->lv_refcount++;
+
+ argvars[2].v_type = VAR_LIST;
+ argvars[2].v_lock = VAR_FIXED;
+ argvars[2].vval.v_list = list;
+
+ argvars[3].v_type = VAR_UNKNOWN;
+
+ textlock++;
+ call_callback(&callback, -1, &rettv, 3, argvars);
+ clear_tv(&rettv);
+ textlock--;
+
+ free_callback(&callback);
+ list_unref(list);
+}
+
+ static void
+clip_provider_paste(char_u *reg, char_u *provider)
+{
+ callback_T callback;
+ typval_T argvars[2];
+ typval_T rettv;
+ int ret;
+ char_u *reg_type;
+ list_T *lines;
+
+ if (clip_provider_get_callback(
+ reg,
+ provider,
+ (char_u *)"paste",
+ &callback) == FAIL)
+ return;
+
+ argvars[0].v_type = VAR_STRING;
+ argvars[0].vval.v_string = reg;
+
+ argvars[1].v_type = VAR_UNKNOWN;
+
+ textlock++;
+ ret = call_callback(&callback, -1, &rettv, 1, argvars);
+ textlock--;
+
+ if (ret == FAIL)
+ goto exit;
+ else if (rettv.v_type == VAR_TUPLE
+ && TUPLE_LEN(rettv.vval.v_tuple) == 2
+ && TUPLE_ITEM(rettv.vval.v_tuple, 0)->v_type == VAR_STRING
+ && TUPLE_ITEM(rettv.vval.v_tuple, 1)->v_type == VAR_LIST)
+ {
+ reg_type = TUPLE_ITEM(rettv.vval.v_tuple, 0)->vval.v_string;
+ lines = TUPLE_ITEM(rettv.vval.v_tuple, 1)->vval.v_list;
+ }
+ else if (rettv.v_type == VAR_LIST
+ && rettv.vval.v_list->lv_len == 2
+ && rettv.vval.v_list->lv_first->li_tv.v_type == VAR_STRING
+ && rettv.vval.v_list->lv_first->li_next->li_tv.v_type == VAR_LIST)
+ {
+ reg_type = rettv.vval.v_list->lv_first->li_tv.vval.v_string;
+ lines = rettv.vval.v_list->lv_first->li_next->li_tv.vval.v_list;
+ }
+ else
+ goto exit;
+
+ {
+ char_u yank_type = MAUTO;
+ long block_len = -1;
+ yankreg_T *y_ptr, *cur_y_ptr;
+ char_u **lstval;
+ char_u **allocval;
+ char_u buf[NUMBUFLEN];
+ char_u **curval;
+ char_u **curallocval;
+ char_u *strval;
+ listitem_T *li;
+ int len;
+
+ // If the list is NULL handle like an empty list.
+ len = lines == NULL ? 0 : lines->lv_len;
+
+ // First half: use for pointers to result lines; second half: use for
+ // pointers to allocated copies.
+ lstval = ALLOC_MULT(char_u *, (len + 1) * 2);
+ if (lstval == NULL)
+ goto exit;
+ curval = lstval;
+ allocval = lstval + len + 2;
+ curallocval = allocval;
+
+ if (lines != NULL)
+ {
+ CHECK_LIST_MATERIALIZE(lines);
+ FOR_ALL_LIST_ITEMS(lines, li)
+ {
+ strval = tv_get_string_buf_chk(&li->li_tv, buf);
+ if (strval == NULL)
+ goto free_lstval;
+ if (strval == buf)
+ {
+ // Need to make a copy, next tv_get_string_buf_chk() will
+ // overwrite the string.
+ strval = vim_strsave(buf);
+ if (strval == NULL)
+ goto free_lstval;
+ *curallocval++ = strval;
+ }
+ *curval++ = strval;
+ }
+ }
+ *curval++ = NULL;
+
+ if (STRLEN(reg_type) <= 0
+ || get_yank_type(&reg_type, &yank_type, &block_len) == FAIL)
+ {
+ emsg(e_invalid_argument);
+ goto free_lstval;
+ }
+
+ if (*reg == '+')
+ y_ptr = get_y_register(REAL_PLUS_REGISTER);
+ else
+ y_ptr = get_y_register(STAR_REGISTER);
+
+ // Free previous register contents
+ cur_y_ptr = get_y_current();
+ set_y_current(y_ptr);
+
+ free_yank_all();
+ get_y_current()->y_size = 0;
+
+ set_y_current(cur_y_ptr);
+
+ str_to_reg(y_ptr,
+ yank_type,
+ (char_u *)lstval,
+ -1,
+ block_len,
+ TRUE);
+
+free_lstval:
+ while (curallocval > allocval)
+ vim_free(*--curallocval);
+ vim_free(lstval);
+ }
+
+exit:
+ free_callback(&callback);
+ clear_tv(&rettv);
+}
+
+// Used to stop calling the provider callback every time there is an update.
+// This prevents unnecessary calls when accessing the provider often in an
+// interval.
+//
+// If -1 then allow provider callback to be called then set to zero. Default
+// value (is allowed) is -2.
+static int star_pause_count = -2, plus_pause_count = -2;
+
+ void
+call_clip_provider_request(int reg)
+{
+ if (clipmethod != CLIPMETHOD_PROVIDER)
+ return;
+
+ if (reg == '+' && plus_pause_count < 0)
+ {
+ if (plus_pause_count == -1)
+ plus_pause_count = 1;
+ clip_provider_paste((char_u *)"+", clip_provider);
+ }
+ else if (reg == '*' && star_pause_count < 0)
+ {
+ if (star_pause_count == -1)
+ star_pause_count = 1;
+ clip_provider_paste((char_u *)"*", clip_provider);
+ }
+ else
+ return;
+}
+
+ void
+call_clip_provider_set(int reg)
+{
+ if (clipmethod != CLIPMETHOD_PROVIDER)
+ return;
+
+ if (reg == '+' && plus_pause_count < 0)
+ {
+ if (plus_pause_count == -1)
+ plus_pause_count = 1;
+ clip_provider_copy((char_u *)"+", clip_provider);
+ }
+ else if (reg == '*' && star_pause_count < 0)
+ {
+ if (star_pause_count == -1)
+ star_pause_count = 1;
+ clip_provider_copy((char_u *)"*", clip_provider);
+ }
+}
+
+/*
+ * Makes it so that the next provider call is only done once any calls after are
+ * ignored, until dec_clip_provider is called the same number of times after
+ * again. Note that this is per clipboard register ("+", "*")
+ */
+ void
+inc_clip_provider(void)
+{
+ plus_pause_count = plus_pause_count == -2 ? -1 : plus_pause_count + 1;
+ star_pause_count = star_pause_count == -2 ? -1 : star_pause_count + 1;
+}
+
+ void
+dec_clip_provider(void)
+{
+ plus_pause_count = plus_pause_count == -1 ? -1 : plus_pause_count - 1;
+ star_pause_count = star_pause_count == -1 ? -1 : star_pause_count - 1;
+
+ if (plus_pause_count == 0 || plus_pause_count == -1)
+ plus_pause_count = -2;
+ if (star_pause_count == 0 || star_pause_count == -1)
+ star_pause_count = -2;
+}
+
+#endif // FEAT_CLIPBOARD_PROVIDER
diff --git a/src/evalfunc.c b/src/evalfunc.c
index 222fa4017..49b7c72b2 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -6866,6 +6866,13 @@ f_has(typval_T *argvars, typval_T *rettv)
1
#else
0
+#endif
+ },
+ {"clipboard_provider",
+#ifdef FEAT_CLIPBOARD_PROVIDER
+ 1
+#else
+ 0
#endif
},
{"cmdline_compl", 1},
@@ -7563,14 +7570,6 @@ f_has(typval_T *argvars, typval_T *rettv)
1
#else
0
-#endif
- },
- {"unnamedplus",
-#if defined(FEAT_CLIPBOARD) && (defined(FEAT_X11) \
- || defined(FEAT_WAYLAND_CLIPBOARD))
- 1
-#else
- 0
#endif
},
{"user-commands", 1}, // was accidentally included in 5.4
@@ -7918,7 +7917,27 @@ f_has(typval_T *argvars, typval_T *rettv)
{
x = TRUE;
#ifdef FEAT_CLIPBOARD
- n = clip_star.available;
+ n = clipmethod == CLIPMETHOD_PROVIDER ? TRUE : clip_star.available;
+#endif
+ }
+ else if (STRICMP(name, "unnamedplus") == 0)
+ {
+ x = TRUE;
+#ifdef FEAT_CLIPBOARD
+ // The + register is available when clipmethod is set to a provider,
+ // but becomes unavailable if on a platform that doesn't support it
+ // and clipmethod is "none".
+ // (Windows, MacOS).
+# if defined(FEAT_X11) || defined(FEAT_WAYLAND_CLIPBOARD)
+ n = TRUE;
+# elif defined(FEAT_EVAL)
+ if (clipmethod == CLIPMETHOD_PROVIDER)
+ n = TRUE;
+ else
+ n = FALSE;
+# else
+ n = FALSE;
+# endif
#endif
}
}
@@ -11493,7 +11512,7 @@ f_setpos(typval_T *argvars, typval_T *rettv)
/*
* Translate a register type string to the yank type and block length
*/
- static int
+ int
get_yank_type(char_u **pp, char_u *yank_type, long *block_len)
{
char_u *stropt = *pp;
diff --git a/src/evalvars.c b/src/evalvars.c
index ca71562d4..af14c8b0d 100644
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -168,6 +168,7 @@ static struct vimvar
{VV_NAME("termda1", VAR_STRING), NULL, VV_RO},
{VV_NAME("termosc", VAR_STRING), NULL, VV_RO},
{VV_NAME("vim_did_init", VAR_NUMBER), NULL, VV_RO},
+ {VV_NAME("clipproviders", VAR_DICT), NULL, VV_RO},
};

// shorthand
diff --git a/src/ex_cmds.c b/src/ex_cmds.c
index 8e861d5a2..e8343cae4 100644
--- a/src/ex_cmds.c
+++ b/src/ex_cmds.c
@@ -5489,12 +5489,18 @@ ex_global(exarg_T *eap)
}
else
{
+#ifdef FEAT_CLIPBOARD_PROVIDER
+ inc_clip_provider();
+#endif
#ifdef FEAT_CLIPBOARD
start_global_changes();
#endif
global_exe(cmd);
#ifdef FEAT_CLIPBOARD
end_global_changes();
+#endif
+#ifdef FEAT_CLIPBOARD_PROVIDER
+ dec_clip_provider();
#endif
}

diff --git a/src/ex_cmds2.c b/src/ex_cmds2.c
index 6b8fd2627..6e9749f5d 100644
--- a/src/ex_cmds2.c
+++ b/src/ex_cmds2.c
@@ -536,6 +536,9 @@ ex_listdo(exarg_T *eap)
#ifdef FEAT_CLIPBOARD
start_global_changes();
#endif
+#ifdef FEAT_CLIPBOARD_PROVIDER
+ inc_clip_provider();
+#endif

if (eap->cmdidx == CMD_windo
|| eap->cmdidx == CMD_tabdo
@@ -760,6 +763,9 @@ ex_listdo(exarg_T *eap)
#ifdef FEAT_CLIPBOARD
end_global_changes();
#endif
+#ifdef FEAT_CLIPBOARD_PROVIDER
+ dec_clip_provider();
+#endif
}

#ifdef FEAT_EVAL
diff --git a/src/ex_docmd.c b/src/ex_docmd.c
index 3f31460a0..bc3c91252 100644
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -375,7 +375,7 @@ static void ex_folddo(exarg_T *eap);
#if !defined(FEAT_WAYLAND)
# define ex_wlrestore ex_ni
#endif
-#if !defined(FEAT_CLIPBOARD)
+#if !defined(HAVE_CLIPMETHOD)
# define ex_clipreset ex_ni
#endif
#if !defined(FEAT_PROP_POPUP)
@@ -2372,7 +2372,7 @@ do_one_cmd(
&& (!IS_USER_CMDIDX(ea.cmdidx) || *ea.arg != '=')
&& !((ea.argt & EX_COUNT) && VIM_ISDIGIT(*ea.arg)))
{
-#ifndef FEAT_CLIPBOARD
+#if !defined(FEAT_CLIPBOARD) && !defined(FEAT_CLIPBOARD_PROVIDER)
// check these explicitly for a more specific error message
if (*ea.arg == '*' || *ea.arg == '+')
{
@@ -10404,6 +10404,9 @@ ex_folddo(exarg_T *eap)
# ifdef FEAT_CLIPBOARD
start_global_changes();
# endif
+#ifdef FEAT_CLIPBOARD_PROVIDER
+ inc_clip_provider();
+#endif

// First set the marks for all lines closed/open.
for (lnum = eap->line1; lnum <= eap->line2; ++lnum)
@@ -10416,6 +10419,9 @@ ex_folddo(exarg_T *eap)
# ifdef FEAT_CLIPBOARD
end_global_changes();
# endif
+#ifdef FEAT_CLIPBOARD_PROVIDER
+ dec_clip_provider();
+#endif
}
#endif

diff --git a/src/globals.h b/src/globals.h
index f412ab5fc..180e3f44f 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -993,6 +993,11 @@ EXTERN regprog_T *clip_exclude_prog INIT(= NULL);
EXTERN int clip_unnamed_saved INIT(= 0);
#endif

+#ifdef FEAT_CLIPBOARD_PROVIDER
+EXTERN char_u *clip_provider INIT(= NULL);
+#endif
+
+
/*
* All regular windows are linked in a list. "firstwin" points to the first
* entry, "lastwin" to the last entry (can be the same as firstwin) and
@@ -2070,7 +2075,7 @@ EXTERN int p_tgc_set INIT(= FALSE);
// If we've already warned about missing/unavailable clipboard
EXTERN bool did_warn_clipboard INIT(= FALSE);

-#ifdef FEAT_CLIPBOARD
+#ifdef HAVE_CLIPMETHOD
EXTERN clipmethod_T clipmethod INIT(= CLIPMETHOD_NONE);
#endif

diff --git a/src/gui.c b/src/gui.c
index 9487fd193..dd81d4c99 100644
--- a/src/gui.c
+++ b/src/gui.c
@@ -146,9 +146,11 @@ gui_start(char_u *arg UNUSED)
emsg(msg);
#endif
}
+#ifdef HAVE_CLIPMETHOD
else
// Reset clipmethod to CLIPMETHOD_NONE
choose_clipmethod();
+#endif

#ifdef FEAT_SOCKETSERVER
// Install socket server listening socket if we are running it
diff --git a/src/main.c b/src/main.c
index 14ae3eaf3..e42aafaa9 100644
--- a/src/main.c
+++ b/src/main.c
@@ -704,7 +704,7 @@ vim_main2(void)
}
#endif

-#ifdef FEAT_CLIPBOARD
+#ifdef HAVE_CLIPMETHOD
choose_clipmethod();
#endif

diff --git a/src/option.h b/src/option.h
index df073b2b0..87096fa60 100644
--- a/src/option.h
+++ b/src/option.h
@@ -507,6 +507,8 @@ EXTERN char_u *p_cedit; // 'cedit'
EXTERN long p_cwh; // 'cmdwinheight'
#ifdef FEAT_CLIPBOARD
EXTERN char_u *p_cb; // 'clipboard'
+#endif
+#ifdef HAVE_CLIPMETHOD
EXTERN char_u *p_cpm; // 'clipmethod'
#endif
EXTERN long p_ch; // 'cmdheight'
diff --git a/src/optiondefs.h b/src/optiondefs.h
index 59d363ffa..81d9ff76e 100644
--- a/src/optiondefs.h
+++ b/src/optiondefs.h
@@ -632,7 +632,7 @@ static struct vimoption options[] =
#endif
SCTX_INIT},
{"clipmethod", "cpm", P_STRING|P_VI_DEF|P_ONECOMMA|P_NODUP,
-#ifdef FEAT_CLIPBOARD
+#ifdef HAVE_CLIPMETHOD
(char_u *)&p_cpm, PV_NONE, did_set_clipmethod, expand_set_clipmethod,
# ifdef UNIX
{(char_u *)"wayland,x11", (char_u *)0L}
diff --git a/src/optionstr.c b/src/optionstr.c
index 106e9003d..42e09b3c9 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -44,8 +44,6 @@ static char *(p_ff_values[]) = {FF_UNIX, FF_DOS, FF_MAC, NULL};
#ifdef FEAT_CLIPBOARD
// Note: Keep this in sync with did_set_clipboard()
static char *(p_cb_values[]) = {"unnamed", "unnamedplus", "autoselect", "autoselectplus", "autoselectml", "html", "exclude:", NULL};
-// Note: Keep this in sync with get_clipmethod()
-static char *(p_cpm_values[]) = {"wayland", "x11", NULL};
#endif
#ifdef FEAT_CRYPT
static char *(p_cm_values[]) = {"zip", "blowfish", "blowfish2",
@@ -1402,7 +1400,9 @@ expand_set_clipboard(optexpand_T *args, int *numMatches, char_u ***matches)
numMatches,
matches);
}
+#endif

+#ifdef HAVE_CLIPMETHOD
char *
did_set_clipmethod(optset_T *args UNUSED)
{
@@ -1412,12 +1412,61 @@ did_set_clipmethod(optset_T *args UNUSED)
int
expand_set_clipmethod(optexpand_T *args, int *numMatches, char_u ***matches)
{
- return expand_set_opt_string(
+ // We want to expand using the predefined clipmethod values + clipboard
+ // provider names.
+ int result;
+ char **values;
+ int count, pos = 0, start = 0;
+#ifdef FEAT_EVAL
+ dict_T *providers = get_vim_var_dict(VV_CLIPPROVIDERS);
+#else
+ dict_T *providers = NULL;
+#endif
+ hashtab_T *ht = providers == NULL ? NULL : &providers->dv_hashtab;
+
+ count = (ht == NULL ? 0 : ht->ht_used);
+#ifdef FEAT_WAYLAND_CLIPBOARD
+ count++;
+ start++;
+#endif
+#ifdef FEAT_XCLIPBOARD
+ count++;
+ start++;
+#endif
+ values = ALLOC_MULT(char *, count + 1); // Add NULL terminator too
+
+ if (values == NULL)
+ return FAIL;
+
+#ifdef FEAT_WAYLAND_CLIPBOARD
+ values[pos++] = "wayland";
+#endif
+#ifdef FEAT_XCLIPBOARD
+ values[pos++] = "x11";
+#endif
+
+ if (ht != NULL)
+ for (long_u i = 0; i < ht->ht_mask + 1; i++)
+ {
+ hashitem_T *hi = ht->ht_array + i;
+
+ if (!HASHITEM_EMPTY(hi))
+ values[pos++] = (char *)vim_strsave(hi->hi_key);
+ }
+ values[pos++] = NULL;
+
+ result = expand_set_opt_string(
args,
- p_cpm_values,
- ARRAY_LENGTH(p_cpm_values) - 1,
+ values,
+ count,
numMatches,
matches);
+
+ for (int i = start; i < count; i++)
+ vim_free(values[i]);
+ vim_free(values);
+
+ return result;
}
#endif

diff --git a/src/proto/clipboard.pro b/src/proto/clipboard.pro
index 5727aa8b7..6ada18c9e 100644
--- a/src/proto/clipboard.pro
+++ b/src/proto/clipboard.pro
@@ -40,4 +40,8 @@ void clip_uninit_wayland(void);
int clip_reset_wayland(void);
char *choose_clipmethod(void);
void ex_clipreset(exarg_T *eap);
+void call_clip_provider_request(int reg);
+void call_clip_provider_set(int reg);
+void inc_clip_provider(void);
+void dec_clip_provider(void);
/* vim: set ft=c : */
diff --git a/src/proto/evalfunc.pro b/src/proto/evalfunc.pro
index 627af17a8..dad8c6771 100644
--- a/src/proto/evalfunc.pro
+++ b/src/proto/evalfunc.pro
@@ -28,4 +28,5 @@ void f_len(typval_T *argvars, typval_T *rettv);
void mzscheme_call_vim(char_u *name, typval_T *args, typval_T *rettv);
void range_list_materialize(list_T *list);
long do_searchpair(char_u *spat, char_u *mpat, char_u *epat, int dir, typval_T *skip, int flags, pos_T *match_pos, linenr_T lnum_stop, long time_limit);
+int get_yank_type(char_u **pp, char_u *yank_type, long *block_len);
/* vim: set ft=c : */
diff --git a/src/register.c b/src/register.c
index 52afe793c..589d07f71 100644
--- a/src/register.c
+++ b/src/register.c
@@ -46,7 +46,7 @@ get_y_regs(void)
}
#endif

-#if defined(FEAT_CLIPBOARD)
+#if defined(FEAT_CLIPBOARD) || defined(FEAT_CLIPBOARD_PROVIDER)
yankreg_T *
get_y_register(int reg)
{
@@ -190,9 +190,17 @@ valid_yank_reg(
|| regname == '"'
|| regname == '-'
|| regname == '_'
-#ifdef FEAT_CLIPBOARD
+#if defined(FEAT_CLIPBOARD) // If +clipboard is enabled, then these registers
+ // always exist.
|| regname == '*'
|| regname == '+'
+#elif defined(FEAT_CLIPBOARD_PROVIDER)
+ || ( // If -clipboard, then these registers only exist when
+ // clipmethod is set to provider.
+ clipmethod == CLIPMETHOD_PROVIDER && (
+ regname == '*'
+ || regname == '+'
+ ))
#endif
#ifdef FEAT_DND
|| (!writing && regname == '~')
@@ -256,7 +264,25 @@ get_yank_register(int regname, int writing)
// When clipboard is not available, use register 0 instead of '+'
else if (clip_plus.available && regname == '+')
{
- i = PLUS_REGISTER;
+#ifdef FEAT_CLIPBOARD_PROVIDER
+ // We want to use the actual + register, since PLUS_REGISTER may be
+ // pointing to STAR_REGISTER.
+ if (clipmethod == CLIPMETHOD_PROVIDER)
+ i = REAL_PLUS_REGISTER;
+ else
+#endif
+ i = PLUS_REGISTER;
+ ret = TRUE;
+ }
+#elif defined(FEAT_CLIPBOARD_PROVIDER)
+ else if (regname == '*')
+ {
+ i = STAR_REGISTER;
+ ret = TRUE;
+ }
+ else if (regname == '+')
+ {
+ i = REAL_PLUS_REGISTER;
ret = TRUE;
}
#endif
@@ -284,20 +310,26 @@ get_register(
yankreg_T *reg;
int i;

+#ifdef FEAT_CLIPBOARD_PROVIDER
+ call_clip_provider_request(name);
+#endif
#ifdef FEAT_CLIPBOARD
- // When Visual area changed, may have to update selection. Obtain the
- // selection too.
- if (name == '*' && clip_star.available)
- {
- if (clip_isautosel_star())
- clip_update_selection(&clip_star);
- may_get_selection(name);
- }
- if (name == '+' && clip_plus.available)
+ if (clipmethod != CLIPMETHOD_PROVIDER)
{
- if (clip_isautosel_plus())
- clip_update_selection(&clip_plus);
- may_get_selection(name);
+ // When Visual area changed, may have to update selection. Obtain the
+ // selection too.
+ if (name == '*' && clip_star.available)
+ {
+ if (clip_isautosel_star())
+ clip_update_selection(&clip_star);
+ may_get_selection(name);
+ }
+ if (name == '+' && clip_plus.available)
+ {
+ if (clip_isautosel_plus())
+ clip_update_selection(&clip_plus);
+ may_get_selection(name);
+ }
}
#endif

@@ -615,8 +647,12 @@ do_execreg(
}
execreg_lastc = regname;

+#ifdef FEAT_CLIPBOARD_PROVIDER
+ call_clip_provider_request(regname);
+#endif
#ifdef FEAT_CLIPBOARD
- regname = may_get_selection(regname);
+ if (clipmethod != CLIPMETHOD_PROVIDER)
+ regname = may_get_selection(regname);
#endif

// black hole: don't stuff anything
@@ -823,8 +859,12 @@ insert_reg(
if (regname != NUL && !valid_yank_reg(regname, FALSE))
return FAIL;

+#ifdef FEAT_CLIPBOARD_PROVIDER
+ call_clip_provider_request(regname);
+#endif
#ifdef FEAT_CLIPBOARD
- regname = may_get_selection(regname);
+ if (clipmethod != CLIPMETHOD_PROVIDER)
+ regname = may_get_selection(regname);
#endif

if (regname == '.') // insert last inserted text
@@ -1379,39 +1419,49 @@ op_yank(oparg_T *oap, int deleting, int mess)
decl(&curbuf->b_op_end);
}

+#ifdef FEAT_CLIPBOARD_PROVIDER
+ if (curr == &y_regs[REAL_PLUS_REGISTER])
+ call_clip_provider_set('+');
+ else if (curr == &y_regs[STAR_REGISTER])
+ call_clip_provider_set('*');
+#endif
+
#ifdef FEAT_CLIPBOARD
- // If we were yanking to the '*' register, send result to clipboard.
- // If no register was specified, and "unnamed" in 'clipboard', make a copy
- // to the '*' register.
- if (clip_star.available
- && (curr == &(y_regs[STAR_REGISTER])
- || (!deleting && oap->regname == 0
- && ((clip_unnamed | clip_unnamed_saved) & CLIP_UNNAMED))))
- {
- if (curr != &(y_regs[STAR_REGISTER]))
- // Copy the text from register 0 to the clipboard register.
- copy_yank_reg(&(y_regs[STAR_REGISTER]));
+ if (clipmethod != CLIPMETHOD_PROVIDER)
+ {
+ // If we were yanking to the '*' register, send result to clipboard. If
+ // no register was specified, and "unnamed" in 'clipboard', make a copy
+ // to the '*' register.
+ if (clip_star.available
+ && (curr == &(y_regs[STAR_REGISTER])
+ || (!deleting && oap->regname == 0
+ && ((clip_unnamed | clip_unnamed_saved) & CLIP_UNNAMED))))
+ {
+ if (curr != &(y_regs[STAR_REGISTER]))
+ // Copy the text from register 0 to the clipboard register.
+ copy_yank_reg(&(y_regs[STAR_REGISTER]));

- clip_own_selection(&clip_star);
- clip_gen_set_selection(&clip_star);
- }
+ clip_own_selection(&clip_star);
+ clip_gen_set_selection(&clip_star);
+ }

# if defined(FEAT_X11) || defined(FEAT_WAYLAND_CLIPBOARD)
- // If we were yanking to the '+' register, send result to selection.
- if (clip_plus.available
- && (curr == &(y_regs[PLUS_REGISTER])
- || (!deleting && oap->regname == 0
- && ((clip_unnamed | clip_unnamed_saved) &
- CLIP_UNNAMED_PLUS))))
- {
- if (curr != &(y_regs[PLUS_REGISTER]))
- // Copy the text from register 0 to the clipboard register.
- copy_yank_reg(&(y_regs[PLUS_REGISTER]));
+ // If we were yanking to the '+' register, send result to selection.
+ if (clip_plus.available
+ && (curr == &(y_regs[PLUS_REGISTER])
+ || (!deleting && oap->regname == 0
+ && ((clip_unnamed | clip_unnamed_saved) &
+ CLIP_UNNAMED_PLUS))))
+ {
+ if (curr != &(y_regs[PLUS_REGISTER]))
+ // Copy the text from register 0 to the clipboard register.
+ copy_yank_reg(&(y_regs[PLUS_REGISTER]));

- clip_own_selection(&clip_plus);
- clip_gen_set_selection(&clip_plus);
- }
+ clip_own_selection(&clip_plus);
+ clip_gen_set_selection(&clip_plus);
+ }
# endif
+ }
#endif

#if defined(FEAT_EVAL)
@@ -1535,12 +1585,19 @@ do_put(
pos_T orig_end = curbuf->b_op_end;
unsigned int cur_ve_flags = get_ve_flags();

+#ifdef FEAT_CLIPBOARD_PROVIDER
+ call_clip_provider_request(regname);
+#endif
#ifdef FEAT_CLIPBOARD
- // Adjust register name for "unnamed" in 'clipboard'.
- adjust_clip_reg(&regname);
- (void)may_get_selection(regname);
+ if (clipmethod != CLIPMETHOD_PROVIDER)
+ {
+ // Adjust register name for "unnamed" in 'clipboard'.
+ adjust_clip_reg(&regname);
+ (void)may_get_selection(regname);
+ }
#endif

+
curbuf->b_op_start = curwin->w_cursor; // default for '[ mark
curbuf->b_op_end = curwin->w_cursor; // default for '] mark

@@ -2326,10 +2383,16 @@ get_register_name(int num)
return num + '0';
else if (num == DELETION_REGISTER)
return '-';
-#ifdef FEAT_CLIPBOARD
+#if defined(FEAT_CLIPBOARD) || defined(FEAT_CLIPBOARD_PROVIDER)
else if (num == STAR_REGISTER)
return '*';
- else if (num == PLUS_REGISTER)
+ // If there is only one clipboard, we only want the plus register to point
+ // to the star register if the clipboard provider is not being used. If the
+ // clipboard provider is being used, then both registers should be available
+ // no matter the platform
+ else if (clipmethod == CLIPMETHOD_PROVIDER && num == REAL_PLUS_REGISTER)
+ return '+';
+ else if (clipmethod != CLIPMETHOD_PROVIDER && num == PLUS_REGISTER)
return '+';
#endif
else
@@ -2368,6 +2431,10 @@ ex_display(exarg_T *eap)
arg = NULL;
attr = HL_ATTR(HLF_8);

+#ifdef FEAT_CLIPBOARD_PROVIDER
+ inc_clip_provider();
+#endif
+
// Highlight title
msg_puts_title(_("
Type Name Content"));
for (i = -1; i < NUM_REGISTERS && !got_int; ++i)
@@ -2381,18 +2448,24 @@ ex_display(exarg_T *eap)
}
if (arg != NULL && vim_strchr(arg, name) == NULL
#ifdef ONE_CLIPBOARD
- // Star register and plus register contain the same thing.
+ // Star register and plus register contain the same thing.
&& (name != '*' || vim_strchr(arg, '+') == NULL)
#endif
)
continue; // did not ask for this register

+#ifdef FEAT_CLIPBOARD_PROVIDER
+ call_clip_provider_request(name);
+#endif
#ifdef FEAT_CLIPBOARD
- // Adjust register name for "unnamed" in 'clipboard'.
- // When it's a clipboard register, fill it with the current contents
- // of the clipboard.
- adjust_clip_reg(&name);
- (void)may_get_selection(name);
+ if (clipmethod != CLIPMETHOD_PROVIDER)
+ {
+ // Adjust register name for "unnamed" in 'clipboard'.
+ // When it's a clipboard register, fill it with the current contents
+ // of the clipboard.
+ adjust_clip_reg(&name);
+ (void)may_get_selection(name);
+ }
#endif

if (i == -1)
@@ -2513,6 +2586,10 @@ ex_display(exarg_T *eap)
dis_msg(expr_line, FALSE);
}
#endif
+
+#ifdef FEAT_CLIPBOARD_PROVIDER
+ dec_clip_provider();
+#endif
}

/*
@@ -2585,8 +2662,12 @@ get_reg_type(int regname, long *reglen)
return MCHAR;
}

+#ifdef FEAT_CLIPBOARD_PROVIDER
+ call_clip_provider_request(regname);
+#endif
# ifdef FEAT_CLIPBOARD
- regname = may_get_selection(regname);
+ if (clipmethod != CLIPMETHOD_PROVIDER)
+ regname = may_get_selection(regname);
# endif

if (regname != NUL && !valid_yank_reg(regname, FALSE))
@@ -2664,8 +2745,12 @@ get_reg_contents(int regname, int flags)
if (regname != NUL && !valid_yank_reg(regname, FALSE))
return NULL;

+#ifdef FEAT_CLIPBOARD_PROVIDER
+ call_clip_provider_request(regname);
+#endif
# ifdef FEAT_CLIPBOARD
- regname = may_get_selection(regname);
+ if (clipmethod != CLIPMETHOD_PROVIDER)
+ regname = may_get_selection(regname);
# endif

if (get_spec_reg(regname, &retval, &allocated, FALSE))
@@ -2830,6 +2915,10 @@ write_reg_contents_lst(
str_to_reg(y_current, yank_type, (char_u *)strings, -1, block_len, TRUE);

finish_write_reg(name, old_y_previous, old_y_current);
+
+#ifdef FEAT_CLIPBOARD_PROVIDER
+ call_clip_provider_set(name);
+#endif
}

void
@@ -2904,6 +2993,10 @@ write_reg_contents_ex(
str_to_reg(y_current, yank_type, str, len, block_len, FALSE);

finish_write_reg(name, old_y_previous, old_y_current);
+
+#ifdef FEAT_CLIPBOARD_PROVIDER
+ call_clip_provider_set(name);
+#endif
}
#endif // FEAT_EVAL

diff --git a/src/structs.h b/src/structs.h
index 3462aeca9..37c8ac6c0 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -4874,12 +4874,20 @@ typedef enum {

// Symbolic names for some registers.
#define DELETION_REGISTER 36
-#ifdef FEAT_CLIPBOARD
+#if defined(FEAT_CLIPBOARD) || defined(HAVE_CLIPMETHOD)
# define STAR_REGISTER 37
# if defined(FEAT_X11) || defined(FEAT_WAYLAND)
# define PLUS_REGISTER 38
+# define REAL_PLUS_REGISTER PLUS_REGISTER
# else
# define PLUS_REGISTER STAR_REGISTER // there is only one
+# ifdef FEAT_EVAL
+// Make it so that if clipmethod is "none", the plus register is not available,
+// but if clipmethod is a provider, then expose the plus register for use.
+# define REAL_PLUS_REGISTER 38
+# else
+# define REAL_PLUS_REGISTER STAR_REGISTER
+# endif
# endif
#endif
#ifdef FEAT_DND
@@ -4890,10 +4898,14 @@ typedef enum {
# ifdef FEAT_DND
# define NUM_REGISTERS (TILDE_REGISTER + 1)
# else
-# define NUM_REGISTERS (PLUS_REGISTER + 1)
+# define NUM_REGISTERS (REAL_PLUS_REGISTER + 1)
# endif
#else
-# define NUM_REGISTERS 37
+# ifdef HAVE_CLIPMETHOD
+# define NUM_REGISTERS (REAL_PLUS_REGISTER + 1)
+# else
+# define NUM_REGISTERS 37
+# endif
#endif

// structure used by block_prep, op_delete and op_yank for blockwise operators
diff --git a/src/testdir/test_clipmethod.vim b/src/testdir/test_clipmethod.vim
index 9b3465842..3e98828bf 100644
--- a/src/testdir/test_clipmethod.vim
+++ b/src/testdir/test_clipmethod.vim
@@ -3,21 +3,26 @@
source util/window_manager.vim

CheckFeature clipboard_working
-CheckFeature xterm_clipboard
-CheckFeature wayland_clipboard
-CheckUnix

" Test if no available clipmethod sets v:clipmethod to none and deinits clipboard
func Test_no_clipmethod_sets_v_clipmethod_none()
+ CheckFeature xterm_clipboard
+ CheckFeature wayland_clipboard
+ CheckUnix
CheckNotGui

set clipmethod=
call assert_equal("none", v:clipmethod)
call assert_equal(0, has('clipboard_working'))
+
+ set clipmethod&
endfunc

" Test if method chosen is in line with clipmethod order
func Test_clipmethod_order()
+ CheckFeature xterm_clipboard
+ CheckFeature wayland_clipboard
+ CheckUnix
CheckNotGui

set cpm=wayland,x11
@@ -60,6 +65,8 @@ func Test_clipmethod_order()
call assert_equal("wayland", v:clipmethod)

call EndWaylandCompositor(l:wayland_display)
+
+ set clipmethod&
endfunc

" Test if clipmethod is set to 'none' when gui is started
@@ -83,6 +90,9 @@ endfunc

" Test if :clipreset switches methods when current one doesn't work
func Test_clipreset_switches()
+ CheckFeature xterm_clipboard
+ CheckFeature wayland_clipboard
+ CheckUnix
CheckNotGui
CheckFeature clientserver
CheckXServer
@@ -171,6 +181,48 @@ func Test_clipreset_switches()
" existing, this why WaitForAssert() is used.
call WaitForAssert({-> assert_equal(['SUCCESS'], readfile('Xtest'))}, 1000)
endif
+
+ set clipmethod&
+endfunc
+
+func s:AAvailable()
+ return g:a_available
+endfunc
+
+func s:BAvailable()
+ return g:b_available
+endfunc
+
+" Test clipmethod when using provider
+func Test_clipmethod_provider()
+ CheckFeature clipboard_provider
+
+ let v:clipproviders["a"] = {
+ \ "available": function("s:AAvailable"),
+ \ }
+ let v:clipproviders["b"] = {
+ \ "available": function("s:BAvailable"),
+ \ }
+ let g:a_available = 1
+ let g:b_available = 1
+
+ set clipmethod=a,b
+ call assert_equal("a", v:clipmethod)
+
+ let g:a_available = 0
+ clipreset
+ call assert_equal("b", v:clipmethod)
+
+ let g:b_available = 0
+ clipreset
+ call assert_equal("none", v:clipmethod)
+
+ let g:a_available = 1
+ let g:b_available = 1
+ clipreset
+ call assert_equal("a", v:clipmethod)
+
+ set clipmethod&
endfunc

" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_eval_stuff.vim b/src/testdir/test_eval_stuff.vim
index 39bb4ba9f..49fa9509e 100644
--- a/src/testdir/test_eval_stuff.vim
+++ b/src/testdir/test_eval_stuff.vim
@@ -727,4 +727,360 @@ func Test_eval_string_in_special_key()
silent! echo 0{1-$"\<S--{> n|nö%
endfunc

+func s:Available()
+ return get(g:, "vim_cp_available", v:true)
+endfunc
+
+func s:Paste(reg)
+ let l:t = g:vim_paste
+
+ if l:t == "tuple"
+ return ("c", ["a", "tuple", a:reg])
+
+ elseif l:t == "list"
+ return ["c", ["a", "list", a:reg]]
+
+ elseif l:t == "block"
+ return ("b40", ["a", "block"])
+
+ elseif l:t == "empty"
+ return ("c", [])
+
+ elseif l:t == "invalid"
+ return ("INVALID", [])
+
+ elseif l:t == "invalid2"
+ return ("c", ["test", [1, 2]])
+
+ elseif l:t == "count"
+ let g:vim_paste_count[a:reg] += 1
+ return ("c", ["hello"])
+
+ elseif l:t == "store"
+ let l:s = get(g:, "vim_reg_store", [])
+
+ if (len(l:s) > 0)
+ return (l:s[0], l:s[1])
+ else
+ return ("c", [])
+ endif
+
+ endif
+endfunc
+
+func s:Copy(reg, type, lines)
+ if exists("g:vim_copy_count")
+ let g:vim_copy_count[a:reg] += 1
+ endif
+
+ let g:vim_copy = {
+ \ "reg": a:reg,
+ \ "type": a:type,
+ \ "lines": a:lines
+ \ }
+ let g:vim_reg_store = (a:type, a:lines)
+endfunc
+
+func Test_clipboard_provider_available()
+ let g:vim_cp_available = v:true
+
+ let v:clipproviders["test"] = {
+ \ "available": function("s:Available"),
+ \ }
+ set clipmethod=test
+
+ call assert_equal("test", v:clipmethod)
+
+ let g:vim_cp_available = v:false
+ clipreset
+ call assert_notequal("test", v:clipmethod)
+
+ " Invalid return value
+ let g:vim_cp_available = "invalid"
+ call assert_fails('set clipmethod=test', "E474:")
+ call assert_notequal("test", v:clipmethod)
+
+ let v:clipproviders["test"] = {}
+ clipreset
+ " Should default to TRUE
+ call assert_equal("test", v:clipmethod)
+
+ set clipmethod&
+endfunc
+
+func Test_clipboard_provider_paste()
+ let v:clipproviders["test"] = {
+ \ "paste": {
+ \ '+': function("s:Paste"),
+ \ '*': function("s:Paste")
+ \ }
+ \ }
+ set clipmethod=test
+
+ let g:vim_paste = "tuple"
+ call assert_equal(["a", "tuple", "+"], getreg("+", 1, 1))
+ call assert_equal(["a", "tuple", "*"], getreg("*", 1, 1))
+
+ let g:vim_paste = "list"
+ call assert_equal(["a", "list", "+"], getreg("+", 1, 1))
+ call assert_equal(["a", "list", "*"], getreg("*", 1, 1))
+
+ let g:vim_paste = "block"
+ call assert_equal(" 40", getregtype("+"))
+ call assert_equal(" 40", getregtype("*"))
+
+ let g:vim_paste = "empty"
+ call assert_equal([], getreg("+", 1, 1))
+ call assert_equal([], getreg("*", 1, 1))
+
+ let g:vim_paste = "invalid"
+ call assert_fails('call getreg("+", 1, 1)', "E474:")
+ call assert_fails('call getreg("*", 1, 1)', "E474:")
+
+ let g:vim_paste = "invalid2"
+ call assert_fails('call getreg("+", 1, 1)', "E730:")
+ call assert_fails('call getreg("*", 1, 1)', "E730:")
+
+ " Test put
+ new
+
+ let g:vim_paste = "tuple"
+ put! *
+
+ call assert_equal(["a", "tuple", "*"], getline(1, 3))
+
+ put +
+
+ call assert_equal(["a", "tuple", "+"], getline(4, 6))
+
+ bw!
+
+ set clipmethod&
+endfunc
+
+func Test_clipboard_provider_copy()
+ let v:clipproviders["test"] = {
+ \ "copy": {
+ \ '+': function("s:Copy"),
+ \ '*': function("s:Copy")
+ \ }
+ \ }
+ set clipmethod=test
+
+ " Test charwise
+ call setreg("+", ["hello", "world!"], "c")
+ call assert_equal("+",g:vim_copy.reg)
+ call assert_equal(["hello", "world!"], g:vim_copy.lines)
+ call assert_equal("v", g:vim_copy.type)
+
+ call setreg("*", ["hello", "world!"], "c")
+ call assert_equal("*",g:vim_copy.reg)
+ call assert_equal(["hello", "world!"], g:vim_copy.lines)
+ call assert_equal("v", g:vim_copy.type)
+
+ " Test linewise
+ call setreg("+", ["hello", "world!"], "l")
+ call assert_equal("+",g:vim_copy.reg)
+ call assert_equal(["hello", "world!"], g:vim_copy.lines)
+ call assert_equal("V", g:vim_copy.type)
+
+ call setreg("*", ["hello", "world!"], "l")
+ call assert_equal("*",g:vim_copy.reg)
+ call assert_equal(["hello", "world!"], g:vim_copy.lines)
+ call assert_equal("V", g:vim_copy.type)
+
+ " Test blockwise
+ call setreg("+", ["hello", "world!"], "b40")
+ call assert_equal("+",g:vim_copy.reg)
+ call assert_equal(["hello", "world!"], g:vim_copy.lines)
+ call assert_equal(" 40", g:vim_copy.type)
+
+ call setreg("*", ["hello", "world!"], "b40")
+ call assert_equal("*",g:vim_copy.reg)
+ call assert_equal(["hello", "world!"], g:vim_copy.lines)
+ call assert_equal(" 40", g:vim_copy.type)
+
+ " Test yanking
+ new
+
+ call setline(1, "TESTING")
+ yank *
+
+ call assert_equal("*",g:vim_copy.reg)
+ call assert_equal(["TESTING"], g:vim_copy.lines)
+ call assert_equal("V", g:vim_copy.type)
+
+ call setline(1, "TESTING2")
+ yank +
+
+ call assert_equal("+",g:vim_copy.reg)
+ call assert_equal(["TESTING2"], g:vim_copy.lines)
+ call assert_equal("V", g:vim_copy.type)
+
+ bw!
+
+ set clipmethod&
+endfunc
+
+" Test on platforms where only the * register is available (+ points to *),
+" and that the clipboard provider feature exposes the + register only when
+" clipmethod is set to a provider. If not, then the plus register points to the
+" star register like normal.
+func Test_clipboard_provider_no_unamedplus()
+ CheckNotFeature unnamedplus
+ CheckFeature clipboard_working
+
+ let g:vim_paste = "store"
+ let v:clipproviders["test"] = {}
+ set clipmethod=test
+
+ call assert_equal(1, has('unnamedplus'))
+
+ call setreg("+", ["plus"], "c")
+ call setreg("*", ["star"], "c")
+ call assert_equal("plus", getreg("+"))
+ call assert_equal("star", getreg("*"))
+
+ set clipmethod=
+ call assert_equal(0, has('unnamedplus'))
+
+ call setreg("+", ["plus2"], "c")
+ call setreg("*", ["star2"], "c")
+ call assert_equal("star2", getreg("+"))
+ call assert_equal("star2", getreg("*"))
+
+ set clipmethod&
+endfunc
+
+" Same as Test_clipboard_provider_registers() but do it when +clipboard isnt
+" enabled.
+func Test_clipboard_provider_no_clipboard()
+ CheckNotFeature clipboard
+
+ let v:clipproviders["test"] = {
+ \ "paste": {
+ \ '+': function("s:Paste"),
+ \ '*': function("s:Paste")
+ \ },
+ \ "copy": {
+ \ '+': function("s:Copy"),
+ \ '*': function("s:Copy")
+ \ }
+ \ }
+
+ call assert_fails('call setreg("+", "")', 'E354:')
+ call assert_fails('call setreg("*", "")', 'E354:')
+
+ let g:vim_paste = "tuple"
+ set clipmethod=test
+
+ call assert_equal(["a", "tuple", "+"], getreg("+", 1, 1))
+ call assert_equal(["a", "tuple", "*"], getreg("*", 1, 1))
+
+ set clipmethod&
+endfunc
+
+" Test if clipboard provider feature doesn't break existing clipboard
+" functionality.
+func Test_clipboard_provider_sys_clipboard()
+ CheckFeature clipboard_working
+
+ let v:clipproviders["test"] = {
+ \ "paste": {
+ \ '+': function("s:Paste"),
+ \ '*': function("s:Paste")
+ \ },
+ \ "copy": {
+ \ '+': function("s:Copy"),
+ \ '*': function("s:Copy")
+ \ }
+ \ }
+
+ call setreg("*", "hello world from the * register!", "c")
+ call assert_equal("hello world from the * register!", getreg("*"))
+ call setreg("+", "hello world from the + register!", "c")
+ call assert_equal("hello world from the + register!", getreg("+"))
+
+ let g:vim_paste = "tuple"
+ set clipmethod=test
+
+ call assert_equal(1, has('clipboard_working'))
+ call setreg("*", "hello world!", "c")
+ call assert_equal(["a", "tuple", "*"], getreg("*", 1, 1))
+ call assert_equal(["a", "tuple", "+"], getreg("+", 1, 1))
+
+ new
+ call setline(1, "TESTING")
+ yank *
+
+ call assert_equal("*",g:vim_copy.reg)
+ call assert_equal(["TESTING"], g:vim_copy.lines)
+ call assert_equal("V", g:vim_copy.type)
+
+ put *
+
+ call assert_equal(["TESTING", "a", "tuple", "*"], getline(1, 4))
+ bw!
+
+ set clipmethod&
+
+ call setreg("*", "testing 1 2 3", "c")
+ call assert_equal("testing 1 2 3", getreg("*"))
+ call setreg("+", "testing 1 2 3 4 5", "c")
+ call assert_equal("testing 1 2 3 4 5", getreg("+"))
+
+ set clipmethod&
+endfunc
+
+" Test if the provider callback are only called once per register on operations
+" that may try calling them multiple times.
+func Test_clipboard_provider_accessed_once()
+ let v:clipproviders["test"] = {
+ \ "paste": {
+ \ '+': function("s:Paste"),
+ \ '*': function("s:Paste")
+ \ },
+ \ "copy": {
+ \ '+': function("s:Copy"),
+ \ '*': function("s:Copy")
+ \ }
+ \ }
+ set clipmethod=test
+
+ let g:vim_paste = "count"
+ let g:vim_paste_count = {'*': 0, '+': 0}
+ let g:vim_copy_count = {'*': 0, '+': 0}
+
+ " Test if the paste callback is only called once per register when the
+ " :registers/:display cmd is run.
+ for i in range(1, 10)
+ registers
+
+ call assert_equal(i, g:vim_paste_count['*'])
+ call assert_equal(i, g:vim_paste_count['+'])
+ endfor
+
+ let g:vim_paste_count = {'*': 0, '+': 0}
+ let g:vim_copy_count = {'*': 0, '+': 0}
+
+ " Test same for :global
+ new
+
+ call setline(1, "The quick brown fox jumps over the lazy dog")
+ call execute(':global/quick/:put +')
+ call execute(':global/quick/:put *')
+
+ call assert_equal(1, g:vim_paste_count['+'])
+ call assert_equal(1, g:vim_paste_count['*'])
+
+ call execute(':global/quick/:yank +')
+ call execute(':global/quick/:yank *')
+
+ call assert_equal(1, g:vim_copy_count['+'])
+ call assert_equal(1, g:vim_copy_count['*'])
+
+ bw!
+ set clipmethod&
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_options.vim b/src/testdir/test_options.vim
index 3abc067b3..2e4e9f727 100644
--- a/src/testdir/test_options.vim
+++ b/src/testdir/test_options.vim
@@ -522,15 +522,21 @@ func Test_set_completion_string_values()
call assert_equal('unload', getcompletion('set bufhidden=', 'cmdline')[1])
call assert_equal('nowrite', getcompletion('set buftype=', 'cmdline')[1])
call assert_equal('internal', getcompletion('set casemap=', 'cmdline')[1])
- if exists('+clipboard')
+ if has('clipboard')
call assert_match('unnamed', getcompletion('set clipboard=', 'cmdline')[1])
endif
- if exists('+clipmethod')
- if has('unix') || has('vms')
- call assert_match('wayland', getcompletion('set clipmethod=', 'cmdline')[1])
- else
- call assert_match('wayland', getcompletion('set clipmethod=', 'cmdline')[0])
- endif
+ if has('wayland_clipboard')
+ call assert_match('wayland', getcompletion('set clipmethod=w', 'cmdline')[0])
+ endif
+ if has('xterm_clipboard')
+ call assert_match('x11', getcompletion('set clipmethod=x', 'cmdline')[0])
+ endif
+ if has('eval')
+ let v:clipproviders["first"] = {}
+ let v:clipproviders["second"] = {}
+
+ call assert_match('first', getcompletion('set clipmethod=f', 'cmdline')[0])
+ call assert_match('second', getcompletion('set clipmethod=s', 'cmdline')[0])
endif
call assert_equal('.', getcompletion('set complete=', 'cmdline')[1])
call assert_equal('menu', getcompletion('set completeopt=', 'cmdline')[1])
diff --git a/src/version.c b/src/version.c
index 922242d91..e754b6021 100644
--- a/src/version.c
+++ b/src/version.c
@@ -155,6 +155,11 @@ static char *(features[]) =
"+clipboard",
#else
"-clipboard",
+#endif
+#ifdef FEAT_CLIPBOARD_PROVIDER
+ "+clipboard_provider",
+#else
+ "-clipboard_provider",
#endif
"+cmdline_compl",
"+cmdline_hist",
@@ -729,6 +734,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 1972,
/**/
1971,
/**/
diff --git a/src/vim.h b/src/vim.h
index bc1a1d47f..fccb31b3d 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -2042,6 +2042,16 @@ typedef __int64 sock_T;
typedef int sock_T;
#endif

+// The clipboard provider feature uses clipmethod as well but should be separate
+// from the clipboard code.
+#if defined(FEAT_CLIPBOARD) || defined(FEAT_EVAL)
+# define HAVE_CLIPMETHOD
+#endif
+
+#if defined(HAVE_CLIPMETHOD) && defined(FEAT_EVAL)
+# define FEAT_CLIPBOARD_PROVIDER
+#endif
+
// Include option.h before structs.h, because the number of window-local and
// buffer-local options is used there.
#include "option.h" // options and default values
@@ -2260,7 +2270,8 @@ typedef int sock_T;
#define VV_TERMDA1 114
#define VV_TERMOSC 115
#define VV_VIM_DID_INIT 116
-#define VV_LEN 117 // number of v: vars
+#define VV_CLIPPROVIDERS 117
+#define VV_LEN 118 // number of v: vars

// used for v_number in VAR_BOOL and VAR_SPECIAL
#define VVAL_FALSE 0L // VAR_BOOL
@@ -2292,6 +2303,16 @@ typedef int sock_T;

#define TABSTOP_MAX 9999

+#ifdef HAVE_CLIPMETHOD
+typedef enum {
+ CLIPMETHOD_FAIL,
+ CLIPMETHOD_NONE,
+ CLIPMETHOD_WAYLAND,
+ CLIPMETHOD_X11,
+ CLIPMETHOD_PROVIDER
+} clipmethod_T;
+#endif
+
#ifdef FEAT_CLIPBOARD

// VIM_ATOM_NAME is the older Vim-specific selection type for X11. Still
@@ -2315,13 +2336,6 @@ typedef int sock_T;
# endif
# endif

-typedef enum {
- CLIPMETHOD_FAIL,
- CLIPMETHOD_NONE,
- CLIPMETHOD_WAYLAND,
- CLIPMETHOD_X11,
-} clipmethod_T;
-
// Info about selected text
typedef struct
{
diff --git a/src/viminfo.c b/src/viminfo.c
index 7f6313ba5..c87f54a1f 100644
--- a/src/viminfo.c
+++ b/src/viminfo.c
@@ -1907,9 +1907,9 @@ write_viminfo_registers(FILE *fp)

for (i = 0; i < NUM_REGISTERS; i++)
{
-#ifdef FEAT_CLIPBOARD
+#if defined(FEAT_CLIPBOARD) || defined(FEAT_CLIPBOARD_PROVIDER)
// Skip '*'/'+' register, we don't want them back next time
- if (i == STAR_REGISTER || i == PLUS_REGISTER)
+ if (i == STAR_REGISTER || i == PLUS_REGISTER || i == REAL_PLUS_REGISTER)
continue;
#endif
#ifdef FEAT_DND
Reply all
Reply to author
Forward
0 new messages