mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 09:44:31 +00:00 
			
		
		
		
	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:
		
				
					committed by
					
						
						Justin M. Keyes
					
				
			
			
				
	
			
			
			
						parent
						
							b19403e73e
						
					
				
				
					commit
					07ad5d71ab
				
			@@ -55,11 +55,22 @@ endfunction
 | 
				
			|||||||
function! provider#clipboard#Executable() abort
 | 
					function! provider#clipboard#Executable() abort
 | 
				
			||||||
  if exists('g:clipboard')
 | 
					  if exists('g:clipboard')
 | 
				
			||||||
    if type({}) isnot# type(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'
 | 
					      let s:err = 'clipboard: invalid g:clipboard'
 | 
				
			||||||
      return ''
 | 
					      return ''
 | 
				
			||||||
    endif
 | 
					    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:copy = get(g:clipboard, 'copy', { '+': v:null, '*': v:null })
 | 
				
			||||||
    let s:paste = get(g:clipboard, 'paste', { '+': 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)
 | 
					    let s:cache_enabled = get(g:clipboard, 'cache_enabled', 0)
 | 
				
			||||||
@@ -127,7 +138,9 @@ if empty(provider#clipboard#Executable())
 | 
				
			|||||||
endif
 | 
					endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function! s:clipboard.get(reg) abort
 | 
					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
 | 
					    return s:selections[a:reg].data
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
  return s:try_cmd(s:paste[a:reg])
 | 
					  return s:try_cmd(s:paste[a:reg])
 | 
				
			||||||
@@ -141,6 +154,12 @@ function! s:clipboard.set(lines, regtype, reg) abort
 | 
				
			|||||||
    end
 | 
					    end
 | 
				
			||||||
    return 0
 | 
					    return 0
 | 
				
			||||||
  end
 | 
					  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
 | 
					  if s:cache_enabled == 0
 | 
				
			||||||
    call s:try_cmd(s:copy[a:reg], a:lines)
 | 
					    call s:try_cmd(s:copy[a:reg], a:lines)
 | 
				
			||||||
    return 0
 | 
					    return 0
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -160,7 +160,9 @@ registers. Nvim looks for these clipboard tools, in order of priority:
 | 
				
			|||||||
  - tmux (if $TMUX is set)
 | 
					  - tmux (if $TMUX is set)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
								 *g:clipboard*
 | 
													 *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 = {
 | 
					    let g:clipboard = {
 | 
				
			||||||
          \   'name': 'myClipboard',
 | 
					          \   'name': 'myClipboard',
 | 
				
			||||||
          \   'copy': {
 | 
					          \   'copy': {
 | 
				
			||||||
@@ -174,9 +176,28 @@ To configure a custom clipboard tool, set `g:clipboard` to a dictionary: >
 | 
				
			|||||||
          \   'cache_enabled': 1,
 | 
					          \   '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
 | 
					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*
 | 
					X11 selection mechanism			      *clipboard-x11* *x11-selection*
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@
 | 
				
			|||||||
local helpers = require('test.functional.helpers')(after_each)
 | 
					local helpers = require('test.functional.helpers')(after_each)
 | 
				
			||||||
local Screen = require('test.functional.ui.screen')
 | 
					local Screen = require('test.functional.ui.screen')
 | 
				
			||||||
local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
 | 
					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 command = helpers.command
 | 
				
			||||||
local meths = helpers.meths
 | 
					local meths = helpers.meths
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -147,6 +147,93 @@ describe('clipboard', function()
 | 
				
			|||||||
    eq('clippy!', eval('provider#clipboard#Executable()'))
 | 
					    eq('clippy!', eval('provider#clipboard#Executable()'))
 | 
				
			||||||
    eq('', eval('provider#clipboard#Error()'))
 | 
					    eq('', eval('provider#clipboard#Error()'))
 | 
				
			||||||
  end)
 | 
					  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)
 | 
					end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('clipboard', function()
 | 
					describe('clipboard', function()
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user