diff --git a/autoload/vital/_lsp.vim b/autoload/vital/_lsp.vim new file mode 100644 index 000000000..55104952e --- /dev/null +++ b/autoload/vital/_lsp.vim @@ -0,0 +1,9 @@ +let s:_plugin_name = expand(':t:r') + +function! vital#{s:_plugin_name}#new() abort + return vital#{s:_plugin_name[1:]}#new() +endfunction + +function! vital#{s:_plugin_name}#function(funcname) abort + silent! return function(a:funcname) +endfunction diff --git a/autoload/vital/_lsp/VS/Vim/Buffer.vim b/autoload/vital/_lsp/VS/Vim/Buffer.vim new file mode 100644 index 000000000..18717c964 --- /dev/null +++ b/autoload/vital/_lsp/VS/Vim/Buffer.vim @@ -0,0 +1,79 @@ +" ___vital___ +" NOTE: lines between '" ___vital___' is generated by :Vitalize. +" Do not modify the code nor insert new lines before '" ___vital___' +function! s:_SID() abort + return matchstr(expand(''), '\zs\d\+\ze__SID$') +endfunction +execute join(['function! vital#_lsp#VS#Vim#Buffer#import() abort', printf("return map({'get_line_count': '', 'do': '', 'create': '', 'load': ''}, \"vital#_lsp#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") +delfunction s:_SID +" ___vital___ +let s:Do = { -> {} } + +let g:___VS_Vim_Buffer_id = get(g:, '___VS_Vim_Buffer_id', 0) + +" +" get_line_count +" +if exists('*nvim_buf_line_count') + function! s:get_line_count(bufnr) abort + return nvim_buf_line_count(a:bufnr) + endfunction +elseif has('patch-8.2.0019') + function! s:get_line_count(bufnr) abort + return getbufinfo(a:bufnr)[0].linecount + endfunction +else + function! s:get_line_count(bufnr) abort + if bufnr('%') == bufnr(a:bufnr) + return line('$') + endif + return len(getbufline(a:bufnr, '^', '$')) + endfunction +endif + +" +" create +" +function! s:create(...) abort + let g:___VS_Vim_Buffer_id += 1 + let l:bufnr = bufnr(printf('VS.Vim.Buffer: %s: %s', + \ g:___VS_Vim_Buffer_id, + \ get(a:000, 0, 'VS.Vim.Buffer.Default') + \ ), v:true) + call s:load(l:bufnr) + return l:bufnr +endfunction + +" +" load +" +if exists('*bufload') + function! s:load(bufnr_or_path) abort + call bufload(bufnr(a:bufnr_or_path, v:true)) + endfunction +else + function! s:load(bufnr_or_path) abort + call s:do(bufnr(a:bufnr_or_path, v:true), { -> {} }) + endfunction +endif + +" +" do +" +function! s:do(bufnr, func) abort + let l:curr_bufnr = bufnr('%') + if l:curr_bufnr == a:bufnr + call a:func() + return + endif + + try + execute printf('noautocmd keepalt keepjumps %sbuffer', a:bufnr) + call a:func() + catch /.*/ + echomsg string({ 'exception': v:exception, 'throwpoint': v:throwpoint }) + finally + execute printf('noautocmd keepalt keepjumps %sbuffer', l:curr_bufnr) + endtry +endfunction + diff --git a/autoload/vital/_lsp/VS/Vim/Window.vim b/autoload/vital/_lsp/VS/Vim/Window.vim new file mode 100644 index 000000000..dba5b0484 --- /dev/null +++ b/autoload/vital/_lsp/VS/Vim/Window.vim @@ -0,0 +1,139 @@ +" ___vital___ +" NOTE: lines between '" ___vital___' is generated by :Vitalize. +" Do not modify the code nor insert new lines before '" ___vital___' +function! s:_SID() abort + return matchstr(expand(''), '\zs\d\+\ze__SID$') +endfunction +execute join(['function! vital#_lsp#VS#Vim#Window#import() abort', printf("return map({'info': '', 'do': '', 'find': '', 'scroll': '', 'screenpos': ''}, \"vital#_lsp#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") +delfunction s:_SID +" ___vital___ +let s:Do = { -> {} } + +" +" do +" +function! s:do(winid, func) abort + let l:curr_winid = win_getid() + if l:curr_winid == a:winid + call a:func() + return + endif + + if exists('*win_execute') + let s:Do = a:func + try + noautocmd keepalt keepjumps call win_execute(a:winid, 'call s:Do()') + catch /.*/ + echomsg string({ 'exception': v:exception, 'throwpoint': v:throwpoint }) + endtry + unlet s:Do + return + endif + + noautocmd keepalt keepjumps call win_gotoid(a:winid) + try + call a:func() + catch /.*/ + echomsg string({ 'exception': v:exception, 'throwpoint': v:throwpoint }) + endtry + noautocmd keepalt keepjumps call win_gotoid(l:curr_winid) +endfunction + +" +" info +" +if has('nvim') + function! s:info(win) abort + let l:info = getwininfo(a:win)[0] + return { + \ 'width': l:info.width, + \ 'height': l:info.height, + \ 'topline': l:info.topline, + \ } + endfunction +else + function! s:info(win) abort + if index(s:_get_visible_popup_winids(), a:win) >= 0 + let l:info = popup_getpos(a:win) + return { + \ 'width': l:info.width, + \ 'height': l:info.height, + \ 'topline': l:info.firstline + \ } + endif + + let l:ctx = {} + let l:ctx.info = {} + function! l:ctx.callback() abort + let self.info.width = winwidth(0) + let self.info.height = winheight(0) + let self.info.topline = line('w0') + endfunction + call s:do(a:win, { -> l:ctx.callback() }) + return l:ctx.info + endfunction +endif + +" +" find +" +function! s:find(callback) abort + let l:winids = [] + let l:winids += map(range(1, tabpagewinnr(tabpagenr(), '$')), 'win_getid(v:val)') + let l:winids += s:_get_visible_popup_winids() + return filter(l:winids, 'a:callback(v:val)') +endfunction + +" +" scroll +" +function! s:scroll(winid, topline) abort + let l:ctx = {} + function! l:ctx.callback(winid, topline) abort + let l:wininfo = s:info(a:winid) + let l:topline = a:topline + let l:topline = max([l:topline, 1]) + let l:topline = min([l:topline, line('$') - l:wininfo.height + 1]) + + if l:topline == l:wininfo.topline + return + endif + + if index(s:_get_visible_popup_winids(), a:winid) >= 0 + call popup_setoptions(a:winid, { + \ 'firstline': l:topline, + \ }) + else + let l:delta = l:topline - l:wininfo.topline + let l:key = l:delta > 0 ? "\" : "\" + execute printf('noautocmd silent normal! %s', repeat(l:key, abs(l:delta))) + endif + endfunction + call s:do(a:winid, { -> l:ctx.callback(a:winid, a:topline) }) +endfunction + +" +" screenpos +" +" @param {[number, number]} pos - position on the current buffer. +" +function! s:screenpos(pos) abort + let l:ui_x = wincol() - col('.') + let l:view = winsaveview() + let l:scroll_x = l:view.leftcol + let l:scroll_y = l:view.topline - 1 + let l:winpos = win_screenpos(win_getid()) + let l:origin1 = [l:winpos[0] + (a:pos[0] - l:scroll_y) - 1, l:winpos[1] + (a:pos[1] + a:pos[2] + l:ui_x - l:scroll_x) - 1] + return [l:origin1[0] - 1, l:origin1[1] - 1] +endfunction + +" +" _get_visible_popup_winids +" +function! s:_get_visible_popup_winids() abort + if !exists('*popup_list') + return [] + endif + return filter(popup_list(), 'popup_getpos(v:val).visible') +endfunction + diff --git a/autoload/vital/_lsp/VS/Vim/Window/FloatingWindow.vim b/autoload/vital/_lsp/VS/Vim/Window/FloatingWindow.vim new file mode 100644 index 000000000..0b3530f7a --- /dev/null +++ b/autoload/vital/_lsp/VS/Vim/Window/FloatingWindow.vim @@ -0,0 +1,344 @@ +" ___vital___ +" NOTE: lines between '" ___vital___' is generated by :Vitalize. +" Do not modify the code nor insert new lines before '" ___vital___' +function! s:_SID() abort + return matchstr(expand(''), '\zs\d\+\ze__SID$') +endfunction +execute join(['function! vital#_lsp#VS#Vim#Window#FloatingWindow#import() abort', printf("return map({'_vital_depends': '', 'is_available': '', 'new': '', '_vital_loaded': ''}, \"vital#_lsp#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") +delfunction s:_SID +" ___vital___ +" +" _vital_loaded +" +function! s:_vital_loaded(V) abort + let s:Window = a:V.import('VS.Vim.Window') +endfunction + +" +" _vital_depends +" +function! s:_vital_depends() abort + return ['VS.Vim.Window'] +endfunction + +" +" managed floating windows. +" +let s:floating_windows = {} + +" +" is_available +" +function! s:is_available() abort + if has('nvim') + return v:true + endif + return exists('*popup_create') && exists('*popup_hide') && exists('*popup_move') && exists('*popup_getpos') +endfunction + +" +" new +" +function! s:new(...) abort + call s:_init() + + return s:FloatingWindow.new(get(a:000, 0, {})) +endfunction + +" +" _notify_opened +" +" @param {number} winid +" @param {VS.Vim.Window.FloatingWindow} floating_window +" +function! s:_notify_opened(winid, floating_window) abort + let s:floating_windows[a:winid] = a:floating_window + call a:floating_window._on_opened() +endfunction + +" +" _notify_closed +" +function! s:_notify_closed() abort + for [l:winid, l:floating_window] in items(s:floating_windows) + if winheight(l:winid) == -1 + call l:floating_window._on_closed() + unlet s:floating_windows[l:winid] + endif + endfor +endfunction + +let s:FloatingWindow = {} + +" +" new +" +" @param {function?} args.on_opened +" @param {function?} args.on_closed +" +function! s:FloatingWindow.new(args) abort + return extend(deepcopy(s:FloatingWindow), { + \ '_winid': v:null, + \ '_bufnr': v:null, + \ '_vars': {}, + \ '_on_opened': get(a:args, 'on_opened', { -> {} }), + \ '_on_closed': get(a:args, 'on_closed', { -> {} }), + \ }) +endfunction + +" +" get_size +" +" @param {number?} args.minwidth +" @param {number?} args.maxwidth +" @param {number?} args.minheight +" @param {number?} args.maxheight +" +function! s:FloatingWindow.get_size(args) abort + if self._bufnr is# v:null + throw 'VS.Vim.Window.FloatingWindow: Failed to detect bufnr.' + endif + + let l:maxwidth = get(a:args, 'maxwidth', -1) + let l:minwidth = get(a:args, 'minwidth', -1) + let l:maxheight = get(a:args, 'maxheight', -1) + let l:minheight = get(a:args, 'minheight', -1) + let l:lines = getbufline(self._bufnr, '^', '$') + + " width + let l:width = 0 + for l:line in l:lines + let l:width = max([l:width, strdisplaywidth(l:line)]) + endfor + + let l:width = l:minwidth == -1 ? l:width : max([l:minwidth, l:width]) + let l:width = l:maxwidth == -1 ? l:width : min([l:maxwidth, l:width]) + + " height + let l:height = 0 + for l:line in l:lines + let l:height += max([1, float2nr(ceil(strdisplaywidth(l:line) / str2float('' . l:width)))]) + endfor + let l:height = l:minheight == -1 ? l:height : max([l:minheight, l:height]) + let l:height = l:maxheight == -1 ? l:height : min([l:maxheight, l:height]) + + return { + \ 'width': max([1, l:width]), + \ 'height': max([1, l:height]), + \ } +endfunction + +" +" set_bufnr +" +" @param {number} bufnr +" +function! s:FloatingWindow.set_bufnr(bufnr) abort + let self._bufnr = a:bufnr +endfunction + +" +" get_bufnr +" +function! s:FloatingWindow.get_bufnr() abort + return self._bufnr +endfunction + +" +" get_winid +" +function! s:FloatingWindow.get_winid() abort + if self.is_visible() + return self._winid + endif + return v:null +endfunction + +" +" set_var +" +" @param {string} key +" @param {unknown} value +" +function! s:FloatingWindow.set_var(key, value) abort + let self._vars[a:key] = a:value + if self.is_visible() + call setwinvar(self._winid, a:key, a:value) + endif +endfunction + +" +" get_var +" +" @param {string} key +" +function! s:FloatingWindow.get_var(key) abort + return self._vars[a:key] +endfunction + +" +" open +" +" @param {number} args.row 0-based indexing +" @param {number} args.col 0-based indexing +" @param {number} args.width +" @param {number} args.height +" +function! s:FloatingWindow.open(args) abort + let l:style = { + \ 'row': a:args.row, + \ 'col': a:args.col, + \ 'width': a:args.width, + \ 'height': a:args.height, + \ } + + if self.is_visible() + call s:_move(self._winid, l:style) + else + let self._winid = s:_open(self._bufnr, l:style, { -> self._on_closed() }) + for [l:key, l:value] in items(self._vars) + call setwinvar(self._winid, l:key, l:value) + endfor + call s:_notify_opened(self._winid, self) + endif +endfunction + +" +" close +" +function! s:FloatingWindow.close() abort + if self.is_visible() + call s:_close(self._winid) + endif + let self._winid = v:null +endfunction + +" +" enter +" +function! s:FloatingWindow.enter() abort + call s:_enter(self._winid) +endfunction + +" +" is_visible +" +function! s:FloatingWindow.is_visible() abort + return s:_exists(self._winid) ? v:true : v:false +endfunction + +" +" open +" +if has('nvim') + function! s:_open(buf, style, callback) abort + return nvim_open_win(a:buf, v:false, s:_style(a:style)) + endfunction +else + function! s:_open(buf, style, callback) abort + return popup_create(a:buf, extend(s:_style(a:style), { + \ 'callback': a:callback, + \ }, 'force')) + endfunction +endif + +" +" close +" +if has('nvim') + function! s:_close(win) abort + call nvim_win_close(a:win, v:true) + call s:_notify_closed() + endfunction +else + function! s:_close(win) abort + call popup_close(a:win) + endfunction +endif + +" +" move +" +if has('nvim') + function! s:_move(win, style) abort + call nvim_win_set_config(a:win, s:_style(a:style)) + endfunction +else + function! s:_move(win, style) abort + call popup_move(a:win, s:_style(a:style)) + endfunction +endif + +" +" enter +" +if has('nvim') + function! s:_enter(win) abort + call win_gotoid(a:win) + endfunction +else + function! s:_enter(win) abort + " not supported. + endfunction +endif + +" +" exists +" +if has('nvim') + function! s:_exists(win) abort + return type(a:win) == type(0) && nvim_win_is_valid(a:win) && nvim_win_get_number(a:win) != -1 + endfunction +else + function! s:_exists(win) abort + return type(a:win) == type(0) && winheight(a:win) != -1 + endfunction +endif + +" +" style +" +if has('nvim') + function! s:_style(style) abort + return { + \ 'relative': 'editor', + \ 'width': a:style.width, + \ 'height': a:style.height, + \ 'row': a:style.row, + \ 'col': a:style.col, + \ 'focusable': v:true, + \ 'style': 'minimal', + \ } + endfunction +else + function! s:_style(style) abort + return { + \ 'line': a:style.row + 1, + \ 'col': a:style.col + 1, + \ 'pos': 'topleft', + \ 'moved': [0, 0, 0], + \ 'scrollbar': 0, + \ 'maxwidth': a:style.width, + \ 'maxheight': a:style.height, + \ 'minwidth': a:style.width, + \ 'minheight': a:style.height, + \ 'tabpage': 0, + \ } + endfunction +endif + +" +" init +" +let s:has_init = v:false +function! s:_init() abort + if s:has_init || !has('nvim') + return + endif + let s:has_init = v:true + augroup printf('') + autocmd! + autocmd WinEnter * call _notify_closed() + augroup END +endfunction + diff --git a/autoload/vital/lsp.vim b/autoload/vital/lsp.vim new file mode 100644 index 000000000..6730f4e0a --- /dev/null +++ b/autoload/vital/lsp.vim @@ -0,0 +1,330 @@ +let s:plugin_name = expand(':t:r') +let s:vital_base_dir = expand(':h') +let s:project_root = expand(':h:h:h') +let s:is_vital_vim = s:plugin_name is# 'vital' + +let s:loaded = {} +let s:cache_sid = {} + +function! vital#{s:plugin_name}#new() abort + return s:new(s:plugin_name) +endfunction + +function! vital#{s:plugin_name}#import(...) abort + if !exists('s:V') + let s:V = s:new(s:plugin_name) + endif + return call(s:V.import, a:000, s:V) +endfunction + +let s:Vital = {} + +function! s:new(plugin_name) abort + let base = deepcopy(s:Vital) + let base._plugin_name = a:plugin_name + return base +endfunction + +function! s:vital_files() abort + if !exists('s:vital_files') + let s:vital_files = map( + \ s:is_vital_vim ? s:_global_vital_files() : s:_self_vital_files(), + \ 'fnamemodify(v:val, ":p:gs?[\\\\/]?/?")') + endif + return copy(s:vital_files) +endfunction +let s:Vital.vital_files = function('s:vital_files') + +function! s:import(name, ...) abort dict + let target = {} + let functions = [] + for a in a:000 + if type(a) == type({}) + let target = a + elseif type(a) == type([]) + let functions = a + endif + unlet a + endfor + let module = self._import(a:name) + if empty(functions) + call extend(target, module, 'keep') + else + for f in functions + if has_key(module, f) && !has_key(target, f) + let target[f] = module[f] + endif + endfor + endif + return target +endfunction +let s:Vital.import = function('s:import') + +function! s:load(...) abort dict + for arg in a:000 + let [name; as] = type(arg) == type([]) ? arg[: 1] : [arg, arg] + let target = split(join(as, ''), '\W\+') + let dict = self + let dict_type = type({}) + while !empty(target) + let ns = remove(target, 0) + if !has_key(dict, ns) + let dict[ns] = {} + endif + if type(dict[ns]) == dict_type + let dict = dict[ns] + else + unlet dict + break + endif + endwhile + if exists('dict') + call extend(dict, self._import(name)) + endif + unlet arg + endfor + return self +endfunction +let s:Vital.load = function('s:load') + +function! s:unload() abort dict + let s:loaded = {} + let s:cache_sid = {} + unlet! s:vital_files +endfunction +let s:Vital.unload = function('s:unload') + +function! s:exists(name) abort dict + if a:name !~# '\v^\u\w*%(\.\u\w*)*$' + throw 'vital: Invalid module name: ' . a:name + endif + return s:_module_path(a:name) isnot# '' +endfunction +let s:Vital.exists = function('s:exists') + +function! s:search(pattern) abort dict + let paths = s:_extract_files(a:pattern, self.vital_files()) + let modules = sort(map(paths, 's:_file2module(v:val)')) + return uniq(modules) +endfunction +let s:Vital.search = function('s:search') + +function! s:plugin_name() abort dict + return self._plugin_name +endfunction +let s:Vital.plugin_name = function('s:plugin_name') + +function! s:_self_vital_files() abort + let builtin = printf('%s/__%s__/', s:vital_base_dir, s:plugin_name) + let installed = printf('%s/_%s/', s:vital_base_dir, s:plugin_name) + let base = builtin . ',' . installed + return split(globpath(base, '**/*.vim', 1), "\n") +endfunction + +function! s:_global_vital_files() abort + let pattern = 'autoload/vital/__*__/**/*.vim' + return split(globpath(&runtimepath, pattern, 1), "\n") +endfunction + +function! s:_extract_files(pattern, files) abort + let tr = {'.': '/', '*': '[^/]*', '**': '.*'} + let target = substitute(a:pattern, '\.\|\*\*\?', '\=tr[submatch(0)]', 'g') + let regexp = printf('autoload/vital/[^/]\+/%s.vim$', target) + return filter(a:files, 'v:val =~# regexp') +endfunction + +function! s:_file2module(file) abort + let filename = fnamemodify(a:file, ':p:gs?[\\/]?/?') + let tail = matchstr(filename, 'autoload/vital/_\w\+/\zs.*\ze\.vim$') + return join(split(tail, '[\\/]\+'), '.') +endfunction + +" @param {string} name e.g. Data.List +function! s:_import(name) abort dict + if has_key(s:loaded, a:name) + return copy(s:loaded[a:name]) + endif + let module = self._get_module(a:name) + if has_key(module, '_vital_created') + call module._vital_created(module) + endif + let export_module = filter(copy(module), 'v:key =~# "^\\a"') + " Cache module before calling module._vital_loaded() to avoid cyclic + " dependences but remove the cache if module._vital_loaded() fails. + " let s:loaded[a:name] = export_module + let s:loaded[a:name] = export_module + if has_key(module, '_vital_loaded') + try + call module._vital_loaded(vital#{s:plugin_name}#new()) + catch + unlet s:loaded[a:name] + throw 'vital: fail to call ._vital_loaded(): ' . v:exception . " from:\n" . s:_format_throwpoint(v:throwpoint) + endtry + endif + return copy(s:loaded[a:name]) +endfunction +let s:Vital._import = function('s:_import') + +function! s:_format_throwpoint(throwpoint) abort + let funcs = [] + let stack = matchstr(a:throwpoint, '^function \zs.*, .\{-} \d\+$') + for line in split(stack, '\.\.') + let m = matchlist(line, '^\(.\+\)\%(\[\(\d\+\)\]\|, .\{-} \(\d\+\)\)$') + if !empty(m) + let [name, lnum, lnum2] = m[1:3] + if empty(lnum) + let lnum = lnum2 + endif + let info = s:_get_func_info(name) + if !empty(info) + let attrs = empty(info.attrs) ? '' : join([''] + info.attrs) + let flnum = info.lnum == 0 ? '' : printf(' Line:%d', info.lnum + lnum) + call add(funcs, printf('function %s(...)%s Line:%d (%s%s)', + \ info.funcname, attrs, lnum, info.filename, flnum)) + continue + endif + endif + " fallback when function information cannot be detected + call add(funcs, line) + endfor + return join(funcs, "\n") +endfunction + +function! s:_get_func_info(name) abort + let name = a:name + if a:name =~# '^\d\+$' " is anonymous-function + let name = printf('{%s}', a:name) + elseif a:name =~# '^\d\+$' " is lambda-function + let name = printf("{'%s'}", a:name) + endif + if !exists('*' . name) + return {} + endif + let body = execute(printf('verbose function %s', name)) + let lines = split(body, "\n") + let signature = matchstr(lines[0], '^\s*\zs.*') + let [_, file, lnum; __] = matchlist(lines[1], + \ '^\t\%(Last set from\|.\{-}:\)\s*\zs\(.\{-}\)\%( \S\+ \(\d\+\)\)\?$') + return { + \ 'filename': substitute(file, '[/\\]\+', '/', 'g'), + \ 'lnum': 0 + lnum, + \ 'funcname': a:name, + \ 'arguments': split(matchstr(signature, '(\zs.*\ze)'), '\s*,\s*'), + \ 'attrs': filter(['dict', 'abort', 'range', 'closure'], 'signature =~# (").*" . v:val)'), + \ } +endfunction + +" s:_get_module() returns module object wihch has all script local functions. +function! s:_get_module(name) abort dict + let funcname = s:_import_func_name(self.plugin_name(), a:name) + try + return call(funcname, []) + catch /^Vim\%((\a\+)\)\?:E117:/ + return s:_get_builtin_module(a:name) + endtry +endfunction + +function! s:_get_builtin_module(name) abort + return s:sid2sfuncs(s:_module_sid(a:name)) +endfunction + +if s:is_vital_vim + " For vital.vim, we can use s:_get_builtin_module directly + let s:Vital._get_module = function('s:_get_builtin_module') +else + let s:Vital._get_module = function('s:_get_module') +endif + +function! s:_import_func_name(plugin_name, module_name) abort + return printf('vital#_%s#%s#import', a:plugin_name, s:_dot_to_sharp(a:module_name)) +endfunction + +function! s:_module_sid(name) abort + let path = s:_module_path(a:name) + if !filereadable(path) + throw 'vital: module not found: ' . a:name + endif + let vital_dir = s:is_vital_vim ? '__\w\+__' : printf('_\{1,2}%s\%%(__\)\?', s:plugin_name) + let base = join([vital_dir, ''], '[/\\]\+') + let p = base . substitute('' . a:name, '\.', '[/\\\\]\\+', 'g') + let sid = s:_sid(path, p) + if !sid + call s:_source(path) + let sid = s:_sid(path, p) + if !sid + throw printf('vital: cannot get from path: %s', path) + endif + endif + return sid +endfunction + +function! s:_module_path(name) abort + return get(s:_extract_files(a:name, s:vital_files()), 0, '') +endfunction + +function! s:_module_sid_base_dir() abort + return s:is_vital_vim ? &rtp : s:project_root +endfunction + +function! s:_dot_to_sharp(name) abort + return substitute(a:name, '\.', '#', 'g') +endfunction + +function! s:_source(path) abort + execute 'source' fnameescape(a:path) +endfunction + +" @vimlint(EVL102, 1, l:_) +" @vimlint(EVL102, 1, l:__) +function! s:_sid(path, filter_pattern) abort + let unified_path = s:_unify_path(a:path) + if has_key(s:cache_sid, unified_path) + return s:cache_sid[unified_path] + endif + for line in filter(split(execute(':scriptnames'), "\n"), 'v:val =~# a:filter_pattern') + let [_, sid, path; __] = matchlist(line, '^\s*\(\d\+\):\s\+\(.\+\)\s*$') + if s:_unify_path(path) is# unified_path + let s:cache_sid[unified_path] = sid + return s:cache_sid[unified_path] + endif + endfor + return 0 +endfunction + +if filereadable(expand(':r') . '.VIM') " is case-insensitive or not + let s:_unify_path_cache = {} + " resolve() is slow, so we cache results. + " Note: On windows, vim can't expand path names from 8.3 formats. + " So if getting full path via and $HOME was set as 8.3 format, + " vital load duplicated scripts. Below's :~ avoid this issue. + function! s:_unify_path(path) abort + if has_key(s:_unify_path_cache, a:path) + return s:_unify_path_cache[a:path] + endif + let value = tolower(fnamemodify(resolve(fnamemodify( + \ a:path, ':p')), ':~:gs?[\\/]?/?')) + let s:_unify_path_cache[a:path] = value + return value + endfunction +else + function! s:_unify_path(path) abort + return resolve(fnamemodify(a:path, ':p:gs?[\\/]?/?')) + endfunction +endif + +" copied and modified from Vim.ScriptLocal +let s:SNR = join(map(range(len("\")), '"[\\x" . printf("%0x", char2nr("\"[v:val])) . "]"'), '') +function! s:sid2sfuncs(sid) abort + let fs = split(execute(printf(':function /^%s%s_', s:SNR, a:sid)), "\n") + let r = {} + let pattern = printf('\m^function\s%d_\zs\w\{-}\ze(', a:sid) + for fname in map(fs, 'matchstr(v:val, pattern)') + let r[fname] = function(s:_sfuncname(a:sid, fname)) + endfor + return r +endfunction + +"" Return funcname of script local functions with SID +function! s:_sfuncname(sid, funcname) abort + return printf('%s_%s', a:sid, a:funcname) +endfunction diff --git a/autoload/vital/lsp.vital b/autoload/vital/lsp.vital new file mode 100644 index 000000000..24db5525c --- /dev/null +++ b/autoload/vital/lsp.vital @@ -0,0 +1,5 @@ +lsp +274654a8153a620b94c7e6869e838d43b8d9e701 + +VS.Vim.Window.FloatingWindow +VS.Vim.Buffer