aboutsummaryrefslogtreecommitdiff
path: root/runtime/autoload/remote/define.vim
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/autoload/remote/define.vim')
-rw-r--r--runtime/autoload/remote/define.vim248
1 files changed, 248 insertions, 0 deletions
diff --git a/runtime/autoload/remote/define.vim b/runtime/autoload/remote/define.vim
new file mode 100644
index 0000000000..dd2482998d
--- /dev/null
+++ b/runtime/autoload/remote/define.vim
@@ -0,0 +1,248 @@
+function! remote#define#CommandOnHost(host, method, sync, name, opts)
+ let prefix = ''
+
+ if has_key(a:opts, 'range')
+ if a:opts.range == '' || a:opts.range == '%'
+ " -range or -range=%, pass the line range in a list
+ let prefix = '<line1>,<line2>'
+ elseif matchstr(a:opts.range, '\d') != ''
+ " -range=N, pass the count
+ let prefix = '<count>'
+ endif
+ elseif has_key(a:opts, 'count')
+ let prefix = '<count>'
+ endif
+
+ let forward_args = [prefix.a:name]
+
+ if has_key(a:opts, 'bang')
+ call add(forward_args, '<bang>')
+ endif
+
+ if has_key(a:opts, 'register')
+ call add(forward_args, ' <register>')
+ endif
+
+ if has_key(a:opts, 'nargs')
+ call add(forward_args, ' <args>')
+ endif
+
+ exe s:GetCommandPrefix(a:name, a:opts)
+ \ .' call remote#define#CommandBootstrap("'.a:host.'"'
+ \ . ', "'.a:method.'"'
+ \ . ', "'.a:sync.'"'
+ \ . ', "'.a:name.'"'
+ \ . ', '.string(a:opts).''
+ \ . ', "'.join(forward_args, '').'"'
+ \ . ')'
+endfunction
+
+
+function! remote#define#CommandBootstrap(host, method, sync, name, opts, forward)
+ let channel = remote#host#Require(a:host)
+
+ if channel
+ call remote#define#CommandOnChannel(channel, a:method, a:sync, a:name, a:opts)
+ exe a:forward
+ else
+ exe 'delcommand '.a:name
+ echoerr 'Host "'a:host.'" is not available, deleting command "'.a:name.'"'
+ endif
+endfunction
+
+
+function! remote#define#CommandOnChannel(channel, method, sync, name, opts)
+ let rpcargs = [a:channel, '"'.a:method.'"']
+ if has_key(a:opts, 'nargs')
+ " -nargs, pass arguments in a list
+ call add(rpcargs, '[<f-args>]')
+ endif
+
+ if has_key(a:opts, 'range')
+ if a:opts.range == '' || a:opts.range == '%'
+ " -range or -range=%, pass the line range in a list
+ call add(rpcargs, '[<line1>, <line2>]')
+ elseif matchstr(a:opts.range, '\d') != ''
+ " -range=N, pass the count
+ call add(rpcargs, '<count>')
+ endif
+ elseif has_key(a:opts, 'count')
+ " count
+ call add(rpcargs, '<count>')
+ endif
+
+ if has_key(a:opts, 'bang')
+ " bang
+ call add(rpcargs, '<q-bang> == "!"')
+ endif
+
+ if has_key(a:opts, 'register')
+ " register
+ call add(rpcargs, '<q-reg>')
+ endif
+
+ call s:AddEval(rpcargs, a:opts)
+ exe s:GetCommandPrefix(a:name, a:opts)
+ \ . ' call '.s:GetRpcFunction(a:sync).'('.join(rpcargs, ', ').')'
+endfunction
+
+
+function! remote#define#AutocmdOnHost(host, method, sync, name, opts)
+ let group = s:GetNextAutocmdGroup()
+ let forward = '"doau '.group.' '.a:name.' ".'.'expand("<amatch>")'
+ let a:opts.group = group
+ let bootstrap_def = s:GetAutocmdPrefix(a:name, a:opts)
+ \ .' call remote#define#AutocmdBootstrap("'.a:host.'"'
+ \ . ', "'.a:method.'"'
+ \ . ', "'.a:sync.'"'
+ \ . ', "'.a:name.'"'
+ \ . ', '.string(a:opts).''
+ \ . ', "'.escape(forward, '"').'"'
+ \ . ')'
+ exe bootstrap_def
+endfunction
+
+
+function! remote#define#AutocmdBootstrap(host, method, sync, name, opts, forward)
+ let channel = remote#host#Require(a:host)
+
+ exe 'autocmd! '.a:opts.group
+ if channel
+ call remote#define#AutocmdOnChannel(channel, a:method, a:sync, a:name,
+ \ a:opts)
+ exe eval(a:forward)
+ else
+ exe 'augroup! '.a:opts.group
+ echoerr 'Host "'a:host.'" for "'.a:name.'" autocmd is not available'
+ endif
+endfunction
+
+
+function! remote#define#AutocmdOnChannel(channel, method, sync, name, opts)
+ let rpcargs = [a:channel, '"'.a:method.'"']
+ call s:AddEval(rpcargs, a:opts)
+
+ let autocmd_def = s:GetAutocmdPrefix(a:name, a:opts)
+ \ . ' call '.s:GetRpcFunction(a:sync).'('.join(rpcargs, ', ').')'
+ exe autocmd_def
+endfunction
+
+
+function! remote#define#FunctionOnHost(host, method, sync, name, opts)
+ let group = s:GetNextAutocmdGroup()
+ exe 'autocmd! '.group.' FuncUndefined '.a:name
+ \ .' call remote#define#FunctionBootstrap("'.a:host.'"'
+ \ . ', "'.a:method.'"'
+ \ . ', "'.a:sync.'"'
+ \ . ', "'.a:name.'"'
+ \ . ', '.string(a:opts)
+ \ . ', "'.group.'"'
+ \ . ')'
+endfunction
+
+
+function! remote#define#FunctionBootstrap(host, method, sync, name, opts, group)
+ let channel = remote#host#Require(a:host)
+
+ exe 'autocmd! '.a:group
+ exe 'augroup! '.a:group
+ if channel
+ call remote#define#FunctionOnChannel(channel, a:method, a:sync, a:name,
+ \ a:opts)
+ else
+ echoerr 'Host "'a:host.'" for "'.a:name.'" function is not available'
+ endif
+endfunction
+
+
+function! remote#define#FunctionOnChannel(channel, method, sync, name, opts)
+ let rpcargs = [a:channel, '"'.a:method.'"', 'a:000']
+ call s:AddEval(rpcargs, a:opts)
+
+ let function_def = s:GetFunctionPrefix(a:name, a:opts)
+ \ . 'return '.s:GetRpcFunction(a:sync).'('.join(rpcargs, ', ').')'
+ \ . "\nendfunction"
+ exe function_def
+endfunction
+
+
+function! s:GetRpcFunction(sync)
+ if a:sync
+ return 'rpcrequest'
+ endif
+ return 'rpcnotify'
+endfunction
+
+
+function! s:GetCommandPrefix(name, opts)
+ return 'command!'.s:StringifyOpts(a:opts, ['nargs', 'complete', 'range',
+ \ 'count', 'bang', 'bar', 'register']).' '.a:name
+endfunction
+
+
+" Each msgpack-rpc autocommand has it's own unique group, which is derived
+" from an autoincrementing gid(group id). This is required for replacing the
+" autocmd implementation with the lazy-load mechanism
+let s:next_gid = 1
+function! s:GetNextAutocmdGroup()
+ let gid = s:next_gid
+ let s:next_gid += 1
+
+ let group_name = 'RPC_DEFINE_AUTOCMD_GROUP_'.gid
+ " Ensure the group is defined
+ exe 'augroup '.group_name.' | augroup END'
+ return group_name
+endfunction
+
+
+function! s:GetAutocmdPrefix(name, opts)
+ if has_key(a:opts, 'group')
+ let group = a:opts.group
+ else
+ let group = s:GetNextAutocmdGroup()
+ endif
+ let rv = ['autocmd!', group, a:name]
+
+ if has_key(a:opts, 'pattern')
+ call add(rv, a:opts.pattern)
+ else
+ call add(rv, '*')
+ endif
+
+ if has_key(a:opts, 'nested') && a:opts.nested
+ call add(rv, 'nested')
+ endif
+
+ return join(rv, ' ')
+endfunction
+
+
+function! s:GetFunctionPrefix(name, opts)
+ return "function! ".a:name."(...)\n"
+endfunction
+
+
+function! s:StringifyOpts(opts, keys)
+ let rv = []
+ for key in a:keys
+ if has_key(a:opts, key)
+ call add(rv, ' -'.key)
+ let val = a:opts[key]
+ if type(val) != type('') || val != ''
+ call add(rv, '='.val)
+ endif
+ endif
+ endfor
+ return join(rv, '')
+endfunction
+
+
+function! s:AddEval(rpcargs, opts)
+ if has_key(a:opts, 'eval')
+ if type(a:opts.eval) != type('') || a:opts.eval == ''
+ throw "Eval option must be a non-empty string"
+ endif
+ " evaluate an expression and pass as argument
+ call add(a:rpcargs, 'eval("'.escape(a:opts.eval, '"').'")')
+ endif
+endfunction