Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for workspace folders #383

Open
wants to merge 26 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
37f6517
Experiment with workspaceFolders
natebosch Jan 21, 2021
d4b7435
Start hacking together a test
natebosch Jan 24, 2021
1027818
Merge branch 'master' into workspace-folders
natebosch Jan 24, 2021
ea701cd
Make directory URIs end in /, fix test for initialization
natebosch Jan 24, 2021
a6efb87
More tests passing
natebosch Jan 24, 2021
a56c349
Get existing tests passing
natebosch Jan 24, 2021
2a9eadd
Drop some echos, implement byMarker for real
natebosch Jan 24, 2021
b5e36b9
Try to handle a callback that throws, seems to not be working
natebosch Jan 24, 2021
8587c83
Fix the test
natebosch Jan 24, 2021
14e7f71
Test cleanup - dup and consistent naming
natebosch Jan 24, 2021
917ccab
Only set workspaceFolders on initialization if it will be supported
natebosch Jan 24, 2021
e612766
Potentially friendlier name
natebosch Jan 24, 2021
a1fe031
Handle failure on later calls
natebosch Jan 25, 2021
3de130d
Merge branch 'master' into workspace-folders
natebosch Jan 28, 2021
7b877e1
Add an error message, remove some prints
natebosch Jan 30, 2021
227a22c
Merge branch 'master' into workspace-folders
natebosch Feb 5, 2021
c9eb6a5
Consistently use trailing /
natebosch Feb 8, 2021
4122cd3
Another trailing slash
natebosch Feb 28, 2021
bcef1fd
Trailing slashes in test expectations
natebosch Feb 28, 2021
b072c6a
Merge branch 'master' into workspace-folders
natebosch Mar 10, 2021
0d4dae2
Merge branch 'master' into workspace-folders
natebosch Mar 15, 2021
fbcce1c
Drop trailing slashes
natebosch Mar 16, 2021
fcc8822
Merge branch 'master' into workspace-folders
natebosch Mar 20, 2021
5fa9822
Merge branch 'master' into workspace-folders
natebosch Mar 23, 2021
c67ff23
Merge branch 'master' into workspace-folders
natebosch Apr 21, 2022
235f569
Merge branch 'master' into workspace-folders
natebosch Apr 26, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions autoload/lsc/capabilities.vim
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,21 @@ function! lsc#capabilities#normalize(capabilities) abort
else
let l:normalized.referenceHighlights = l:document_highlight_provider
endif
if has_key(a:capabilities, 'workspace')
let l:workspace = a:capabilities.workspace
if has_key(l:workspace, 'workspaceFolders')
let l:workspace_folders = l:workspace.workspaceFolders
if has_key(l:workspace_folders, 'changeNotifications')
if type(l:workspace_folders.changeNotifications) == type(v:true)
let l:normalized.workspace.didChangeWorkspaceFolders =
\ l:workspace_folders.changeNotifications
else
" Does not handle deregistration
let l:normalized.workspace.didChangeWorkspaceFolders = v:true
endif
endif
endif
endif
return l:normalized
endfunction

Expand All @@ -45,5 +60,8 @@ function! lsc#capabilities#defaults() abort
\ 'sendDidSave': v:false,
\ },
\ 'referenceHighlights': v:false,
\ 'workspace': {
\ 'didChangeWorkspaceFolders': v:false,
\ },
\}
endfunction
25 changes: 24 additions & 1 deletion autoload/lsc/file.vim
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function! lsc#file#onOpen() abort
if l:server.status ==# 'running'
call s:DidOpen(l:server, l:bufnr, l:file_path, &filetype)
else
call lsc#server#start(l:server)
call lsc#server#start(l:server, l:file_path)
endif
endfor
endif
Expand Down Expand Up @@ -90,6 +90,7 @@ function! s:DidOpen(server, bufnr, file_path, filetype) abort
\ }
\ }
if a:server.notify('textDocument/didOpen', l:params)
call s:UpdateRoots(a:server, a:file_path)
let s:file_versions[a:file_path] = l:version
if get(g:, 'lsc_enable_incremental_sync', v:true)
\ && a:server.capabilities.textDocumentSync.incremental
Expand All @@ -99,6 +100,28 @@ function! s:DidOpen(server, bufnr, file_path, filetype) abort
endif
endfunction

