]> git.madduck.net Git - etc/vim.git/blob - .vim/bundle/vim-lsp/autoload/lsp/utils.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:

Merge commit '56df844d3c39ec494dacc69eae34272b27db185a' as '.vim/bundle/asyncomplete'
[etc/vim.git] / .vim / bundle / vim-lsp / autoload / lsp / utils.vim
1 let s:has_lua = has('nvim-0.4.0') || (has('lua') && has('patch-8.2.0775'))
2 function! lsp#utils#has_lua() abort
3     return s:has_lua
4 endfunction
5
6 let s:has_native_lsp_client = !has('nvim') && has('patch-8.2.4780')
7 function! lsp#utils#has_native_lsp_client() abort
8     return s:has_native_lsp_client
9 endfunction
10
11 let s:has_virtual_text = exists('*nvim_buf_set_virtual_text') && exists('*nvim_create_namespace')
12 function! lsp#utils#_has_nvim_virtual_text() abort
13     return s:has_virtual_text
14 endfunction
15
16 let s:has_signs = exists('*sign_define') && (has('nvim') || has('patch-8.1.0772'))
17 function! lsp#utils#_has_signs() abort
18     return s:has_signs
19 endfunction
20
21 let s:has_nvim_buf_highlight = exists('*nvim_buf_add_highlight') && has('nvim')
22 function! lsp#utils#_has_nvim_buf_highlight() abort
23     return s:has_nvim_buf_highlight
24 endfunction
25
26 " https://github.com/prabirshrestha/vim-lsp/issues/399#issuecomment-500585549
27 let s:has_textprops = exists('*prop_add') && has('patch-8.1.1035')
28 function! lsp#utils#_has_textprops() abort
29     return s:has_textprops
30 endfunction
31
32 let s:has_vim9textprops = exists('*prop_add') && has('patch-9.0.0178')
33 function! lsp#utils#_has_vim_virtual_text() abort
34     return s:has_vim9textprops
35 endfunction
36
37 let s:has_prop_remove_types = exists('*prop_remove') && has('patch-9.0.0233')
38 function! lsp#utils#_has_prop_remove_types() abort
39     return s:has_prop_remove_types
40 endfunction
41
42 let s:has_higlights = has('nvim') ? lsp#utils#_has_nvim_buf_highlight() : lsp#utils#_has_textprops()
43 function! lsp#utils#_has_highlights() abort
44     return s:has_higlights
45 endfunction
46
47 let s:has_popup_menu = exists('*popup_menu')
48 function! lsp#utils#_has_popup_menu() abort
49     return s:has_popup_menu
50 endfunction
51
52 function! lsp#utils#is_file_uri(uri) abort
53     return stridx(a:uri, 'file:///') == 0
54 endfunction
55
56 function! lsp#utils#is_remote_uri(uri) abort
57     return a:uri =~# '^\w\+::' || a:uri =~# '^[a-z][a-z0-9+.-]*://'
58 endfunction
59
60 function! s:decode_uri(uri) abort
61     let l:ret = substitute(a:uri, '[?#].*', '', '')
62     return substitute(l:ret, '%\(\x\x\)', '\=printf("%c", str2nr(submatch(1), 16))', 'g')
63 endfunction
64
65 function! s:urlencode_char(c) abort
66     return printf('%%%02X', char2nr(a:c))
67 endfunction
68
69 function! s:get_prefix(path) abort
70     return matchstr(a:path, '\(^\w\+::\|^\w\+://\)')
71 endfunction
72
73 function! s:encode_uri(path, start_pos_encode, default_prefix) abort
74     let l:prefix = s:get_prefix(a:path)
75     let l:path = a:path[len(l:prefix):]
76     if len(l:prefix) == 0
77         let l:prefix = a:default_prefix
78     endif
79
80     let l:result = strpart(a:path, 0, a:start_pos_encode)
81
82     for l:i in range(a:start_pos_encode, len(l:path) - 1)
83         " Don't encode '/' here, `path` is expected to be a valid path.
84         if l:path[l:i] =~# '^[a-zA-Z0-9_.~/@-]$'
85             let l:result .= l:path[l:i]
86         else
87             let l:result .= s:urlencode_char(l:path[l:i])
88         endif
89     endfor
90
91     return l:prefix . l:result
92 endfunction
93
94 let s:path_to_uri_cache = {}
95 if has('win32') || has('win64') || has('win32unix')
96     function! lsp#utils#path_to_uri(path) abort
97         if has_key(s:path_to_uri_cache, a:path)
98             return s:path_to_uri_cache[a:path]
99         endif
100
101         if empty(a:path) || lsp#utils#is_remote_uri(a:path)
102             let s:path_to_uri_cache[a:path] = a:path
103             return s:path_to_uri_cache[a:path]
104         else
105             " Transform cygwin paths to windows paths
106             let l:path = a:path
107             if has('win32unix')
108                 let l:path = substitute(a:path, '\c^/\([a-z]\)/', '\U\1:/', '')
109             endif
110
111             " You must not encode the volume information on the path if
112             " present
113             let l:end_pos_volume = matchstrpos(l:path, '\c[A-Z]:')[2]
114
115             if l:end_pos_volume == -1
116                 let l:end_pos_volume = 0
117             endif
118
119             let s:path_to_uri_cache[l:path] = s:encode_uri(substitute(l:path, '\', '/', 'g'), l:end_pos_volume, 'file:///')
120             return s:path_to_uri_cache[l:path]
121         endif
122     endfunction
123 else
124     function! lsp#utils#path_to_uri(path) abort
125         if has_key(s:path_to_uri_cache, a:path)
126             return s:path_to_uri_cache[a:path]
127         endif
128
129         if empty(a:path) || lsp#utils#is_remote_uri(a:path)
130             let s:path_to_uri_cache[a:path] = a:path
131             return s:path_to_uri_cache[a:path]
132         else
133             let s:path_to_uri_cache[a:path] = s:encode_uri(a:path, 0, 'file://')
134             return s:path_to_uri_cache[a:path]
135         endif
136     endfunction
137 endif
138
139 let s:uri_to_path_cache = {}
140 if has('win32') || has('win64') || has('win32unix')
141     function! lsp#utils#uri_to_path(uri) abort
142         if has_key(s:uri_to_path_cache, a:uri)
143             return s:uri_to_path_cache[a:uri]
144         endif
145
146         let l:path = substitute(s:decode_uri(a:uri[len('file:///'):]), '/', '\\', 'g')
147
148         " Transform windows paths to cygwin paths
149         if has('win32unix')
150             let l:path = substitute(l:path, '\c^\([A-Z]\):\\', '/\l\1/', '')
151             let l:path = substitute(l:path, '\\', '/', 'g')
152         endif
153
154         let s:uri_to_path_cache[a:uri] = l:path
155         return s:uri_to_path_cache[a:uri]
156     endfunction
157 else
158     function! lsp#utils#uri_to_path(uri) abort
159         if has_key(s:uri_to_path_cache, a:uri)
160             return s:uri_to_path_cache[a:uri]
161         endif
162
163         let s:uri_to_path_cache[a:uri] = s:decode_uri(a:uri[len('file://'):])
164         return s:uri_to_path_cache[a:uri]
165     endfunction
166 endif
167
168 if has('win32') || has('win64')
169     function! lsp#utils#normalize_uri(uri) abort
170         " Refer to https://github.com/microsoft/language-server-protocol/pull/1019 on normalization of urls.
171         " TODO: after the discussion is settled, modify this function.
172         let l:ret = substitute(a:uri, '^file:///[a-zA-Z]\zs%3[aA]', ':', '')
173         return substitute(l:ret, '^file:///\zs\([A-Z]\)', "\\=tolower(submatch(1))", '')
174     endfunction
175 else
176     function! lsp#utils#normalize_uri(uri) abort
177         return a:uri
178     endfunction
179 endif
180
181 function! lsp#utils#get_default_root_uri() abort
182     return lsp#utils#path_to_uri(getcwd())
183 endfunction
184
185 function! lsp#utils#get_buffer_path(...) abort
186     return expand((a:0 > 0 ? '#' . a:1 : '%') . ':p')
187 endfunction
188
189 function! lsp#utils#get_buffer_uri(...) abort
190     let l:name = a:0 > 0 ? bufname(a:1) : expand('%')
191     if empty(l:name)
192         let l:nr = a:0 > 0 ? a:1 : bufnr('%')
193         let l:name = printf('%s/__NO_NAME_%d__', getcwd(), l:nr)
194     endif
195     return lsp#utils#path_to_uri(fnamemodify(l:name, ':p'))
196 endfunction
197
198 " Find a nearest to a `path` parent directory `directoryname` by traversing the filesystem upwards
199 function! lsp#utils#find_nearest_parent_directory(path, directoryname) abort
200     let l:relative_path = finddir(a:directoryname, fnameescape(a:path) . ';')
201
202     if !empty(l:relative_path)
203         return fnamemodify(l:relative_path, ':p')
204     else
205         return ''
206     endif
207 endfunction
208
209 " Find a nearest to a `path` parent filename `filename` by traversing the filesystem upwards
210 function! lsp#utils#find_nearest_parent_file(path, filename) abort
211     let l:relative_path = findfile(a:filename, fnameescape(a:path) . ';')
212
213     if !empty(l:relative_path)
214         return fnamemodify(l:relative_path, ':p')
215     else
216         return ''
217     endif
218 endfunction
219
220 function! lsp#utils#_compare_nearest_path(matches, lhs, rhs) abort
221   let l:llhs = len(a:lhs)
222   let l:lrhs = len(a:rhs)
223   if l:llhs ># l:lrhs
224     return -1
225   elseif l:llhs <# l:lrhs
226     return 1
227   endif
228   if a:matches[a:lhs] ># a:matches[a:rhs]
229     return -1
230   elseif a:matches[a:lhs] <# a:matches[a:rhs]
231     return 1
232   endif
233   return 0
234 endfunction
235
236 function! lsp#utils#_nearest_path(matches) abort
237   return empty(a:matches) ?
238               \ '' :
239               \ sort(keys(a:matches), function('lsp#utils#_compare_nearest_path', [a:matches]))[0]
240 endfunction
241
242 " Find a nearest to a `path` parent filename `filename` by traversing the filesystem upwards
243 " The filename ending with '/' or '\' will be regarded as directory name,
244 " otherwith as file name
245 function! lsp#utils#find_nearest_parent_file_directory(path, filename) abort
246     if type(a:filename) == 3
247         let l:matched_paths = {}
248         for l:current_name in a:filename
249             let l:path = lsp#utils#find_nearest_parent_file_directory(a:path, l:current_name)
250
251             if !empty(l:path)
252                 if has_key(l:matched_paths, l:path)
253                     let l:matched_paths[l:path] += 1
254                 else
255                     let l:matched_paths[l:path] = 1
256                 endif
257             endif
258         endfor
259
260         return lsp#utils#_nearest_path(l:matched_paths)
261     elseif type(a:filename) == 1
262         if a:filename[-1:] ==# '/' || a:filename[-1:] ==# '\'
263             let l:modify_str = ':p:h:h'
264             let l:path = lsp#utils#find_nearest_parent_directory(a:path, a:filename[:-2])
265         else
266             let l:modify_str = ':p:h'
267             let l:path = lsp#utils#find_nearest_parent_file(a:path, a:filename)
268         endif
269
270         return empty(l:path) ? '' : fnamemodify(l:path, l:modify_str)
271     else
272         echoerr "The type of argument \"filename\" must be String or List"
273     endif
274 endfunction
275
276 if exists('*matchstrpos')
277     function! lsp#utils#matchstrpos(expr, pattern) abort
278         return matchstrpos(a:expr, a:pattern)
279     endfunction
280 else
281     function! lsp#utils#matchstrpos(expr, pattern) abort
282         return [matchstr(a:expr, a:pattern), match(a:expr, a:pattern), matchend(a:expr, a:pattern)]
283     endfunction
284 endif
285
286 function! lsp#utils#empty_complete(...) abort
287     return []
288 endfunction
289
290 function! lsp#utils#error(msg) abort
291     echohl ErrorMsg
292     echom a:msg
293     echohl NONE
294 endfunction
295
296 function! lsp#utils#warning(msg) abort
297     echohl WarningMsg
298     echom a:msg
299     echohl NONE
300 endfunction
301
302
303 function! lsp#utils#echo_with_truncation(msg) abort
304     let l:msg = a:msg
305
306     if &laststatus == 0 || (&laststatus == 1 && tabpagewinnr(tabpagenr(), '$') == 1)
307         let l:winwidth = winwidth(0)
308
309         if &ruler
310             let l:winwidth -= 18
311         endif
312     else
313         let l:winwidth = &columns
314     endif
315
316     if &showcmd
317         let l:winwidth -= 12
318     endif
319
320     if l:winwidth > 5 && l:winwidth < strdisplaywidth(l:msg)
321         let l:msg = l:msg[:l:winwidth - 5] . '...'
322     endif
323
324     exec 'echo l:msg'
325 endfunction
326
327 " Convert a byte-index (1-based) to a character-index (0-based)
328 " This function requires a buffer specifier (expr, see :help bufname()),
329 " a line number (lnum, 1-based), and a byte-index (char, 1-based).
330 function! lsp#utils#to_char(expr, lnum, col) abort
331     let l:lines = getbufline(a:expr, a:lnum)
332     if l:lines == []
333         if type(a:expr) != v:t_string || !filereadable(a:expr)
334             " invalid a:expr
335             return a:col - 1
336         endif
337         " a:expr is a file that is not yet loaded as a buffer
338         let l:lines = readfile(a:expr, '', a:lnum)
339     endif
340     let l:linestr = l:lines[-1]
341     return strchars(strpart(l:linestr, 0, a:col - 1))
342 endfunction
343
344 function! s:get_base64_alphabet() abort
345     let l:alphabet = []
346
347     " Uppercase letters
348     for l:c in range(char2nr('A'), char2nr('Z'))
349         call add(l:alphabet, nr2char(l:c))
350     endfor
351
352     " Lowercase letters
353     for l:c in range(char2nr('a'), char2nr('z'))
354         call add(l:alphabet, nr2char(l:c))
355     endfor
356
357     " Numbers
358     for l:c in range(char2nr('0'), char2nr('9'))
359         call add(l:alphabet, nr2char(l:c))
360     endfor
361
362     " Symbols
363     call add(l:alphabet, '+')
364     call add(l:alphabet, '/')
365
366     return l:alphabet
367 endfunction
368
369 if exists('*trim')
370   function! lsp#utils#_trim(string) abort
371     return trim(a:string)
372   endfunction
373 else
374   function! lsp#utils#_trim(string) abort
375     return substitute(a:string, '^\s*\|\s*$', '', 'g')
376   endfunction
377 endif
378
379 function! lsp#utils#_get_before_line() abort
380   let l:text = getline('.')
381   let l:idx = min([strlen(l:text), col('.') - 2])
382   let l:idx = max([l:idx, -1])
383   if l:idx == -1
384     return ''
385   endif
386   return l:text[0 : l:idx]
387 endfunction
388
389 function! lsp#utils#_get_before_char_skip_white() abort
390   let l:current_lnum = line('.')
391
392   let l:lnum = l:current_lnum
393   while l:lnum > 0
394     if l:lnum == l:current_lnum
395       let l:text = lsp#utils#_get_before_line()
396     else
397       let l:text = getline(l:lnum)
398     endif
399     let l:match = matchlist(l:text, '\([^[:blank:]]\)\s*$')
400     if get(l:match, 1, v:null) isnot v:null
401       return l:match[1]
402     endif
403     let l:lnum -= 1
404   endwhile
405
406   return ''
407 endfunction
408
409 let s:alphabet = s:get_base64_alphabet()
410
411 function! lsp#utils#base64_decode(data) abort
412     let l:ret = []
413
414     " Process base64 string in chunks of 4 chars
415     for l:group in split(a:data, '.\{4}\zs')
416         let l:group_dec = 0
417
418         " Convert 4 chars to 3 octets
419         for l:char in split(l:group, '\zs')
420             let l:group_dec = l:group_dec * 64
421             let l:group_dec += max([index(s:alphabet, l:char), 0])
422         endfor
423
424         " Split the number representing the 3 octets into the individual
425         " octets
426         let l:octets = []
427         let l:i = 0
428         while l:i < 3
429             call add(l:octets, l:group_dec % 256)
430             let l:group_dec = l:group_dec / 256
431             let l:i += 1
432         endwhile
433
434         call extend(l:ret, reverse(l:octets))
435     endfor
436
437     " Handle padding
438     if len(a:data) >= 2
439         if strpart(a:data, len(a:data) - 2) ==# '=='
440             call remove(l:ret, -2, -1)
441         elseif strpart(a:data, len(a:data) - 1) ==# '='
442             call remove(l:ret, -1, -1)
443         endif
444     endif
445
446     return l:ret
447 endfunction
448
449 function! lsp#utils#make_valid_word(str) abort
450    let l:str = substitute(a:str, '\$[0-9]\+\|\${\%(\\.\|[^}]\)\+}', '', 'g')
451    let l:str = substitute(l:str, '\\\(.\)', '\1', 'g')
452    let l:valid = matchstr(l:str, '^[^"'' (<{\[\t\r\n]\+')
453    if empty(l:valid)
454        return l:str
455    endif
456    if l:valid =~# ':$'
457        return l:valid[:-2]
458    endif
459    return l:valid
460 endfunction
461
462 function! lsp#utils#_split_by_eol(text) abort
463     return split(a:text, '\r\n\|\r\|\n', v:true)
464 endfunction
465
466 " parse command options like "-key" or "-key=value"
467 function! lsp#utils#parse_command_options(params) abort
468     let l:result = {}
469     for l:param in a:params
470         let l:match = matchlist(l:param, '-\{1,2}\zs\([^=]*\)\(=\(.*\)\)\?\m')
471         let l:result[l:match[1]] = l:match[3]
472     endfor
473     return l:result
474 endfunction
475
476 function! lsp#utils#is_large_window(winid) abort
477     let l:buffer_size = line2byte(line('$', a:winid))
478     return g:lsp_max_buffer_size >= 0 && l:buffer_size >= g:lsp_max_buffer_size
479 endfunction
480
481 " polyfill for the neovim wait function
482 if exists('*wait')
483     function! lsp#utils#_wait(timeout, condition, ...) abort
484         if type(a:timeout) != type(0)
485             return -3
486         endif
487         if type(get(a:000, 0, 0)) != type(0)
488             return -3
489         endif
490         while 1
491             let l:result=call('wait', extend([a:timeout, a:condition], a:000))
492             if l:result != -3 " ignore spurious errors
493                 return l:result
494             endif
495         endwhile
496     endfunction
497 else
498     function! lsp#utils#_wait(timeout, condition, ...) abort
499         try
500             let l:timeout = a:timeout / 1000.0
501             let l:interval = get(a:000, 0, 200)
502             let l:Condition = a:condition
503             if type(l:Condition) != type(function('eval'))
504                 let l:Condition = function('eval', l:Condition)
505             endif
506             let l:start = reltime()
507             while l:timeout < 0 || reltimefloat(reltime(l:start)) < l:timeout
508                 if l:Condition()
509                     return 0
510                 endif
511
512                 execute 'sleep ' . l:interval . 'm'
513             endwhile
514             return -1
515         catch /^Vim:Interrupt$/
516             return -2
517         endtry
518     endfunction
519 endif
520
521 function! lsp#utils#iteratable(list) abort
522     return type(a:list) !=# v:t_list ? [] : a:list
523 endfunction