define.vim (8373B)
1 function! remote#define#CommandOnHost(host, method, sync, name, opts) 2 let prefix = '' 3 4 if has_key(a:opts, 'range') 5 if a:opts.range == '' || a:opts.range == '%' 6 " -range or -range=%, pass the line range in a list 7 let prefix = '<line1>,<line2>' 8 elseif matchstr(a:opts.range, '\d') != '' 9 " -range=N, pass the count 10 let prefix = '<count>' 11 endif 12 elseif has_key(a:opts, 'count') 13 let prefix = '<count>' 14 endif 15 16 let forward_args = [prefix.a:name] 17 18 if has_key(a:opts, 'bang') 19 call add(forward_args, '<bang>') 20 endif 21 22 if has_key(a:opts, 'register') 23 call add(forward_args, ' <register>') 24 endif 25 26 if has_key(a:opts, 'nargs') 27 call add(forward_args, ' " . <q-args> . "') 28 endif 29 30 exe s:GetCommandPrefix(a:name, a:opts) 31 \ .' call remote#define#CommandBootstrap("'.a:host.'"' 32 \ . ', "'.a:method.'"' 33 \ . ', '.string(a:sync) 34 \ . ', "'.a:name.'"' 35 \ . ', '.string(a:opts).'' 36 \ . ', "'.join(forward_args, '').'"' 37 \ . ')' 38 endfunction 39 40 41 function! remote#define#CommandBootstrap(host, method, sync, name, opts, forward) 42 let channel = remote#host#Require(a:host) 43 44 if channel 45 call remote#define#CommandOnChannel(channel, a:method, a:sync, a:name, a:opts) 46 exe a:forward 47 else 48 exe 'delcommand '.a:name 49 echoerr 'Host "'a:host.'" is not available, deleting command "'.a:name.'"' 50 endif 51 endfunction 52 53 54 function! remote#define#CommandOnChannel(channel, method, sync, name, opts) 55 let rpcargs = [a:channel, '"'.a:method.'"'] 56 if has_key(a:opts, 'nargs') 57 " -nargs, pass arguments in a list 58 call add(rpcargs, '[<f-args>]') 59 endif 60 61 if has_key(a:opts, 'range') 62 if a:opts.range == '' || a:opts.range == '%' 63 " -range or -range=%, pass the line range in a list 64 call add(rpcargs, '[<line1>, <line2>]') 65 elseif matchstr(a:opts.range, '\d') != '' 66 " -range=N, pass the count 67 call add(rpcargs, '<count>') 68 endif 69 elseif has_key(a:opts, 'count') 70 " count 71 call add(rpcargs, '<count>') 72 endif 73 74 if has_key(a:opts, 'bang') 75 " bang 76 call add(rpcargs, '<q-bang> == "!"') 77 endif 78 79 if has_key(a:opts, 'register') 80 " register 81 call add(rpcargs, '<q-reg>') 82 endif 83 84 call s:AddEval(rpcargs, a:opts) 85 exe s:GetCommandPrefix(a:name, a:opts) 86 \ . ' call '.s:GetRpcFunction(a:sync).'('.join(rpcargs, ', ').')' 87 endfunction 88 89 90 function! remote#define#AutocmdOnHost(host, method, sync, name, opts) 91 let group = s:GetNextAutocmdGroup() 92 let forward = '"doau '.group.' '.a:name.' ".' 93 \ . 'fnameescape(expand("<amatch>"))' 94 let a:opts.group = group 95 let bootstrap_def = s:GetAutocmdPrefix(a:name, a:opts) 96 \ .' call remote#define#AutocmdBootstrap("'.a:host.'"' 97 \ . ', "'.a:method.'"' 98 \ . ', '.string(a:sync) 99 \ . ', "'.a:name.'"' 100 \ . ', '.string(a:opts).'' 101 \ . ', "'.escape(forward, '"').'"' 102 \ . ')' 103 exe bootstrap_def 104 endfunction 105 106 107 function! remote#define#AutocmdBootstrap(host, method, sync, name, opts, forward) 108 let channel = remote#host#Require(a:host) 109 110 exe 'autocmd! '.a:opts.group 111 if channel 112 call remote#define#AutocmdOnChannel(channel, a:method, a:sync, a:name, 113 \ a:opts) 114 exe eval(a:forward) 115 else 116 exe 'augroup! '.a:opts.group 117 echoerr 'Host "'a:host.'" for "'.a:name.'" autocmd is not available' 118 endif 119 endfunction 120 121 122 function! remote#define#AutocmdOnChannel(channel, method, sync, name, opts) 123 let rpcargs = [a:channel, '"'.a:method.'"'] 124 call s:AddEval(rpcargs, a:opts) 125 126 let autocmd_def = s:GetAutocmdPrefix(a:name, a:opts) 127 \ . ' call '.s:GetRpcFunction(a:sync).'('.join(rpcargs, ', ').')' 128 exe autocmd_def 129 endfunction 130 131 132 function! remote#define#FunctionOnHost(host, method, sync, name, opts) 133 let group = s:GetNextAutocmdGroup() 134 exe 'autocmd! '.group.' FuncUndefined '.a:name 135 \ .' call remote#define#FunctionBootstrap("'.a:host.'"' 136 \ . ', "'.a:method.'"' 137 \ . ', '.string(a:sync) 138 \ . ', "'.a:name.'"' 139 \ . ', '.string(a:opts) 140 \ . ', "'.group.'"' 141 \ . ')' 142 endfunction 143 144 145 function! remote#define#FunctionBootstrap(host, method, sync, name, opts, group) 146 let channel = remote#host#Require(a:host) 147 148 exe 'autocmd! '.a:group 149 exe 'augroup! '.a:group 150 if channel 151 call remote#define#FunctionOnChannel(channel, a:method, a:sync, a:name, 152 \ a:opts) 153 else 154 echoerr 'Host "'a:host.'" for "'.a:name.'" function is not available' 155 endif 156 endfunction 157 158 159 function! remote#define#FunctionOnChannel(channel, method, sync, name, opts) 160 let rpcargs = [a:channel, '"'.a:method.'"', 'a:000'] 161 if has_key(a:opts, 'range') 162 call add(rpcargs, '[a:firstline, a:lastline]') 163 endif 164 call s:AddEval(rpcargs, a:opts) 165 166 let function_def = s:GetFunctionPrefix(a:name, a:opts) 167 \ . 'return '.s:GetRpcFunction(a:sync).'('.join(rpcargs, ', ').')' 168 \ . "\nendfunction" 169 exe function_def 170 endfunction 171 172 let s:busy = {} 173 let s:pending_notifications = {} 174 175 function! s:GetRpcFunction(sync) 176 if a:sync ==# 'urgent' 177 return 'rpcnotify' 178 elseif a:sync 179 return 'remote#define#request' 180 endif 181 return 'remote#define#notify' 182 endfunction 183 184 function! remote#define#notify(chan, ...) 185 if get(s:busy, a:chan, 0) > 0 186 let pending = get(s:pending_notifications, a:chan, []) 187 call add(pending, deepcopy(a:000)) 188 let s:pending_notifications[a:chan] = pending 189 else 190 call call('rpcnotify', [a:chan] + a:000) 191 endif 192 endfunction 193 194 function! remote#define#request(chan, ...) 195 let s:busy[a:chan] = get(s:busy, a:chan, 0)+1 196 let val = call('rpcrequest', [a:chan]+a:000) 197 let s:busy[a:chan] -= 1 198 if s:busy[a:chan] == 0 199 for msg in get(s:pending_notifications, a:chan, []) 200 call call('rpcnotify', [a:chan] + msg) 201 endfor 202 let s:pending_notifications[a:chan] = [] 203 endif 204 return val 205 endfunction 206 207 function! s:GetCommandPrefix(name, opts) 208 return 'command!'.s:StringifyOpts(a:opts, ['nargs', 'complete', 'range', 209 \ 'count', 'bang', 'bar', 'register']).' '.a:name 210 endfunction 211 212 213 " Each msgpack-rpc autocommand has it's own unique group, which is derived 214 " from an autoincrementing gid(group id). This is required for replacing the 215 " autocmd implementation with the lazy-load mechanism 216 let s:next_gid = 1 217 function! s:GetNextAutocmdGroup() 218 let gid = s:next_gid 219 let s:next_gid += 1 220 221 let group_name = 'RPC_DEFINE_AUTOCMD_GROUP_'.gid 222 " Ensure the group is defined 223 exe 'augroup '.group_name.' | augroup END' 224 return group_name 225 endfunction 226 227 228 function! s:GetAutocmdPrefix(name, opts) 229 if has_key(a:opts, 'group') 230 let group = a:opts.group 231 else 232 let group = s:GetNextAutocmdGroup() 233 endif 234 let rv = ['autocmd!', group, a:name] 235 236 if has_key(a:opts, 'pattern') 237 call add(rv, a:opts.pattern) 238 else 239 call add(rv, '*') 240 endif 241 242 if has_key(a:opts, 'nested') && a:opts.nested 243 call add(rv, '++nested') 244 endif 245 246 if has_key(a:opts, 'once') && a:opts.once 247 call add(rv, '++once') 248 endif 249 250 return join(rv, ' ') 251 endfunction 252 253 254 function! s:GetFunctionPrefix(name, opts) 255 let res = "function! ".a:name."(...)" 256 if has_key(a:opts, 'range') 257 let res = res." range" 258 endif 259 return res."\n" 260 endfunction 261 262 263 function! s:StringifyOpts(opts, keys) 264 let rv = [] 265 for key in a:keys 266 if has_key(a:opts, key) 267 call add(rv, ' -'.key) 268 let val = a:opts[key] 269 if type(val) != type('') || val != '' 270 call add(rv, '='.val) 271 endif 272 endif 273 endfor 274 return join(rv, '') 275 endfunction 276 277 278 function! s:AddEval(rpcargs, opts) 279 if has_key(a:opts, 'eval') 280 if type(a:opts.eval) != type('') || a:opts.eval == '' 281 throw "Eval option must be a non-empty string" 282 endif 283 " evaluate an expression and pass as argument 284 call add(a:rpcargs, 'eval("'.escape(a:opts.eval, '"').'")') 285 endif 286 endfunction