function! s:UpdateRoots(server, file_path) abort
if !has_key(a:server.config, 'WorkspaceRoot') | return | endif
if !a:server.capabilities.workspace.didChangeWorkspaceFolders | return | endif
try
let l:root = a:server.config.WorkspaceRoot(a:file_path)
catch
return
endtry
if index(a:server.roots, l:root) >= 0 | return | endif
call add(a:server.roots, l:root)
let l:workspace_folders = {'event':
\ {'added': [{
\ 'uri': lsc#uri#documentUri(l:root),
\ 'name': fnamemodify(l:root, ':.'),
\ }],
\ 'removed': [],
\ },
\ }
call a:server.notify('workspace/didChangeWorkspaceFolders',
\ l:workspace_folders)
endfunction

" Mark all files of type `filetype` as untracked.
function! lsc#file#clean(filetype) abort
for l:buffer in getbufinfo({'bufloaded': v:true})
Expand Down
47 changes: 37 additions & 10 deletions autoload/lsc/server.vim
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,22 @@ if !exists('s:initialized')
" - capabilities. Configuration for client/server interaction.
" - filetypes. List of filetypes handled by this server.
" - logs. The last 100 logs from `window/logMessage`.
" - roots. All workspace folders seen by this server.
" - config. Config dict. Contains:
" - name: Same as the key into `s:servers`
" - command: Executable
" - enabled: (optional) Whether the server should be started.
" - message_hooks: (optional) Functions call to override params
" - workspace_config: (optional) Arbitrary data to send as
" `workspace/didChangeConfiguration` settings on startup.
" - WorkspaceRoot: (optional) Callback to discover the root of the project
" containing a given file path.
let s:servers = {}
let s:initialized = v:true
endif

function! lsc#server#start(server) abort
call s:Start(a:server)
function! lsc#server#start(server, file_path) abort
call s:Start(a:server, a:file_path)
endfunction

function! lsc#server#status(filetype) abort
Expand Down Expand Up @@ -99,9 +102,10 @@ function! lsc#server#restart() abort
let l:server = s:servers[l:server_name]
let l:old_status = l:server.status
if l:old_status ==# 'starting' || l:old_status ==# 'running'
let l:server.started_from = lsc#file#fullPath()
call s:Kill(l:server, 'restarting', v:null)
else
call s:Start(l:server)
call s:Start(l:server, lsc#file#fullPath())
endif
endfunction

Expand All @@ -119,7 +123,7 @@ function! lsc#server#userCall(method, params, callback) abort
endfunction

" Start `server` if it isn't already running.
function! s:Start(server) abort
function! s:Start(server, file_path) abort
if has_key(a:server, '_channel')
" Server is already running
return
Expand All @@ -139,12 +143,30 @@ function! s:Start(server) abort
else
let l:trace_level = 'off'
endif
try
let l:root = has_key(a:server.config, 'WorkspaceRoot')
\ ? a:server.config.WorkspaceRoot(a:file_path)
\ : lsc#file#cwd()
catch
call lsc#message#error(
\ 'Disabling workspace roots due to error: '.string(v:exception))
let l:root = lsc#file#cwd()
unlet a:server.config.WorkspaceRoot
endtry
let a:server.roots = [l:root]
let l:capabilities = s:ClientCapabilities(a:server.config)
let l:params = {'processId': getpid(),
\ 'clientInfo': {'name': 'vim-lsc'},
\ 'rootUri': lsc#uri#documentUri(lsc#file#cwd()),
\ 'capabilities': s:ClientCapabilities(),
\ 'trace': l:trace_level
\ 'rootUri': lsc#uri#documentUri(l:root),
\ 'capabilities': l:capabilities,
\ 'trace': l:trace_level,
\}
if l:capabilities.workspace.workspaceFolders
let l:params.workspaceFolders = [{
\ 'uri': lsc#uri#documentUri(l:root),
\ 'name': fnamemodify(l:root, ':.')
\ }]
endif
call a:server._initialize(l:params, funcref('<SID>OnInitialize', [a:server]))
endfunction

Expand All @@ -164,7 +186,7 @@ function! s:OnInitialize(server, init_result) abort
endfunction

