From 1ac661697d98f67b912a750c56b6437be9334395 Mon Sep 17 00:00:00 2001 From: Yegappan Lakshmanan Date: Mon, 28 Dec 2020 19:32:27 -0800 Subject: [PATCH] Add support for URI encoding/decoding --- autoload/lsp.vim | 111 ++++++++++++++++++++++++++++++++++++----------- plugin/lsp.vim | 9 +++- 2 files changed, 93 insertions(+), 27 deletions(-) diff --git a/autoload/lsp.vim b/autoload/lsp.vim index f075c31..1c0011b 100644 --- a/autoload/lsp.vim +++ b/autoload/lsp.vim @@ -13,6 +13,9 @@ var lspServers: list> = [] # filetype to LSP server map var ftypeServerMap: dict> = {} +# Buffer number to LSP server map +var bufnrToServer: dict> = {} + # List of diagnostics for each opened file var diagsMap: dict = {} @@ -46,7 +49,12 @@ def s:lspAddServer(ftype: string, lspserver: dict) enddef # Lsp server trace log directory -var lsp_log_dir: string = '/tmp/' +var lsp_log_dir: string +if has('unix') + lsp_log_dir = '/tmp/' +else + lsp_log_dir = $TEMP .. '\\' +endif var lsp_server_trace: bool = v:false def lsp#enableServerTrace() @@ -91,12 +99,46 @@ enddef # Convert a LSP file URI (file://) to a Vim file name def LspUriToFile(uri: string): string - return uri[7:] + # Replace all the %xx numbers (e.g. %20 for space) in the URI to character + var uri_decoded: string = substitute(uri, '%\(\x\x\)', + '\=nr2char(str2nr(submatch(1), 16))', 'g') + + # File URIs on MS-Windows start with file:///[a-zA-Z]:' + if uri_decoded =~? '^file:///\a:' + # MS-Windows URI + uri_decoded = uri_decoded[8:] + uri_decoded = uri_decoded->substitute('/', '\\', 'g') + else + uri_decoded = uri_decoded[7:] + endif + + return uri_decoded enddef # Convert a Vim filenmae to an LSP URI (file://) def LspFileToUri(fname: string): string - return 'file://' .. fnamemodify(fname, ':p') + var uri: string = fnamemodify(fname, ':p') + + var on_windows: bool = v:false + if uri =~? '^\a:' + on_windows = v:true + endif + + if on_windows + # MS-Windows + uri = uri->substitute('\\', '/', 'g') + endif + + uri = uri->substitute('\([^A-Za-z0-9-._~:/]\)', + '\=printf("%%%02x", char2nr(submatch(1)))', 'g') + + if on_windows + uri = 'file:///' .. uri + else + uri = 'file://' .. uri + endif + + return uri enddef # process the 'initialize' method reply from the LSP server @@ -324,7 +366,7 @@ def s:processDocHighlightReply(lspserver: dict, req: dict, reply: dict endif var fname: string = LspUriToFile(req.params.textDocument.uri) - var bnum = bufnr(fname) + var bnr = bufnr(fname) for docHL in reply.result var kind: number = docHL->get('kind', 1) @@ -342,7 +384,7 @@ def s:processDocHighlightReply(lspserver: dict, req: dict, reply: dict prop_add(docHL.range.start.line + 1, docHL.range.start.character + 1, {'end_lnum': docHL.range.end.line + 1, 'end_col': docHL.range.end.character + 1, - 'bufnr': bnum, + 'bufnr': bnr, 'type': propName}) endfor enddef @@ -722,6 +764,8 @@ enddef def s:processDiagNotif(lspserver: dict, reply: dict): void var fname: string = LspUriToFile(reply.params.uri) diagsMap->extend({[fname]: reply.params.diagnostics}) + + # store the diagnostic for each line separately enddef # process notification messages from the LSP server @@ -762,7 +806,8 @@ def s:processMessages(lspserver: dict): void idx = idx + 4 if lspserver.data->len() - idx < len - ErrMsg("Error: Didn't receive a complete message") + # message is not fully received. Process the message after more data is + # received return endif @@ -946,29 +991,29 @@ def s:stopServer(lspserver: dict): number enddef # Send a LSP "textDocument/didOpen" notification -def s:textdocDidOpen(lspserver: dict, bnum: number, ftype: string): void +def s:textdocDidOpen(lspserver: dict, bnr: number, ftype: string): void var notif: dict = lspserver.createNotification('textDocument/didOpen') # interface DidOpenTextDocumentParams # interface TextDocumentItem var tdi = {} - tdi.uri = LspFileToUri(bufname(bnum)) + tdi.uri = LspFileToUri(bufname(bnr)) tdi.languageId = ftype tdi.version = 1 - tdi.text = getbufline(bnum, 1, '$')->join("\n") .. "\n" + tdi.text = getbufline(bnr, 1, '$')->join("\n") .. "\n" notif.params->extend({'textDocument': tdi}) lspserver.sendMessage(notif) enddef # Send a LSP "textDocument/didClose" notification -def s:textdocDidClose(lspserver: dict, fname: string): void +def s:textdocDidClose(lspserver: dict, bnr: number): void var notif: dict = lspserver.createNotification('textDocument/didClose') # interface DidCloseTextDocumentParams # interface TextDocumentIdentifier var tdid = {} - tdid.uri = LspFileToUri(fname) + tdid.uri = LspFileToUri(bufname(bnr)) notif.params->extend({'textDocument': tdid}) lspserver.sendMessage(notif) @@ -1215,8 +1260,8 @@ def lsp#showSignature(): string enddef # buffer change notification listener -def lsp#bufchange_listener(bnum: number, start: number, end: number, added: number, changes: list>) - var ftype = bnum->getbufvar('&filetype') +def lsp#bufchange_listener(bnr: number, start: number, end: number, added: number, changes: list>) + var ftype = bnr->getbufvar('&filetype') var lspserver: dict = s:lspGetServer(ftype) if lspserver->empty() || !lspserver.running return @@ -1227,9 +1272,9 @@ def lsp#bufchange_listener(bnum: number, start: number, end: number, added: numb # interface DidChangeTextDocumentParams # interface VersionedTextDocumentIdentifier var vtdid: dict = {} - vtdid.uri = LspFileToUri(bufname(bnum)) + vtdid.uri = LspFileToUri(bufname(bnr)) # Use Vim 'changedtick' as the LSP document version number - vtdid.version = bnum->getbufvar('changedtick') + vtdid.version = bnr->getbufvar('changedtick') notif.params->extend({'textDocument': vtdid}) # interface TextDocumentContentChangeEvent var changeset: list> @@ -1248,7 +1293,7 @@ def lsp#bufchange_listener(bnum: number, start: number, end: number, added: numb # # lines changed # start_lnum = change.lnum - 1 # end_lnum = change.end - 1 - # lines = getbufline(bnum, change.lnum, change.end - 1)->join("\n") .. "\n" + # lines = getbufline(bnr, change.lnum, change.end - 1)->join("\n") .. "\n" # start_col = 0 # end_col = 0 # elseif change.added > 0 @@ -1257,7 +1302,7 @@ def lsp#bufchange_listener(bnum: number, start: number, end: number, added: numb # end_lnum = change.lnum - 1 # start_col = 0 # end_col = 0 - # lines = getbufline(bnum, change.lnum, change.lnum + change.added - 1)->join("\n") .. "\n" + # lines = getbufline(bnr, change.lnum, change.lnum + change.added - 1)->join("\n") .. "\n" # else # # lines removed # start_lnum = change.lnum - 1 @@ -1269,7 +1314,7 @@ def lsp#bufchange_listener(bnum: number, start: number, end: number, added: numb # 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(bnum, 1, '$')->join("\n") .. "\n"}) + changeset->add({'text': getbufline(bnr, 1, '$')->join("\n") .. "\n"}) notif.params->extend({'contentChanges': changeset}) lspserver.sendMessage(notif) @@ -1302,7 +1347,13 @@ def s:lspSavedFile() enddef # A new buffer is opened. If LSP is supported for this buffer, then add it -def lsp#addFile(bnum: number, ftype: string): void +def lsp#addFile(bnr: number): void + if bufnrToServer->has_key(bnr) + # LSP server for this buffer is already initialized and running + return + endif + + var ftype: string = bnr->getbufvar('&filetype') if ftype == '' return endif @@ -1313,7 +1364,7 @@ def lsp#addFile(bnum: number, ftype: string): void if !lspserver.running lspserver.startServer() endif - lspserver.textdocDidOpen(bnum, ftype) + lspserver.textdocDidOpen(bnr, ftype) # Display hover information autocmd CursorHold call s:LspHover() @@ -1321,13 +1372,22 @@ def lsp#addFile(bnum: number, ftype: string): void autocmd BufWritePost call s:lspSavedFile() # add a listener to track changes to this buffer - listener_add(function('lsp#bufchange_listener'), bnum) - setbufvar(bnum, '&completefunc', 'lsp#completeFunc') - setbufvar(bnum, '&completeopt', 'menuone,preview,noinsert') + listener_add(function('lsp#bufchange_listener'), bnr) + setbufvar(bnr, '&completefunc', 'lsp#completeFunc') + setbufvar(bnr, '&completeopt', 'menuone,preview,noinsert') + + bufnrToServer[bnr] = lspserver enddef # Notify LSP server to remove a file -def lsp#removeFile(fname: string, ftype: string): void +def lsp#removeFile(bnr: number): void + if !bufnrToServer->has_key(bnr) + # LSP server for this buffer is not running + return + endif + + var fname: string = bufname(bnr) + var ftype: string = bnr->getbufvar('&filetype') if fname == '' || ftype == '' return endif @@ -1335,10 +1395,11 @@ def lsp#removeFile(fname: string, ftype: string): void if lspserver->empty() || !lspserver.running return endif - lspserver.textdocDidClose(fname) + lspserver.textdocDidClose(bnr) if diagsMap->has_key(fname) diagsMap->remove(fname) endif + remove(bufnrToServer, bnr) enddef # Stop all the LSP servers diff --git a/plugin/lsp.vim b/plugin/lsp.vim index cece095..30ff039 100644 --- a/plugin/lsp.vim +++ b/plugin/lsp.vim @@ -5,8 +5,13 @@ if v:version < 802 || !has('patch-8.2.2082') finish endif -autocmd BufNewFile,BufReadPost * call lsp#addFile(expand('') + 0, &filetype) -autocmd BufWipeOut * call lsp#removeFile(expand(':p'), &filetype) +augroup LSPAutoCmds + au! + autocmd BufNewFile,BufReadPost * + \ call lsp#addFile(expand('') + 0) + autocmd BufWipeOut * + \ call lsp#removeFile(expand('') + 0) +augroup END " TODO: Is it needed to shutdown all the LSP servers when exiting Vim? " This takes some time. -- 2.48.1