diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 46450685aa4a9f..84aba13c8abdaa 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -13023,6 +13023,7 @@ channel Compiled with support for |channel| and |job| cindent Compiled with 'cindent' support. (always true) clientserver Compiled with remote invocation support |clientserver|. clipboard Compiled with 'clipboard' support. +clipboard_provider Compiled with |clipboard-providers| support clipboard_working Compiled with 'clipboard' support and it can be used. cmdline_compl Compiled with |cmdline-completion| support. cmdline_hist Compiled with |cmdline-history| support. diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 5488050a874766..38ce09001f51b9 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -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|. @@ -2251,6 +2252,11 @@ v:clipmethod The current method of accessing the clipboard that is being unavailable. 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: 1. The extra arguments given to a file read/write command. @@ -5261,5 +5267,131 @@ Usage: >vim :call dist#vim9#Launch() :Launch . < +============================================================================== +15. Clipboard providers *clipboard-providers* + +When Vim is compiled with the |+clipboard_provider| feature, which requires +the |+eval| feature, creating custom clipboards is possible. These providers +handle the "+" and "*" registers. Note that on non-Unix or non-VMS systems, +only the "*" register will be available for use. + +To add a provider, add a new entry in the |v:clipproviders| dictionary, in the +format of: > + let v:clipproviders["name"] = { + \ "available": function("Available"), + \ "paste": { + \ '+': function("Paste"), " For the + register + \ '*': function("Paste"), " For the * register + \ }, + \ "copy": { + \ '+': function("Copy"), " Same thing as above + \ '*': function("Copy"), + \ }, + \ } + +The key is the provider name, which should be used in 'clipmethod', for +example in this example, you would add "name" to 'clipmethod' in order to use +it. > + set clipmethod=name,wayland,x11,gui + +Each callback can either be a name of a function in a string, a |Funcref|, or +a |lambda| expression. + +Note that these dictionary entries are optional, for example, if you don't +care about the "copy" functions, then you can simply exclude them. When Vim +yanks/copies something, then simply nothing will be done. + + *clipboard-provider-available* +The "available" callback should return a string, which should contain which +clipboard registers are available. For example, if the "+" register is only +available, then the function would return "+", or if both "+" and "*" are +available, then return "+*". + + *clipboard-provider-paste* +The "paste" callback takes a first argument which is the register being put +(string), and a second argument which is the type of access (string). It +should return either a tuple/list or string. If a tuple/list is returned, it +should have two elements: + - The register type conforming to |setreg()|. + - A list of strings +If the register type is an empty string, then the type is automatically +chosen, see |setreg()|. If a string is returned, then it can either be "clear" +or "previous". "clear" makes Vim clear the register, and "previous" makes Vim +use the previous register contents (or current depending on how you view it). + +The second argument, the access type, can either be "explicit" or "implicit". +"explicit" means that the user is directly accessing the clipboard, such as +putting text, or calling |getreg()|. "implicit" means that the clipboard is +being accessed indirectly, such when |:registers| is called, or when an +unrelated operation causes Vim to access the clipboard. + +This is useful since some operations in Vim implicity access the clipboard +indirectly. For example, if when you want to create a provider for the OSC52 +command (accessing the clipboard via an escape code). Many terminals show a +confirmation if you want Vim to access the clipboard. This can be very +annoying with the terminal asking for permission everytime you do something +that accesses the clipboard behind the scenes. A good way to handle implicit +access is to return "previous", which leaves the register contents unchanged. + + *clipboard-provider-copy* +The "copy" callback returns nothing, and takes the given arguments in order: + - The register being operated on + - The register type, conforming to |getregtype()| + - A list of strings to copy + +The provider can do whatever it wants with the given information. This +function is called on every request to change the clipboard register(s). + + *clipboard-provider-textlock* +In both the "paste" and "copy" callbacks, it is not allowed to change the +buffer text, see |textlock|. + + *clipboard-provider-example* +Here is an example script that uses the clipboard provider feature through the +OSC52 command: > + :func! Available() + : return "+" + :endfunc + : + :func! Paste(reg, type) + : " If implicit access, don't do anything + : if a:type == "implicit" + : return "previous" + : endif + : + : augroup OSC + : autocmd! + : autocmd TermResponseAll osc ++once call feedkeys("\", '!') + : augroup END + : + : " Send command + : call echoraw("\]52;c;?\\\") + : + : " Wait until autocmd is triggered + : while getchar(-1) != "\" + : endwhile + : + : autocmd! OSC + : + : " Extract the base64 stuff + : let l:stuff = matchstr(v:termosc, '52;c;\zs[A-Za-z0-9+/=]\+') + : + : return ("", blob2str(base64_decode(l:stuff))) + :endfunc + :func! Copy(reg, type, lines) + : call echoraw("\]52;c;" .. + : \ base64_encode(str2blob(a:lines)) .. "\\\") + :endfunc + :let v:clipproviders["myosc"] = { + : \ "available": function("Available"), + : \ "paste": { + : \ '+': function("Paste"), + : \ }, + : \ "copy": { + : \ '+': function("Copy"), + : \ }, + : \ } + :set clipmethod=myosc +< vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 12483baa81b950..81a0be5a685887 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1918,10 +1918,15 @@ A jump table for the options with a short description can be found at |Q_op|. x11 X11 selections gui GUI specific method other Some other method + * Clipboard provider method Note: "other" is used on systems without X11/Wayland, such as MS-Windows or MacOS, when running Vim without the GUI. + Note that the name of the clipboard provider should be used when you + want to use a clipboard provider. See |clipboard-providers| for more + information. + 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 determines is available or is working is used as the actual method for diff --git a/runtime/doc/tags b/runtime/doc/tags index 28d21de7077d99..459684dae94fe1 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -1411,6 +1411,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* @@ -6686,6 +6687,10 @@ 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-provider-available eval.txt /*clipboard-provider-available* +clipboard-provider-copy eval.txt /*clipboard-provider-copy* +clipboard-provider-paste eval.txt /*clipboard-provider-paste* +clipboard-providers eval.txt /*clipboard-providers* clipboard-unnamed options.txt /*clipboard-unnamed* clipboard-unnamedplus options.txt /*clipboard-unnamedplus* clojure-indent indent.txt /*clojure-indent* @@ -11249,6 +11254,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 b91e3ef237c8b6..fa802568412bae 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -378,6 +378,7 @@ m *+channel* inter process communication |channel| T *+cindent* 'cindent', C indenting; Always enabled N *+clientserver* Unix and Win32: Remote invocation |clientserver| *+clipboard* |clipboard| support compiled-in +N *+clipboard_provider* |clipboard-providers| support compiled-in *+clipboard_working* |clipboard| support compiled-in and working T *+cmdline_compl* command line completion |cmdline-completion| T *+cmdline_hist* command line history |cmdline-history| @@ -806,7 +807,10 @@ K Run a program to lookup the keyword under the :clip[reset] Attempts to choose a new method for accessing the clipboard, using the 'clipmethod' option. This is useful when the current method has become unavailable, - and you want to try using another method. + and you want to try using another method. If the + |+clipboard_provider| feature is being used, this + command should be called after the availability of one + of the clipboard registers changes. {only available when compiled with the |+clipboard| feature} diff --git a/src/auto/configure b/src/auto/configure index 7cf35d66deddc7..0f53625186e1d0 100755 --- a/src/auto/configure +++ b/src/auto/configure @@ -857,6 +857,7 @@ enable_arabic enable_farsi enable_xim enable_fontset +enable_clipboard_provider with_wayland enable_wayland_focus_steal with_x @@ -1539,6 +1540,7 @@ Optional Features: --disable-farsi Deprecated. --enable-xim Include XIM input support. --enable-fontset Include X fontset output support. + --enable-clipboard-provider Include clipboard provider support. --enable-wayland-focus-steal Include focus stealing support for Wayland clipboard. @@ -9208,6 +9210,35 @@ fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $enable_fontset" >&5 printf "%s\n" "$enable_fontset" >&6; } +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking --enable-clipboard-provider" >&5 +printf %s "checking --enable-clipboard-provider... " >&6; } +# Check whether --enable-clipboard-provider was given. +if test ${enable_clipboard_provider+y} +then : + enableval=$enable_clipboard_provider; enable_clipboard_provider=$enableval + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $enableval" >&5 +printf "%s\n" "$enableval" >&6; } +else case e in #( + e) if test "x$features" = xtiny +then : + enable_clipboard_provider="no" + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: cannot use clipboard provider with tiny features" >&5 +printf "%s\n" "cannot use clipboard provider with tiny features" >&6; } +else case e in #( + e) enable_clipboard_provider="yes" + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf "%s\n" "yes" >&6; } ;; +esac +fi ;; +esac +fi + +if test "$enable_clipboard_provider" = "yes"; then + printf "%s\n" "#define FEAT_CLIPBOARD_PROVIDER 1" >>confdefs.h + +fi + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if shm_open is available" >&5 printf %s "checking if shm_open is available... " >&6; } cppflags_save=$CPPFLAGS @@ -14538,18 +14569,18 @@ then : fi if test "$enable_largefile,$enable_year2038" != no,no then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CPPFLAGS option for large files" >&5 -printf %s "checking for $CPPFLAGS option for large files... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable large file support" >&5 +printf %s "checking for $CC option to enable large file support... " >&6; } if test ${ac_cv_sys_largefile_opts+y} then : printf %s "(cached) " >&6 else case e in #( - e) ac_save_CPPFLAGS=$CPPFLAGS + e) ac_save_CC="$CC" ac_opt_found=no - for ac_opt in "none needed" "-D_FILE_OFFSET_BITS=64" "-D_LARGE_FILES=1"; do + for ac_opt in "none needed" "-D_FILE_OFFSET_BITS=64" "-D_LARGE_FILES=1" "-n32"; do if test x"$ac_opt" != x"none needed" then : - CPPFLAGS="$ac_save_CPPFLAGS $ac_opt" + CC="$ac_save_CC $ac_opt" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -14578,12 +14609,12 @@ then : if test x"$ac_opt" = x"none needed" then : # GNU/Linux s390x and alpha need _FILE_OFFSET_BITS=64 for wide ino_t. - CPPFLAGS="$CPPFLAGS -DFTYPE=ino_t" + CC="$CC -DFTYPE=ino_t" if ac_fn_c_try_compile "$LINENO" then : else case e in #( - e) CPPFLAGS="$CPPFLAGS -D_FILE_OFFSET_BITS=64" + e) CC="$CC -D_FILE_OFFSET_BITS=64" if ac_fn_c_try_compile "$LINENO" then : ac_opt='-D_FILE_OFFSET_BITS=64' @@ -14599,7 +14630,7 @@ fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext test $ac_opt_found = no || break done - CPPFLAGS=$ac_save_CPPFLAGS + CC="$ac_save_CC" test $ac_opt_found = yes || ac_cv_sys_largefile_opts="support not detected" ;; esac @@ -14623,14 +14654,16 @@ printf "%s\n" "#define _FILE_OFFSET_BITS 64" >>confdefs.h printf "%s\n" "#define _LARGE_FILES 1" >>confdefs.h ;; #( + "-n32") : + CC="$CC -n32" ;; #( *) : as_fn_error $? "internal error: bad value for \$ac_cv_sys_largefile_opts" "$LINENO" 5 ;; esac if test "$enable_year2038" != no then : - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CPPFLAGS option for timestamps after 2038" >&5 -printf %s "checking for $CPPFLAGS option for timestamps after 2038... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option for timestamps after 2038" >&5 +printf %s "checking for $CC option for timestamps after 2038... " >&6; } if test ${ac_cv_sys_year2038_opts+y} then : printf %s "(cached) " >&6 diff --git a/src/clipboard.c b/src/clipboard.c index 232566d7da9958..736682b2393e9c 100644 --- a/src/clipboard.c +++ b/src/clipboard.c @@ -134,6 +134,12 @@ static bool clip_wl_owner_exists(Clipboard_T *cbd); #endif // FEAT_WAYLAND_CLIPBOARD +#ifdef FEAT_CLIPBOARD_PROVIDER +static int clip_provider_is_available(Clipboard_T *cbd, char_u *provider); +static void clip_provider_set_selection(Clipboard_T *cbd, char_u *provider); +static void clip_provider_request_selection(Clipboard_T *cbd, char_u *provider); +#endif + /* * Selection stuff using Visual mode, for cutting and pasting text to other * windows. @@ -254,7 +260,7 @@ clip_gen_own_selection(Clipboard_T *cbd UNUSED) } else if (clipmethod == CLIPMETHOD_OTHER) { -#if !defined(FEAT_XCLIPBOARD) && !defined(FEAT_WAYLAND_CLIPBOARD) +#ifndef UNIX return clip_mch_own_selection(cbd); #endif } @@ -321,7 +327,7 @@ clip_gen_lose_selection(Clipboard_T *cbd UNUSED) } else if (clipmethod == CLIPMETHOD_OTHER) { -#if !defined(FEAT_XCLIPBOARD) && !defined(FEAT_WAYLAND_CLIPBOARD) +#ifndef UNIX clip_mch_lose_selection(cbd); #endif } @@ -1354,7 +1360,7 @@ clip_gen_set_selection(Clipboard_T *cbd) { #ifdef FEAT_GUI if (gui.in_use) - clip_mch_set_selection(cbd); + clip_mch_set_selection(cbd); #endif } else if (clipmethod == CLIPMETHOD_WAYLAND) @@ -1371,8 +1377,14 @@ clip_gen_set_selection(Clipboard_T *cbd) } else if (clipmethod == CLIPMETHOD_OTHER) { -#if !defined(FEAT_XCLIPBOARD) && !defined(FEAT_WAYLAND_CLIPBOARD) +#ifndef UNIX clip_mch_set_selection(cbd); +#endif + } + else if (clipmethod == CLIPMETHOD_PROVIDER) + { +#ifdef FEAT_CLIPBOARD_PROVIDER + clip_provider_set_selection(cbd, clipprovider_name); #endif } } @@ -1401,8 +1413,14 @@ clip_gen_request_selection(Clipboard_T *cbd UNUSED) } else if (clipmethod == CLIPMETHOD_OTHER) { -#if !defined(FEAT_XCLIPBOARD) && !defined(FEAT_WAYLAND_CLIPBOARD) +#ifndef UNIX clip_mch_request_selection(cbd); +#endif + } + else if (clipmethod == CLIPMETHOD_PROVIDER) + { +#ifdef FEAT_CLIPBOARD_PROVIDER + clip_provider_request_selection(cbd, clipprovider_name); #endif } } @@ -2266,10 +2284,18 @@ clip_get_selection(Clipboard_T *cbd) } else if (!is_clipboard_needs_update()) { - clip_free_selection(cbd); +#ifdef FEAT_CLIPBOARD_PROVIDER + // We will choose if we want to the free the selection if using provider + if (clipmethod != CLIPMETHOD_PROVIDER) + clip_free_selection(cbd); +#endif // Try to get selected text from another window clip_gen_request_selection(cbd); +#ifdef FEAT_CLIPBOARD_PROVIDER + if (clipmethod == CLIPMETHOD_PROVIDER) + clip_access_type = CLIP_ACCESS_IMPLICIT; +#endif } } @@ -3507,15 +3533,34 @@ get_clipmethod(char_u *str, bool *regular, bool *primary) } else if (STRCMP(buf, "other") == 0) { -#if !defined(FEAT_XCLIPBOARD) && !defined(FEAT_WAYLAND_CLIPBOARD) +#ifndef UNIX method = CLIPMETHOD_OTHER; *regular = *primary = true; #endif } else { - ret = CLIPMETHOD_FAIL; - goto exit; +#ifdef FEAT_CLIPBOARD_PROVIDER + // Check if it is the name of a provider + int reg = clip_provider_is_available(&clip_plus, buf); + int pri = clip_provider_is_available(&clip_star, buf); + + if (reg == 1 || pri == 1) + { + method = CLIPMETHOD_PROVIDER; + + vim_free(clipprovider_name); + clipprovider_name = vim_strsave(buf); + + *regular = reg == 1; + *primary = pri == 1; + } + else if (reg == -1) +#endif + { + ret = CLIPMETHOD_FAIL; + goto exit; + } } // Keep on going in order to catch errors @@ -3548,6 +3593,15 @@ clipmethod_to_str(clipmethod_T method) return (char_u *)"gui"; case CLIPMETHOD_OTHER: return (char_u *)"other"; + case CLIPMETHOD_PROVIDER: +#ifdef FEAT_CLIPBOARD_PROVIDER + if (clipprovider_name == NULL) + return (char_u *)"none"; + else + return clipprovider_name; +#else + return (char_u *)"none"; +#endif default: return (char_u *)"none"; } @@ -3584,16 +3638,26 @@ choose_clipmethod(void) clip_plus.did_warn = false; clip_star.did_warn = false; } - // Disown clipboard if we are switching to a new method - else if (clipmethod != CLIPMETHOD_NONE && method != clipmethod) + else if ((clipmethod != CLIPMETHOD_NONE && method != clipmethod)) { + // Disown clipboard if we are switching to a new method if (clip_star.owned) clip_lose_selection(&clip_star); if (clip_plus.owned) clip_lose_selection(&clip_plus); + clip_init_single(&clip_plus, regular); clip_init_single(&clip_star, primary); } + else + { + // If availability of a clipboard changed, then update the clipboard + // structure. + if (regular != clip_plus.available) + clip_init_single(&clip_plus, regular); + if (primary != clip_star.available) + clip_init_single(&clip_star, primary); + } clipmethod = method; @@ -3621,4 +3685,321 @@ ex_clipreset(exarg_T *eap UNUSED) clipmethod_to_str(clipmethod)); } +#ifdef FEAT_CLIPBOARD_PROVIDER + +/* + * Check if a clipboard provider with given name exists and is available for the + * given clipboard. Returns 1 if the provider exists and the 'available' + * function returned true, 0 if the provider exists but the function returned + * false, and -1 on error. + */ + static int +clip_provider_is_available(Clipboard_T *cbd, 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}; + char_u *avail; + int res = 0; + + if (dict_get_tv(providers, (char *)provider, &provider_tv) == FAIL + || provider_tv.v_type != VAR_DICT) + return -1; + + if (dict_get_tv(provider_tv.vval.v_dict, "available", &func_tv) == FAIL) + { + clear_tv(&provider_tv); + // If "available" functon 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_STRING) + goto fail; + + avail = rettv.vval.v_string; + + if ((vim_strchr(avail, '+') != NULL && cbd == &clip_plus) + || (vim_strchr(avail, '*') != NULL && cbd == &clip_star)) + 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 of 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_set_selection(Clipboard_T *cbd, char_u *provider) +{ + char_u *reg = (char_u *)(cbd == &clip_star ? "*" : "+"); + 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; + + // Possibly get selected text, if using autoselect for 'clipboard' + cbd->owned = TRUE; + clip_get_selection(cbd); + cbd->owned = FALSE; + + // Convert register type into a string + if (cbd == &clip_plus) + y_ptr = get_y_register(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_request_selection(Clipboard_T *cbd, char_u *provider) +{ + char_u *reg = (char_u *)(cbd == &clip_star ? "*" : "+"); + callback_T callback; + typval_T argvars[3]; + 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_STRING; + argvars[1].vval.v_string = (char_u *) + (clip_access_type == CLIP_ACCESS_EXPLICIT ? "explicit" : "implicit"); + + argvars[2].v_type = VAR_UNKNOWN; + + textlock++; + ret = call_callback(&callback, -1, &rettv, 2, argvars); + textlock--; + + if (ret == FAIL) + goto exit; + else if (rettv.v_type == VAR_STRING + && STRCMP(rettv.vval.v_string, "clear") == 0) + { + clip_free_selection(cbd); + goto exit; + } + else if (rettv.v_type == VAR_STRING + && STRCMP(rettv.vval.v_string, "previous") == 0) + 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; + char_u **contents; + listitem_T *li; + int i = 0; + + contents = ALLOC_MULT(char_u *, lines->lv_len + 1); // Ends with a NULL + + if (contents == NULL) + goto exit; + + // Convert strings in list to type char_u ** + FOR_ALL_LIST_ITEMS(lines, li) + { + char_u *str = tv_get_string_chk(&li->li_tv); + + if (str == NULL) + goto exit; + + contents[i++] = vim_strsave(str); + } + contents[i] = NULL; + + if (STRLEN(reg_type) > 0 + && get_yank_type(®_type, &yank_type, &block_len) == FAIL) + goto exit; + + if (cbd == &clip_plus) + y_ptr = get_y_register(PLUS_REGISTER); + else + y_ptr = get_y_register(STAR_REGISTER); + + clip_free_selection(cbd); + + str_to_reg(y_ptr, + yank_type, + (char_u *)contents, + STRLEN(contents), + block_len, + TRUE); + + for (int k = 0; k < i; k++) + vim_free(contents[k]); + vim_free(contents); + } + +exit: + free_callback(&callback); + clear_tv(&rettv); +} + +#endif // FEAT_CLIPBOARD_PROVIDER + #endif // FEAT_CLIPBOARD diff --git a/src/config.h.in b/src/config.h.in index 983f186b8d1d72..eb0937d393d272 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -528,3 +528,6 @@ /* Define if you want to load libgpm dynamically */ #undef DYNAMIC_GPM + +/* Define if you want to have clipboard provider functionality*/ +#undef FEAT_CLIPBOARD_PROVIDER diff --git a/src/configure.ac b/src/configure.ac index fa6757d69f0c27..635b5f7222cc94 100644 --- a/src/configure.ac +++ b/src/configure.ac @@ -2434,6 +2434,21 @@ AC_ARG_ENABLE(fontset, AC_MSG_RESULT($enable_fontset) dnl defining FEAT_XFONTSET is delayed, so that it can be disabled for no GUI +AC_MSG_CHECKING(--enable-clipboard-provider) +AC_ARG_ENABLE(clipboard-provider, + [ --enable-clipboard-provider Include clipboard provider support.], + [enable_clipboard_provider=$enableval + AC_MSG_RESULT($enableval)], + AS_IF([test "x$features" = xtiny], + [enable_clipboard_provider="no" + AC_MSG_RESULT([cannot use clipboard provider with tiny features])], + [enable_clipboard_provider="yes" + AC_MSG_RESULT([yes])])) +if test "$enable_clipboard_provider" = "yes"; then + AC_DEFINE(FEAT_CLIPBOARD_PROVIDER) +fi + + AC_MSG_CHECKING(if shm_open is available) cppflags_save=$CPPFLAGS CPPFLAGS="$CPPFLAGS $X_CFLAGS" diff --git a/src/evalfunc.c b/src/evalfunc.c index 7c3139edc5de0f..e2e9bcbef94d92 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -6500,6 +6500,11 @@ f_getreg(typval_T *argvars, typval_T *rettv) return; } +#ifdef FEAT_CLIPBOARD_PROVIDER + if (clipmethod == CLIPMETHOD_PROVIDER) + clip_access_type = CLIP_ACCESS_EXPLICIT; +#endif + if (return_list) { rettv->v_type = VAR_LIST; @@ -6866,6 +6871,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}, @@ -11493,7 +11505,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 e529758ef32107..dff6e0ff4233dc 100644 --- a/src/evalvars.c +++ b/src/evalvars.c @@ -166,7 +166,8 @@ static struct vimvar {VV_NAME("wayland_display", VAR_STRING), NULL, VV_RO}, {VV_NAME("clipmethod", VAR_STRING), NULL, VV_RO}, {VV_NAME("termda1", VAR_STRING), NULL, VV_RO}, - {VV_NAME("termosc", VAR_STRING), NULL, VV_RO}, + {VV_NAME("termosc", VAR_STRING), NULL, VV_RO}, + {VV_NAME("clipproviders", VAR_DICT), &t_dict_string, VV_RO} }; // shorthand diff --git a/src/feature.h b/src/feature.h index 11700a060d6fa7..6860cbd981c4d8 100644 --- a/src/feature.h +++ b/src/feature.h @@ -905,6 +905,19 @@ # define FEAT_CLIPBOARD #endif +/* + * +clipboard_provider Allow Vim to use clipboard providers + */ +#if defined(FEAT_CLIPBOARD_PROVIDER) +# ifndef FEAT_EVAL +# undef FEAT_CLIPBOARD_PROVIDER +# else +# ifndef FEAT_CLIPBOARD +# define FEAT_CLIPBOARD +# endif +# endif +#endif + #ifdef FEAT_GUI # ifndef FEAT_CLIPBOARD # define FEAT_CLIPBOARD diff --git a/src/globals.h b/src/globals.h index 6eba836d442f28..0a705ef2502218 100644 --- a/src/globals.h +++ b/src/globals.h @@ -972,7 +972,8 @@ EXTERN int gui_win_y INIT(= -1); #ifdef FEAT_CLIPBOARD EXTERN Clipboard_T clip_star; // PRIMARY selection in X11/Wayland -# if defined(FEAT_X11) || defined(FEAT_WAYLAND_CLIPBOARD) +# if defined(FEAT_X11) || defined(FEAT_WAYLAND_CLIPBOARD) \ + || ((defined(UNIX) || defined(VMS)) && defined(FEAT_CLIPBOARD_PROVIDER)) EXTERN Clipboard_T clip_plus; // CLIPBOARD selection in X11/Wayland # else # define clip_plus clip_star // there is only one clipboard @@ -2067,6 +2068,9 @@ EXTERN int p_tgc_set INIT(= FALSE); #ifdef FEAT_CLIPBOARD EXTERN clipmethod_T clipmethod INIT(= CLIPMETHOD_NONE); +# ifdef FEAT_CLIPBOARD_PROVIDER +EXTERN char_u *clipprovider_name INIT(= NULL); +# endif #endif #ifdef FEAT_WAYLAND @@ -2116,3 +2120,16 @@ INIT(= CLIENTSERVER_METHOD_NONE); // Path to socket of last client that communicated with us EXTERN char_u *client_socket INIT(= NULL); #endif + +#ifdef FEAT_CLIPBOARD_PROVIDER +typedef enum +{ + CLIP_ACCESS_IMPLICIT, + CLIP_ACCESS_EXPLICIT, +} clip_access_T; + +// Only relevant for the clipboard provider feature. This indicates if the +// clipboard request is implicit (ex. access when doing :registers), +// explicit (ex. typing "+p). Always defaults to implicit. +EXTERN clip_access_T clip_access_type INIT(= CLIP_ACCESS_IMPLICIT); +#endif diff --git a/src/proto/evalfunc.pro b/src/proto/evalfunc.pro index 627af17a8ba68e..dad8c6771a801a 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 1b3f60ec541cad..8d0ed7ace3c9da 100644 --- a/src/register.c +++ b/src/register.c @@ -1559,6 +1559,10 @@ do_put( #ifdef FEAT_CLIPBOARD // Adjust register name for "unnamed" in 'clipboard'. adjust_clip_reg(®name); +# ifdef FEAT_CLIPBOARD_PROVIDER + if (clipmethod == CLIPMETHOD_PROVIDER) + clip_access_type = CLIP_ACCESS_EXPLICIT; +# endif (void)may_get_selection(regname); #endif diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak index 0d4aeb0432fb50..61ce33a0a74656 100644 --- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -105,6 +105,7 @@ NEW_TESTS = \ test_cjk_linebreak \ test_clientserver \ test_clipmethod \ + test_clipboard_provider \ test_close_count \ test_cmd_lists \ test_cmdline \ @@ -395,6 +396,7 @@ NEW_TESTS_RES = \ test_cjk_linebreak.res \ test_clientserver.res \ test_clipmethod.res \ + test_clipboard_provider.res \ test_close_count.res \ test_cmd_lists.res \ test_cmdline.res \ diff --git a/src/testdir/test_clipboard_provider.vim b/src/testdir/test_clipboard_provider.vim new file mode 100644 index 00000000000000..8cc55c3e35a489 --- /dev/null +++ b/src/testdir/test_clipboard_provider.vim @@ -0,0 +1,170 @@ +" Test for clipboard provider feature + +CheckFeature clipboard_provider + +func! AvailableBoth() + return "+*" +endfunc + +func! AvailablePlus() + return "+" +endfunc + +func! PasteList(reg, type) + return ["c", ["list"]] +endfunc + +func! PasteTuple(reg, type) + return ("", ["tuple", "of", "strings"]) +endfunc + +func! PasteType(reg, type) + let g:vim_test_type = a:type + return ("c", [a:type]) +endfunc + +func! PasteRegType(reg, type) + return (g:vim_test_reg_type, ["7 chars"]) +endfunc + +func! Copy(reg, type, lines) + let g:vim_test_stuff = { + \ "type": a:type, + \ "lines": a:lines + \ } +endfunc + +" Test if "available" function works properly for provider +func Test_clipboard_provider_available() + CheckUnix + let v:clipproviders["test"] = { + \ "available": function("AvailablePlus"), + \ "paste": { + \ '+': function("PasteList"), + \ '*': function("PasteList") + \ } + \ } + + set clipmethod=test + call assert_equal("test", v:clipmethod) + + call assert_equal("list", getreg("+")) + " Test if star register is unavailable + call assert_equal("", getreg("*")) + + let v:clipproviders["test"] = { + \ "available": function("AvailableBoth"), + \ "paste": { + \ '+': function("PasteList"), + \ '*': function("PasteList") + \ } + \ } + + clipreset + + call assert_equal("list", getreg("+")) + call assert_equal("list", getreg("*")) + + let v:clipproviders["test"] = { + \ "paste": { + \ '+': function("PasteList"), + \ '*': function("PasteList") + \ } + \ } + + " Should default to TRUE + call assert_equal("list", getreg("+")) + call assert_equal("list", getreg("*")) + + set clipmethod& +endfunc + +" Test if "paste" functions work properly for provider +func Test_clipboard_provider_paste() + " Test if tuples and lists work the same + let v:clipproviders["test"] = { + \ "paste": { + \ '*': function("PasteList") + \ } + \ } + + set clipmethod=test + call assert_equal("test", v:clipmethod) + + call assert_equal("list", getreg("*")) + + let v:clipproviders["test"] = { + \ "paste": { + \ '*': function("PasteTuple") + \ } + \ } + + call assert_equal("tuple\nof\nstrings\n", getreg("*")) + + " Test if "implicit" and "explicit" arguments are correctly used + let v:clipproviders["test"] = { + \ "paste": { + \ '*': function("PasteType") + \ } + \ } + + call assert_equal("explicit", getreg("*")) + + :registers + + call assert_equal("implicit", g:vim_test_type) + unlet g:vim_test_type + + " Test if correct register type is used + let v:clipproviders["test"] = { + \ "paste": { + \ '*': function("PasteRegType") + \ } + \ } + + let g:vim_test_reg_type = "v" + call assert_equal("v", getregtype("*")) + let g:vim_test_reg_type = "c" + call assert_equal("v", getregtype("*")) + + let g:vim_test_reg_type = "l" + call assert_equal("V", getregtype("*")) + let g:vim_test_reg_type = "l" + call assert_equal("V", getregtype("*")) + + let g:vim_test_reg_type = "b" + call assert_equal("7", getregtype("*")) + let g:vim_test_reg_type = "" + call assert_equal("7", getregtype("*")) + + let g:vim_test_reg_type = "b40" + call assert_equal("40", getregtype("*")) + + set clipmethod& +endfunc + +" Test if "copy" functions work properly for provider +func Test_clipboard_provider_copy() + let v:clipproviders["test"] = { + \ "copy": { + \ '*': function("Copy") + \ } + \ } + + set clipmethod=test + call assert_equal("test", v:clipmethod) + + call setreg("*", ["hello", "world", "!"], "c") + call assert_equal(["hello", "world", "!"], g:vim_test_stuff.lines) + call assert_equal("v", g:vim_test_stuff.type) + + call setreg("*", ["hello", "world", "!"], "l") + call assert_equal(["hello", "world", "!"], g:vim_test_stuff.lines) + call assert_equal("V", g:vim_test_stuff.type) + + call setreg("*", ["hello", "world", "!"], "b40") + call assert_equal(["hello", "world", "!"], g:vim_test_stuff.lines) + call assert_equal("40", g:vim_test_stuff.type) + + set clipmethod& +endfunc diff --git a/src/version.c b/src/version.c index 47d7e9f7585d91..d29d5c5c6d5fe1 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", diff --git a/src/vim.h b/src/vim.h index 69be64270274aa..6c35fe1ef43e04 100644 --- a/src/vim.h +++ b/src/vim.h @@ -2256,7 +2256,8 @@ typedef int sock_T; #define VV_CLIPMETHOD 113 #define VV_TERMDA1 114 #define VV_TERMOSC 115 -#define VV_LEN 116 // number of v: vars +#define VV_CLIPPROVIDERS 116 +#define VV_LEN 117 // number of v: vars // used for v_number in VAR_BOOL and VAR_SPECIAL #define VVAL_FALSE 0L // VAR_BOOL @@ -2318,6 +2319,7 @@ typedef enum { CLIPMETHOD_X11, CLIPMETHOD_GUI, CLIPMETHOD_OTHER, + CLIPMETHOD_PROVIDER } clipmethod_T; // Info about selected text