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.
1 let s:has_lua = has('nvim-0.4.0') || (has('lua') && has('patch-8.2.0775'))
2 function! lsp#utils#has_lua() abort
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
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
16 let s:has_signs = exists('*sign_define') && (has('nvim') || has('patch-8.1.0772'))
17 function! lsp#utils#_has_signs() abort
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
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
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
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
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
47 let s:has_popup_menu = exists('*popup_menu')
48 function! lsp#utils#_has_popup_menu() abort
49 return s:has_popup_menu
52 function! lsp#utils#is_file_uri(uri) abort
53 return stridx(a:uri, 'file:///') == 0
56 function! lsp#utils#is_remote_uri(uri) abort
57 return a:uri =~# '^\w\+::' || a:uri =~# '^[a-z][a-z0-9+.-]*://'
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')
65 function! s:urlencode_char(c) abort
66 return printf('%%%02X', char2nr(a:c))
69 function! s:get_prefix(path) abort
70 return matchstr(a:path, '\(^\w\+::\|^\w\+://\)')
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):]
77 let l:prefix = a:default_prefix
80 let l:result = strpart(a:path, 0, a:start_pos_encode)
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]
87 let l:result .= s:urlencode_char(l:path[l:i])
91 return l:prefix . l:result
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]
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]
105 " Transform cygwin paths to windows paths
108 let l:path = substitute(a:path, '\c^/\([a-z]\)/', '\U\1:/', '')
111 " You must not encode the volume information on the path if
113 let l:end_pos_volume = matchstrpos(l:path, '\c[A-Z]:')[2]
115 if l:end_pos_volume == -1
116 let l:end_pos_volume = 0
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]
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]
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]
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]
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]
146 let l:path = substitute(s:decode_uri(a:uri[len('file:///'):]), '/', '\\', 'g')
148 " Transform windows paths to cygwin paths
150 let l:path = substitute(l:path, '\c^\([A-Z]\):\\', '/\l\1/', '')
151 let l:path = substitute(l:path, '\\', '/', 'g')
154 let s:uri_to_path_cache[a:uri] = l:path
155 return s:uri_to_path_cache[a:uri]
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]
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]
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))", '')
176 function! lsp#utils#normalize_uri(uri) abort
181 function! lsp#utils#get_default_root_uri() abort
182 return lsp#utils#path_to_uri(getcwd())
185 function! lsp#utils#get_buffer_path(...) abort
186 return expand((a:0 > 0 ? '#' . a:1 : '%') . ':p')
189 function! lsp#utils#get_buffer_uri(...) abort
190 let l:name = a:0 > 0 ? bufname(a:1) : expand('%')
192 let l:nr = a:0 > 0 ? a:1 : bufnr('%')
193 let l:name = printf('%s/__NO_NAME_%d__', getcwd(), l:nr)
195 return lsp#utils#path_to_uri(fnamemodify(l:name, ':p'))
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) . ';')
202 if !empty(l:relative_path)
203 return fnamemodify(l:relative_path, ':p')
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) . ';')
213 if !empty(l:relative_path)
214 return fnamemodify(l:relative_path, ':p')
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)
225 elseif l:llhs <# l:lrhs
228 if a:matches[a:lhs] ># a:matches[a:rhs]
230 elseif a:matches[a:lhs] <# a:matches[a:rhs]
236 function! lsp#utils#_nearest_path(matches) abort
237 return empty(a:matches) ?
239 \ sort(keys(a:matches), function('lsp#utils#_compare_nearest_path', [a:matches]))[0]
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)
252 if has_key(l:matched_paths, l:path)
253 let l:matched_paths[l:path] += 1
255 let l:matched_paths[l:path] = 1
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])
266 let l:modify_str = ':p:h'
267 let l:path = lsp#utils#find_nearest_parent_file(a:path, a:filename)
270 return empty(l:path) ? '' : fnamemodify(l:path, l:modify_str)
272 echoerr "The type of argument \"filename\" must be String or List"
276 if exists('*matchstrpos')
277 function! lsp#utils#matchstrpos(expr, pattern) abort
278 return matchstrpos(a:expr, a:pattern)
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)]
286 function! lsp#utils#empty_complete(...) abort
290 function! lsp#utils#error(msg) abort
296 function! lsp#utils#warning(msg) abort
303 function! lsp#utils#echo_with_truncation(msg) abort
306 if &laststatus == 0 || (&laststatus == 1 && tabpagewinnr(tabpagenr(), '$') == 1)
307 let l:winwidth = winwidth(0)
313 let l:winwidth = &columns
320 if l:winwidth > 5 && l:winwidth < strdisplaywidth(l:msg)
321 let l:msg = l:msg[:l:winwidth - 5] . '...'
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)
333 if type(a:expr) != v:t_string || !filereadable(a:expr)
337 " a:expr is a file that is not yet loaded as a buffer
338 let l:lines = readfile(a:expr, '', a:lnum)
340 let l:linestr = l:lines[-1]
341 return strchars(strpart(l:linestr, 0, a:col - 1))
344 function! s:get_base64_alphabet() abort
348 for l:c in range(char2nr('A'), char2nr('Z'))
349 call add(l:alphabet, nr2char(l:c))
353 for l:c in range(char2nr('a'), char2nr('z'))
354 call add(l:alphabet, nr2char(l:c))
358 for l:c in range(char2nr('0'), char2nr('9'))
359 call add(l:alphabet, nr2char(l:c))
363 call add(l:alphabet, '+')
364 call add(l:alphabet, '/')
370 function! lsp#utils#_trim(string) abort
371 return trim(a:string)
374 function! lsp#utils#_trim(string) abort
375 return substitute(a:string, '^\s*\|\s*$', '', 'g')
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])
386 return l:text[0 : l:idx]
389 function! lsp#utils#_get_before_char_skip_white() abort
390 let l:current_lnum = line('.')
392 let l:lnum = l:current_lnum
394 if l:lnum == l:current_lnum
395 let l:text = lsp#utils#_get_before_line()
397 let l:text = getline(l:lnum)
399 let l:match = matchlist(l:text, '\([^[:blank:]]\)\s*$')
400 if get(l:match, 1, v:null) isnot v:null
409 let s:alphabet = s:get_base64_alphabet()
411 function! lsp#utils#base64_decode(data) abort
414 " Process base64 string in chunks of 4 chars
415 for l:group in split(a:data, '.\{4}\zs')
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])
424 " Split the number representing the 3 octets into the individual
429 call add(l:octets, l:group_dec % 256)
430 let l:group_dec = l:group_dec / 256
434 call extend(l:ret, reverse(l:octets))
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)
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]\+')
462 function! lsp#utils#_split_by_eol(text) abort
463 return split(a:text, '\r\n\|\r\|\n', v:true)
466 " parse command options like "-key" or "-key=value"
467 function! lsp#utils#parse_command_options(params) abort
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]
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
481 " polyfill for the neovim wait function
483 function! lsp#utils#_wait(timeout, condition, ...) abort
484 if type(a:timeout) != type(0)
487 if type(get(a:000, 0, 0)) != type(0)
491 let l:result=call('wait', extend([a:timeout, a:condition], a:000))
492 if l:result != -3 " ignore spurious errors
498 function! lsp#utils#_wait(timeout, condition, ...) abort
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)
506 let l:start = reltime()
507 while l:timeout < 0 || reltimefloat(reltime(l:start)) < l:timeout
512 execute 'sleep ' . l:interval . 'm'
515 catch /^Vim:Interrupt$/
521 function! lsp#utils#iteratable(list) abort
522 return type(a:list) !=# v:t_list ? [] : a:list