clipboard: Support custom VimL functions #9304

Up to now g:clipboard["copy"] only supported string values invoked as
system commands.

This commit enables the use of VimL functions instead. The function
signatures are the same as in provider/clipboard.vim. A clipboard
provider is expected to store and return a list of lines (i.e. the text)
and a register type (as seen in setreg()).

cache_enabled is ignored if "copy" is provided by a VimL function.
This commit is contained in:
Rui Abreu Ferreira 2018-12-01 15:30:50 +00:00 committed by Justin M. Keyes
parent b19403e73e
commit 07ad5d71ab
3 changed files with 134 additions and 7 deletions

View File

@ -55,11 +55,22 @@ endfunction
function! provider#clipboard#Executable() abort
if exists('g:clipboard')
if type({}) isnot# type(g:clipboard)
\ || type({}) isnot# type(get(g:clipboard, 'copy', v:null))
\ || type({}) isnot# type(get(g:clipboard, 'paste', v:null))
let s:err = 'clipboard: invalid g:clipboard'
return ''
endif
if type(get(g:clipboard, 'copy', v:null)) isnot# v:t_dict
\ && type(get(g:clipboard, 'copy', v:null)) isnot# v:t_func
let s:err = "clipboard: invalid g:clipboard['copy']"
return ''
endif
if type(get(g:clipboard, 'paste', v:null)) isnot# v:t_dict
\ && type(get(g:clipboard, 'paste', v:null)) isnot# v:t_func
let s:err = "clipboard: invalid g:clipboard['paste']"
return ''
endif
let s:copy = get(g:clipboard, 'copy', { '+': v:null, '*': v:null })
let s:paste = get(g:clipboard, 'paste', { '+': v:null, '*': v:null })
let s:cache_enabled = get(g:clipboard, 'cache_enabled', 0)
@ -127,7 +138,9 @@ if empty(provider#clipboard#Executable())
endif
function! s:clipboard.get(reg) abort
if s:selections[a:reg].owner > 0
if type(s:paste[a:reg]) == v:t_func
return s:paste[a:reg]()
elseif s:selections[a:reg].owner > 0
return s:selections[a:reg].data
end
return s:try_cmd(s:paste[a:reg])
@ -141,6 +154,12 @@ function! s:clipboard.set(lines, regtype, reg) abort
end
return 0
end
if type(s:copy[a:reg]) == v:t_func
call s:copy[a:reg](a:lines, a:regtype)
return 0
end
if s:cache_enabled == 0
call s:try_cmd(s:copy[a:reg], a:lines)
return 0

View File

@ -160,7 +160,9 @@ registers. Nvim looks for these clipboard tools, in order of priority:
- tmux (if $TMUX is set)
*g:clipboard*
To configure a custom clipboard tool, set `g:clipboard` to a dictionary: >
To configure a custom clipboard tool, set g:clipboard to a dictionary.
For example this configuration integrates the tmux clipboard: >
let g:clipboard = {
\ 'name': 'myClipboard',
\ 'copy': {
@ -174,9 +176,28 @@ To configure a custom clipboard tool, set `g:clipboard` to a dictionary: >
\ 'cache_enabled': 1,
\ }
If `cache_enabled` is |TRUE| then when a selection is copied, Nvim will cache
If "cache_enabled" is |TRUE| then when a selection is copied Nvim will cache
the selection until the copy command process dies. When pasting, if the copy
process has not died, the cached selection is applied.
process has not died the cached selection is applied.
g:clipboard can also use functions (see |lambda|) instead of strings.
For example this configuration uses the g:foo variable as a fake clipboard: >
let g:clipboard = {
\ 'name': 'myClipboard',
\ 'copy': {
\ '+': {lines, regtype -> extend(g:, {'foo': [lines, regtype]}) },
\ '*': {lines, regtype -> extend(g:, {'foo': [lines, regtype]}) },
\ },
\ 'paste': {
\ '+': {-> get(g:, 'foo', [])},
\ '*': {-> get(g:, 'foo', [])},
\ },
\ }
The "copy" function stores a list of lines and the register type. The "paste"
function returns the clipboard as a `[lines, regtype]` list, where `lines` is
a list of lines and `regtype` is a register type conforming to |setreg()|.
==============================================================================
X11 selection mechanism *clipboard-x11* *x11-selection*

View File

@ -3,7 +3,7 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
local feed_command, expect, eq, eval = helpers.feed_command, helpers.expect, helpers.eq, helpers.eval
local feed_command, expect, eq, eval, source = helpers.feed_command, helpers.expect, helpers.eq, helpers.eval, helpers.source
local command = helpers.command
local meths = helpers.meths
@ -147,6 +147,93 @@ describe('clipboard', function()
eq('clippy!', eval('provider#clipboard#Executable()'))
eq('', eval('provider#clipboard#Error()'))
end)
it('g:clipboard using VimL functions', function()
-- Implements a fake clipboard provider. cache_enabled is meaningless here.
source([[let g:clipboard = {
\ 'name': 'custom',
\ 'copy': {
\ '+': {lines, regtype -> extend(g:, {'dummy_clipboard_plus': [lines, regtype]}) },
\ '*': {lines, regtype -> extend(g:, {'dummy_clipboard_star': [lines, regtype]}) },
\ },
\ 'paste': {
\ '+': {-> get(g:, 'dummy_clipboard_plus', [])},
\ '*': {-> get(g:, 'dummy_clipboard_star', [])},
\ },
\ 'cache_enabled': 1,
\}]])
eq('', eval('provider#clipboard#Error()'))
eq('custom', eval('provider#clipboard#Executable()'))
eq('', eval("getreg('*')"))
eq('', eval("getreg('+')"))
command('call setreg("*", "star")')
command('call setreg("+", "plus")')
eq('star', eval("getreg('*')"))
eq('plus', eval("getreg('+')"))
command('call setreg("*", "star", "v")')
eq({{'star'}, 'v'}, eval("g:dummy_clipboard_star"))
command('call setreg("*", "star", "V")')
eq({{'star', ''}, 'V'}, eval("g:dummy_clipboard_star"))
command('call setreg("*", "star", "b")')
eq({{'star', ''}, 'b'}, eval("g:dummy_clipboard_star"))
end)
describe('g:clipboard[paste] VimL function', function()
it('can return empty list for empty clipboard', function()
source([[let g:dummy_clipboard = []
let g:clipboard = {
\ 'name': 'custom',
\ 'copy': { '*': {lines, regtype -> 0} },
\ 'paste': { '*': {-> g:dummy_clipboard} },
\}]])
eq('', eval('provider#clipboard#Error()'))
eq('custom', eval('provider#clipboard#Executable()'))
eq('', eval("getreg('*')"))
end)
it('can return a list with a single string', function()
source([=[let g:dummy_clipboard = ['hello']
let g:clipboard = {
\ 'name': 'custom',
\ 'copy': { '*': {lines, regtype -> 0} },
\ 'paste': { '*': {-> g:dummy_clipboard} },
\}]=])
eq('', eval('provider#clipboard#Error()'))
eq('custom', eval('provider#clipboard#Executable()'))
eq('hello', eval("getreg('*')"))
source([[let g:dummy_clipboard = [''] ]])
eq('', eval("getreg('*')"))
end)
it('can return a list of lines if a regtype is provided', function()
source([=[let g:dummy_clipboard = [['hello'], 'v']
let g:clipboard = {
\ 'name': 'custom',
\ 'copy': { '*': {lines, regtype -> 0} },
\ 'paste': { '*': {-> g:dummy_clipboard} },
\}]=])
eq('', eval('provider#clipboard#Error()'))
eq('custom', eval('provider#clipboard#Executable()'))
eq('hello', eval("getreg('*')"))
end)
it('can return a list of lines instead of [lines, regtype]', function()
source([=[let g:dummy_clipboard = ['hello', 'v']
let g:clipboard = {
\ 'name': 'custom',
\ 'copy': { '*': {lines, regtype -> 0} },
\ 'paste': { '*': {-> g:dummy_clipboard} },
\}]=])
eq('', eval('provider#clipboard#Error()'))
eq('custom', eval('provider#clipboard#Executable()'))
eq('hello\nv', eval("getreg('*')"))
end)
end)
end)
describe('clipboard', function()