" Missing value means no support
function! s:ClientCapabilities() abort
function! s:ClientCapabilities(config) abort
let l:applyEdit = v:false
if !exists('g:lsc_enable_apply_edit') || g:lsc_enable_apply_edit
let l:applyEdit = v:true
Expand All @@ -173,6 +195,8 @@ function! s:ClientCapabilities() abort
\ 'workspace': {
\ 'applyEdit': l:applyEdit,
\ 'configuration': v:true,
\ 'workspaceFolders':
\ has_key(a:config, 'WorkspaceRoot') ? v:true : v:false,
\ },
\ 'textDocument': {
\ 'synchronization': {
Expand Down Expand Up @@ -221,7 +245,7 @@ function! lsc#server#enable() abort
endif
let l:server = s:servers[g:lsc_servers_by_filetype[&filetype]]
let l:server.config.enabled = v:true
call s:Start(l:server)
call s:Start(l:server, lsc#file#fullPath())
endfunction

function! lsc#server#register(filetype, config) abort
Expand Down Expand Up @@ -294,6 +318,7 @@ function! lsc#server#register(filetype, config) abort
endfunction
function! l:server.on_exit() abort
unlet l:self._channel
unlet l:self.roots
let l:old_status = l:self.status
if l:old_status ==# 'starting'
let l:self.status= 'failed'
Expand All @@ -315,7 +340,9 @@ function! lsc#server#register(filetype, config) abort
call lsc#cursor#clean()
endfor
if l:old_status ==# 'restarting'
call s:Start(l:self)
let l:started_from = l:self.started_from
unlet l:self.started_from
call s:Start(l:self, l:started_from)
endif
endfunction
function! l:server.find_config(item) abort
Expand Down
36 changes: 36 additions & 0 deletions autoload/lsc/workspace.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
function! lsc#workspace#byMarker(markers) abort
return function('<SID>FindByMarkers', [a:markers])
endfunction

function! s:FindByMarkers(markers, file_path) abort
for l:path in s:ParentDirectories(a:file_path)
if s:ContainsAny(l:path, a:markers) | return l:path | endif
endfor
return fnamemodify(a:file_path, ':h')
endfunction

" Whether `path` contains any children from `markers`.
function! s:ContainsAny(path, markers) abort
for l:marker in a:markers
if l:marker[-1:] ==# '/'
if isdirectory(a:path.'/'.l:marker) | return v:true | endif
else
if filereadable(a:path.'/'.l:marker) | return v:true | endif
endif
endfor
return v:false
endfunction

" Returns a list of the parents of the current file up to a root directory.
function! s:ParentDirectories(file_path) abort
let l:dirs = []
let l:current_dir = fnamemodify(a:file_path, ':h')
let l:parent = fnamemodify(l:current_dir, ':h')
while l:parent != l:current_dir
call add(l:dirs, l:current_dir)
let l:current_dir = l:parent
let l:parent = fnamemodify(l:parent, ':h')
endwhile
call add(l:dirs, l:current_dir)
return l:dirs
endfunction
2 changes: 1 addition & 1 deletion plugin/lsc.vim
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ function! RegisterLanguageServer(filetype, config) abort
call lsc#file#track(l:server, l:buffer, a:filetype)
endfor
else
call lsc#server#start(l:server)
call lsc#server#start(l:server, lsc#file#normalize(l:buffers[0].name))
endif
endfunction

Expand Down
5 changes: 4 additions & 1 deletion test/integration/lib/stub_lsp.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import 'package:json_rpc_2/json_rpc_2.dart';

class StubServer {
final Peer peer;
Future<Map<String, dynamic>> get initialization => _initialization.future;
final _initialization = Completer<Map<String, dynamic>>();

StubServer(this.peer, {Map<String, dynamic> capabilities = const {}}) {
peer
..registerMethod('initialize', (_) {
..registerMethod('initialize', (Parameters p) {
_initialization.complete(p.asMap.cast<String, dynamic>());
return {'capabilities': capabilities};
})
..registerMethod('initialized', (_) {
Expand Down
5 changes: 5 additions & 0 deletions test/integration/lib/vim_remote.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ class Vim {
final result = await Process.run(
'vim', [..._serverNameArg, '--remote-expr', expression]);
final stdout = result.stdout as String;
final stderr = result.stderr as String;
if (stderr.isNotEmpty) {
throw Exception(
'Failed to evaluate vim expression [$expression]:\n$stderr');
}
return stdout.endsWith('\n')
? stdout.substring(0, stdout.length - 1)
: stdout;
Expand Down
Loading