README.md | 13 ++++++++++--- autoload/buf.vim | 2 +- autoload/handlers.vim | 66 ++++++++++++++++++++++++++++++++++++++++++++++------- autoload/lsp.vim | 22 +++++++++++++++++++++- autoload/lspserver.vim | 30 +++++++++++++++++++++++++----- diff --git a/README.md b/README.md index 5620e139577ff8733de62809f814d13925d378fb..cb286d07aba08d85b3082ddd696baf1c76317670 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,19 @@ You can install this plugin directly from github using the following steps: ``` - $ mkdir -p $HOME/.vim/pack/downloads/lsp - $ cd $HOME/.vim/packa/downloads/lsp + $ mkdir -p $HOME/.vim/pack/downloads/opt/lsp + $ cd $HOME/.vim/pack/downloads/opt/lsp $ git clone https://github.com/yegappan/lsp ``` -or you can use any one of the Vim plugin managers (dein.vim, pathogen, vam, vim-plug, volt, Vundle, etc.) to install and manage this plugin. +After installing the plugin using the above steps, add the following line to +your $HOME/.vimrc file: + +``` + packadd lsp +``` + +You can also install and manage this plugin using any one of the Vim plugin managers (dein.vim, pathogen, vam, vim-plug, volt, Vundle, etc.). You will also need to install one or more language servers corresponding to the programming languages that you are using. Refer to the https://langserver.org/ page for the list of available language servers. diff --git a/autoload/buf.vim b/autoload/buf.vim index e11a21a971294b04f01d094529e4e1971c35123b..fb2309ccbbe120cc1a9619d9394312240b008978 100644 --- a/autoload/buf.vim +++ b/autoload/buf.vim @@ -33,7 +33,7 @@ endif var signs: list> = [] for [lnum, diag] in lspserver.diagsMap[bnr]->items() - signs->add({id: 0, buffer: str2nr(bnr), group: 'LSPDiag', + signs->add({id: 0, buffer: bnr, group: 'LSPDiag', lnum: str2nr(lnum), name: s:lspDiagSevToSignName(diag.severity)}) endfor diff --git a/autoload/handlers.vim b/autoload/handlers.vim index d644c9985bf23fd9be1e634a74f8ccf4814d2122..caf07830a9a574cb7a43cfc3a0568fabde8c7bd7 100644 --- a/autoload/handlers.vim +++ b/autoload/handlers.vim @@ -61,18 +61,38 @@ endif return endif - var result: dict = reply.result[0] - var file = LspUriToFile(result.uri) - var wid = file->bufwinid() + var location: dict + if reply.result->type() == v:t_list + location = reply.result[0] + else + location = reply.result + endif + var fname = LspUriToFile(location.uri) + var wid = fname->bufwinid() if wid != -1 wid->win_gotoid() else - exe 'split ' .. file + var bnr: number = fname->bufnr() + if bnr != -1 + if &modified || &buftype != '' + exe 'sb ' .. bnr + else + exe 'buf ' .. bnr + endif + else + if &modified || &buftype != '' + # if the current buffer has unsaved changes, then open the file in a + # new window + exe 'split ' .. fname + else + exe 'edit ' .. fname + endif + endif endif # Set the previous cursor location mark setpos("'`", getcurpos()) - setcursorcharpos(result.range.start.line + 1, - result.range.start.character + 1) + setcursorcharpos(location.range.start.line + 1, + location.range.start.character + 1) redraw! enddef @@ -89,7 +109,12 @@ WarnMsg('No signature help available') return endif - var sig: dict = result.signatures[result.activeSignature] + var sigidx: number = 0 + if result->has_key('activeSignature') + sigidx = result.activeSignature + endif + + var sig: dict = result.signatures[sigidx] var text = sig.label var hllen = 0 var startcol = 0 @@ -190,6 +215,11 @@ if g:LSP_24x7_Complete if completeItems->empty() # no matches + return + endif + + if mode() != 'i' && mode() != 'R' && mode() != 'Rv' + # If not in insert or replace mode, then don't start the completion return endif @@ -867,6 +897,25 @@ lspserver.workspaceSymbolPopup->popup_settext( symbols->copy()->mapnew('v:val.name')) enddef +# process the 'textDocument/prepareCallHierarchy' reply from the LSP server +# Result: CallHierarchyItem[] | null +def s:processPrepareCallHierarchy(lspserver: dict, req: dict, reply: dict) + if reply.result->empty() + return + endif + + var items: list = ['Select a Call Hierarchy Item:'] + for i in range(reply.result->len()) + items->add(printf("%d. %s", i + 1, reply.result[i].name)) + endfor + var choice = inputlist(items) + if choice < 1 || choice > items->len() + return + endif + + echomsg reply.result[choice - 1] +enddef + # Process various reply messages from the LSP server export def ProcessReply(lspserver: dict, req: dict, reply: dict): void var lsp_reply_handlers: dict = @@ -889,7 +938,8 @@ 'textDocument/codeAction': function('s:processCodeActionReply'), 'textDocument/selectionRange': function('s:processSelectionRangeReply'), 'textDocument/foldingRange': function('s:processFoldingRangeReply'), 'workspace/executeCommand': function('s:processWorkspaceExecuteReply'), - 'workspace/symbol': function('s:processWorkspaceSymbolReply') + 'workspace/symbol': function('s:processWorkspaceSymbolReply'), + 'textDocument/prepareCallHierarchy': function('s:processPrepareCallHierarchy') } if lsp_reply_handlers->has_key(req.method) diff --git a/autoload/lsp.vim b/autoload/lsp.vim index 4bd3d78716889f6b1427ddb5dfff5d8fcf56ed85..d2607dbf829dc2ef9b949d7153416cfd60b07a37 100644 --- a/autoload/lsp.vim +++ b/autoload/lsp.vim @@ -1022,7 +1022,27 @@ # Display all the locations where the current symbol is called from. # Uses LSP "callHierarchy/incomingCalls" request def lsp#incomingCalls() - :echomsg 'Error: Not implemented yet' + var ftype = &filetype + if ftype == '' + return + endif + + var lspserver: dict = s:lspGetServer(ftype) + if lspserver->empty() + ErrMsg('Error: LSP server for "' .. ftype .. '" filetype is not found') + return + endif + if !lspserver.running + ErrMsg('Error: LSP server for "' .. ftype .. '" filetype is not running') + return + endif + + var fname = @% + if fname == '' + return + endif + + lspserver.incomingCalls(fname) enddef # Display all the symbols used by the current symbol. diff --git a/autoload/lspserver.vim b/autoload/lspserver.vim index 1c089d8e9b9003954daefd1bcfe04e8b0844d4d8..db972b6af5c1f0a72391a945f0cdc03f18620997 100644 --- a/autoload/lspserver.vim +++ b/autoload/lspserver.vim @@ -87,7 +87,7 @@ # client capabilities (ClientCapabilities) var clientCaps: dict = { workspace: { workspaceFolders: true, - applyEdit: true, + applyEdit: true }, textDocument: { foldingRange: {lineFoldingOnly: true}, @@ -120,10 +120,10 @@ } var curdir: string = getcwd() initparams.rootPath = curdir initparams.rootUri = LspFileToUri(curdir) - initparams.workspaceFolders = { + initparams.workspaceFolders = [{ name: fnamemodify(curdir, ':t'), uri: LspFileToUri(curdir) - } + }] initparams.trace = 'off' initparams.capabilities = clientCaps req.params->extend(initparams) @@ -283,7 +283,7 @@ var vtdid: dict = {} vtdid.uri = LspBufnrToUri(bnr) # Use Vim 'changedtick' as the LSP document version number vtdid.version = bnr->getbufvar('changedtick') - notif.params->extend({textDocument: vtdid}) + # interface TextDocumentContentChangeEvent var changeset: list> @@ -322,8 +322,9 @@ # endif # var range: dict> = {'start': {'line': start_lnum, 'character': start_col}, 'end': {'line': end_lnum, 'character': end_col}} # changeset->add({'range': range, 'text': lines}) # endfor + changeset->add({text: getbufline(bnr, 1, '$')->join("\n") .. "\n"}) - notif.params->extend({contentChanges: changeset}) + notif.params->extend({textDocument: vtdid, contentChanges: changeset}) lspserver.sendMessage(notif) enddef @@ -615,6 +616,24 @@ lspserver.sendMessage(req) enddef +# Request: "callHierarchy/incomingCalls" +# Param: CallHierarchyIncomingCallsParams +def s:incomingCalls(lspserver: dict, fname: string) + # Check whether LSP server supports incoming calls + if !lspserver.caps->has_key('callHierarchyProvider') + || !lspserver.caps.callHierarchyProvider + ErrMsg("Error: LSP server does not support call hierarchy") + return + endif + + var req = lspserver.createRequest('textDocument/prepareCallHierarchy') + + # interface CallHierarchyPrepareParams + # interface TextDocumentPositionParams + req.params->extend(s:getLspTextDocPosition()) + lspserver.sendMessage(req) +enddef + # Request: "textDocument/rename" # Param: RenameParams def s:renameSymbol(lspserver: dict, newName: string) @@ -832,6 +851,7 @@ showReferences: function('s:showReferences', [lspserver]), docHighlight: function('s:docHighlight', [lspserver]), getDocSymbols: function('s:getDocSymbols', [lspserver]), textDocFormat: function('s:textDocFormat', [lspserver]), + incomingCalls: function('s:incomingCalls', [lspserver]), renameSymbol: function('s:renameSymbol', [lspserver]), codeAction: function('s:codeAction', [lspserver]), workspaceQuery: function('s:workspaceQuerySymbols', [lspserver]),