From: Foxe Chen Date: Thu, 11 Dec 2025 20:11:03 +0000 (+0100) Subject: patch 9.1.1972: No way to access the clipboard without X11/Wayland X-Git-Tag: v9.1.1972^0 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=fcd3958dcb62011e865204f9c82da648f3635eae;p=thirdparty%2Fvim.git 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 Signed-off-by: Christian Brabandt --- diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 4f1b0b42f4..ebb6d5ba73 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() :Launch . < - +============================================================================== +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 0017864564..a678644756 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 + 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 bc900fdf21..79c3a6fe0a 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 42b0b66578..89fce0d934 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 a736370f0b..ff6de1dc5b 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 050880215e..17161fad43 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -2,7 +2,7 @@ " Language: Vim script " Maintainer: Hirohito Higashi " Doug Kearns -" 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 af3fee308b..51f7054303 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(®_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 222fa40172..49b7c72b29 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 ca71562d42..af14c8b0dd 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 8e861d5a2f..e8343cae43 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 6b8fd2627a..6e9749f5dd 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 3f31460a0b..bc3c91252b 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 f412ab5fc2..180e3f44f1 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 9487fd1930..dd81d4c999 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 14ae3eaf37..e42aafaa9d 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 df073b2b0b..87096fa60b 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 59d363ffac..81d9ff76e7 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 106e9003dd..42e09b3c94 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 5727aa8b77..6ada18c9e7 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 627af17a8b..dad8c6771a 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 52afe793c8..589d07f715 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(®name); - (void)may_get_selection(regname); + if (clipmethod != CLIPMETHOD_PROVIDER) + { + // Adjust register name for "unnamed" in 'clipboard'. + adjust_clip_reg(®name); + (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(_("\nType 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 3462aeca95..37c8ac6c0a 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 9b34658426..3e98828bf3 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 39bb4ba9f2..49fa9509e3 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-$"\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 3abc067b3f..2e4e9f7271 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 922242d911..e754b6021f 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 bc1a1d47ff..fccb31b3dc 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 7f6313ba5f..c87f54a1f6 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