]> git.madduck.net Git - etc/vim.git/blob - autoload/vital/lsp.vim

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Squashed '.vim/bundle/vim-lsp/' content from commit 04428c92
[etc/vim.git] / autoload / vital / lsp.vim
1 let s:plugin_name = expand('<sfile>:t:r')
2 let s:vital_base_dir = expand('<sfile>:h')
3 let s:project_root = expand('<sfile>:h:h:h')
4 let s:is_vital_vim = s:plugin_name is# 'vital'
5
6 let s:loaded = {}
7 let s:cache_sid = {}
8
9 function! vital#{s:plugin_name}#new() abort
10   return s:new(s:plugin_name)
11 endfunction
12
13 function! vital#{s:plugin_name}#import(...) abort
14   if !exists('s:V')
15     let s:V = s:new(s:plugin_name)
16   endif
17   return call(s:V.import, a:000, s:V)
18 endfunction
19
20 let s:Vital = {}
21
22 function! s:new(plugin_name) abort
23   let base = deepcopy(s:Vital)
24   let base._plugin_name = a:plugin_name
25   return base
26 endfunction
27
28 function! s:vital_files() abort
29   if !exists('s:vital_files')
30     let s:vital_files = map(
31     \   s:is_vital_vim ? s:_global_vital_files() : s:_self_vital_files(),
32     \   'fnamemodify(v:val, ":p:gs?[\\\\/]?/?")')
33   endif
34   return copy(s:vital_files)
35 endfunction
36 let s:Vital.vital_files = function('s:vital_files')
37
38 function! s:import(name, ...) abort dict
39   let target = {}
40   let functions = []
41   for a in a:000
42     if type(a) == type({})
43       let target = a
44     elseif type(a) == type([])
45       let functions = a
46     endif
47     unlet a
48   endfor
49   let module = self._import(a:name)
50   if empty(functions)
51     call extend(target, module, 'keep')
52   else
53     for f in functions
54       if has_key(module, f) && !has_key(target, f)
55         let target[f] = module[f]
56       endif
57     endfor
58   endif
59   return target
60 endfunction
61 let s:Vital.import = function('s:import')
62
63 function! s:load(...) abort dict
64   for arg in a:000
65     let [name; as] = type(arg) == type([]) ? arg[: 1] : [arg, arg]
66     let target = split(join(as, ''), '\W\+')
67     let dict = self
68     let dict_type = type({})
69     while !empty(target)
70       let ns = remove(target, 0)
71       if !has_key(dict, ns)
72         let dict[ns] = {}
73       endif
74       if type(dict[ns]) == dict_type
75         let dict = dict[ns]
76       else
77         unlet dict
78         break
79       endif
80     endwhile
81     if exists('dict')
82       call extend(dict, self._import(name))
83     endif
84     unlet arg
85   endfor
86   return self
87 endfunction
88 let s:Vital.load = function('s:load')
89
90 function! s:unload() abort dict
91   let s:loaded = {}
92   let s:cache_sid = {}
93   unlet! s:vital_files
94 endfunction
95 let s:Vital.unload = function('s:unload')
96
97 function! s:exists(name) abort dict
98   if a:name !~# '\v^\u\w*%(\.\u\w*)*$'
99     throw 'vital: Invalid module name: ' . a:name
100   endif
101   return s:_module_path(a:name) isnot# ''
102 endfunction
103 let s:Vital.exists = function('s:exists')
104
105 function! s:search(pattern) abort dict
106   let paths = s:_extract_files(a:pattern, self.vital_files())
107   let modules = sort(map(paths, 's:_file2module(v:val)'))
108   return uniq(modules)
109 endfunction
110 let s:Vital.search = function('s:search')
111
112 function! s:plugin_name() abort dict
113   return self._plugin_name
114 endfunction
115 let s:Vital.plugin_name = function('s:plugin_name')
116
117 function! s:_self_vital_files() abort
118   let builtin = printf('%s/__%s__/', s:vital_base_dir, s:plugin_name)
119   let installed = printf('%s/_%s/', s:vital_base_dir, s:plugin_name)
120   let base = builtin . ',' . installed
121   return split(globpath(base, '**/*.vim', 1), "\n")
122 endfunction
123
124 function! s:_global_vital_files() abort
125   let pattern = 'autoload/vital/__*__/**/*.vim'
126   return split(globpath(&runtimepath, pattern, 1), "\n")
127 endfunction
128
129 function! s:_extract_files(pattern, files) abort
130   let tr = {'.': '/', '*': '[^/]*', '**': '.*'}
131   let target = substitute(a:pattern, '\.\|\*\*\?', '\=tr[submatch(0)]', 'g')
132   let regexp = printf('autoload/vital/[^/]\+/%s.vim$', target)
133   return filter(a:files, 'v:val =~# regexp')
134 endfunction
135
136 function! s:_file2module(file) abort
137   let filename = fnamemodify(a:file, ':p:gs?[\\/]?/?')
138   let tail = matchstr(filename, 'autoload/vital/_\w\+/\zs.*\ze\.vim$')
139   return join(split(tail, '[\\/]\+'), '.')
140 endfunction
141
142 " @param {string} name e.g. Data.List
143 function! s:_import(name) abort dict
144   if has_key(s:loaded, a:name)
145     return copy(s:loaded[a:name])
146   endif
147   let module = self._get_module(a:name)
148   if has_key(module, '_vital_created')
149     call module._vital_created(module)
150   endif
151   let export_module = filter(copy(module), 'v:key =~# "^\\a"')
152   " Cache module before calling module._vital_loaded() to avoid cyclic
153   " dependences but remove the cache if module._vital_loaded() fails.
154   " let s:loaded[a:name] = export_module
155   let s:loaded[a:name] = export_module
156   if has_key(module, '_vital_loaded')
157     try
158       call module._vital_loaded(vital#{s:plugin_name}#new())
159     catch
160       unlet s:loaded[a:name]
161       throw 'vital: fail to call ._vital_loaded(): ' . v:exception . " from:\n" . s:_format_throwpoint(v:throwpoint)
162     endtry
163   endif
164   return copy(s:loaded[a:name])
165 endfunction
166 let s:Vital._import = function('s:_import')
167
168 function! s:_format_throwpoint(throwpoint) abort
169   let funcs = []
170   let stack = matchstr(a:throwpoint, '^function \zs.*, .\{-} \d\+$')
171   for line in split(stack, '\.\.')
172     let m = matchlist(line, '^\(.\+\)\%(\[\(\d\+\)\]\|, .\{-} \(\d\+\)\)$')
173     if !empty(m)
174       let [name, lnum, lnum2] = m[1:3]
175       if empty(lnum)
176         let lnum = lnum2
177       endif
178       let info = s:_get_func_info(name)
179       if !empty(info)
180         let attrs = empty(info.attrs) ? '' : join([''] + info.attrs)
181         let flnum = info.lnum == 0 ? '' : printf(' Line:%d', info.lnum + lnum)
182         call add(funcs, printf('function %s(...)%s Line:%d (%s%s)',
183         \        info.funcname, attrs, lnum, info.filename, flnum))
184         continue
185       endif
186     endif
187     " fallback when function information cannot be detected
188     call add(funcs, line)
189   endfor
190   return join(funcs, "\n")
191 endfunction
192
193 " @vimlint(EVL102, 1, l:_)
194 " @vimlint(EVL102, 1, l:__)
195 function! s:_get_func_info(name) abort
196   let name = a:name
197   if a:name =~# '^\d\+$'  " is anonymous-function
198     let name = printf('{%s}', a:name)
199   elseif a:name =~# '^<lambda>\d\+$'  " is lambda-function
200     let name = printf("{'%s'}", a:name)
201   endif
202   if !exists('*' . name)
203     return {}
204   endif
205   let body = execute(printf('verbose function %s', name))
206   let lines = split(body, "\n")
207   let signature = matchstr(lines[0], '^\s*\zs.*')
208   let [_, file, lnum; __] = matchlist(lines[1],
209   \   '^\t\%(Last set from\|.\{-}:\)\s*\zs\(.\{-}\)\%( \S\+ \(\d\+\)\)\?$')
210   return {
211   \   'filename': substitute(file, '[/\\]\+', '/', 'g'),
212   \   'lnum': 0 + lnum,
213   \   'funcname': a:name,
214   \   'arguments': split(matchstr(signature, '(\zs.*\ze)'), '\s*,\s*'),
215   \   'attrs': filter(['dict', 'abort', 'range', 'closure'], 'signature =~# (").*" . v:val)'),
216   \ }
217 endfunction
218 " @vimlint(EVL102, 0, l:__)
219 " @vimlint(EVL102, 0, l:_)
220
221 " s:_get_module() returns module object which has all script local functions.
222 function! s:_get_module(name) abort dict
223   let funcname = s:_import_func_name(self.plugin_name(), a:name)
224   try
225     return call(funcname, [])
226   catch /^Vim\%((\a\+)\)\?:E117:/
227     return s:_get_builtin_module(a:name)
228   endtry
229 endfunction
230
231 function! s:_get_builtin_module(name) abort
232  return s:sid2sfuncs(s:_module_sid(a:name))
233 endfunction
234
235 if s:is_vital_vim
236   " For vital.vim, we can use s:_get_builtin_module directly
237   let s:Vital._get_module = function('s:_get_builtin_module')
238 else
239   let s:Vital._get_module = function('s:_get_module')
240 endif
241
242 function! s:_import_func_name(plugin_name, module_name) abort
243   return printf('vital#_%s#%s#import', a:plugin_name, s:_dot_to_sharp(a:module_name))
244 endfunction
245
246 function! s:_module_sid(name) abort
247   let path = s:_module_path(a:name)
248   if !filereadable(path)
249     throw 'vital: module not found: ' . a:name
250   endif
251   let vital_dir = s:is_vital_vim ? '__\w\+__' : printf('_\{1,2}%s\%%(__\)\?', s:plugin_name)
252   let base = join([vital_dir, ''], '[/\\]\+')
253   let p = base . substitute('' . a:name, '\.', '[/\\\\]\\+', 'g')
254   let sid = s:_sid(path, p)
255   if !sid
256     call s:_source(path)
257     let sid = s:_sid(path, p)
258     if !sid
259       throw printf('vital: cannot get <SID> from path: %s', path)
260     endif
261   endif
262   return sid
263 endfunction
264
265 function! s:_module_path(name) abort
266   return get(s:_extract_files(a:name, s:vital_files()), 0, '')
267 endfunction
268
269 function! s:_module_sid_base_dir() abort
270   return s:is_vital_vim ? &rtp : s:project_root
271 endfunction
272
273 function! s:_dot_to_sharp(name) abort
274   return substitute(a:name, '\.', '#', 'g')
275 endfunction
276
277 function! s:_source(path) abort
278   execute 'source' fnameescape(a:path)
279 endfunction
280
281 " @vimlint(EVL102, 1, l:_)
282 " @vimlint(EVL102, 1, l:__)
283 function! s:_sid(path, filter_pattern) abort
284   let unified_path = s:_unify_path(a:path)
285   if has_key(s:cache_sid, unified_path)
286     return s:cache_sid[unified_path]
287   endif
288   for line in filter(split(execute(':scriptnames'), "\n"), 'v:val =~# a:filter_pattern')
289     let [_, sid, path; __] = matchlist(line, '^\s*\(\d\+\):\s\+\(.\+\)\s*$')
290     if s:_unify_path(path) is# unified_path
291       let s:cache_sid[unified_path] = sid
292       return s:cache_sid[unified_path]
293     endif
294   endfor
295   return 0
296 endfunction
297
298 if filereadable(expand('<sfile>:r') . '.VIM') " is case-insensitive or not
299   let s:_unify_path_cache = {}
300   " resolve() is slow, so we cache results.
301   " Note: On windows, vim can't expand path names from 8.3 formats.
302   " So if getting full path via <sfile> and $HOME was set as 8.3 format,
303   " vital load duplicated scripts. Below's :~ avoid this issue.
304   function! s:_unify_path(path) abort
305     if has_key(s:_unify_path_cache, a:path)
306       return s:_unify_path_cache[a:path]
307     endif
308     let value = tolower(fnamemodify(resolve(fnamemodify(
309     \                   a:path, ':p')), ':~:gs?[\\/]?/?'))
310     let s:_unify_path_cache[a:path] = value
311     return value
312   endfunction
313 else
314   function! s:_unify_path(path) abort
315     return resolve(fnamemodify(a:path, ':p:gs?[\\/]?/?'))
316   endfunction
317 endif
318
319 " copied and modified from Vim.ScriptLocal
320 let s:SNR = join(map(range(len("\<SNR>")), '"[\\x" . printf("%0x", char2nr("\<SNR>"[v:val])) . "]"'), '')
321 function! s:sid2sfuncs(sid) abort
322   let fs = split(execute(printf(':function /^%s%s_', s:SNR, a:sid)), "\n")
323   let r = {}
324   let pattern = printf('\m^function\s<SNR>%d_\zs\w\{-}\ze(', a:sid)
325   for fname in map(fs, 'matchstr(v:val, pattern)')
326     let r[fname] = function(s:_sfuncname(a:sid, fname))
327   endfor
328   return r
329 endfunction
330
331 "" Return funcname of script local functions with SID
332 function! s:_sfuncname(sid, funcname) abort
333   return printf('<SNR>%s_%s', a:sid, a:funcname)
334 endfunction