From d9f5c019072b95fbe2569df5eb39bd69db53ccde Mon Sep 17 00:00:00 2001 From: Yegappan Lakshmanan Date: Thu, 10 Dec 2020 10:09:11 -0800 Subject: [PATCH] Add support for displaying signature help --- autoload/lsp.vim | 172 ++++++++++++++++++++++++++++++++++++++++------- plugin/lsp.vim | 1 + 2 files changed, 149 insertions(+), 24 deletions(-) diff --git a/autoload/lsp.vim b/autoload/lsp.vim index 897650b..aa784c4 100644 --- a/autoload/lsp.vim +++ b/autoload/lsp.vim @@ -4,22 +4,77 @@ vim9script var lsp_servers: dict> = {} -def lsp#process_reply(req: dict, reply: dict): void - if req.method == 'textDocument/definition' || req.method == 'textDocument/declaration' - if reply.result->len() == 0 - echomsg "Error: definition is not found" - else - var result: dict = reply.result[0] - var file = result.uri[7:] - var wid = bufwinid(file) - if wid != -1 - win_gotoid(wid) - else - exe 'split ' .. file - endif - cursor(result.range.start.line + 1, result.range.start.character + 1) - redraw! - endif +var lsp_log_dir: string = '/tmp/' + +# process the initialize reply from the LSP server +def lsp#processInitializeReply(ftype: string, reply: dict): void + if reply.result->len() <= 0 + return + endif + + var caps: dict = reply.result.capabilities + lsp_servers[ftype].caps = caps + if caps->has_key('signatureHelpProvider') + var triggers = caps.signatureHelpProvider.triggerCharacters + for ch in triggers + exe 'inoremap ' .. ch .. ' ' .. ch .. "=lsp#show_signature()" + endfor + endif +enddef + +# process the 'textDocument/definition'/'textDocument/declaration' replies +# from the LSP server +def lsp#processDefDeclReply(reply: dict): void + if reply.result->len() == 0 + echomsg "Error: definition is not found" + return + endif + + var result: dict = reply.result[0] + var file = result.uri[7:] + var wid = bufwinid(file) + if wid != -1 + win_gotoid(wid) + else + exe 'split ' .. file + endif + cursor(result.range.start.line + 1, result.range.start.character + 1) + redraw! +enddef + +# process the signatureHelp reply from the LSP server +def lsp#process_signatureHelp_reply(reply: dict): void + var result: dict = reply.result + if result.signatures->len() <= 0 + return + endif + + var sig: dict = result.signatures[result.activeSignature] + var text = sig.label + var hllen = 0 + var startcol = 0 + var params_len = sig.parameters->len() + if params_len > 0 && result.activeParameter < params_len + var label = sig.parameters[result.activeParameter].label + hllen = label->len() + startcol = text->stridx(label) + endif + var popupID = popup_atcursor(text, {}) + prop_type_add('signature', {'bufnr': popupID->winbufnr(), + 'highlight': 'Title'}) + if hllen > 0 + prop_add(1, startcol + 1, {'bufnr': popupID->winbufnr(), 'length': hllen, 'type': 'signature'}) + endif +enddef + +# Process varous reply messages from the LSP server +def lsp#process_reply(ftype: string, req: dict, reply: dict): void + if req.method == 'initialize' + lsp#processInitializeReply(ftype, reply) + elseif req.method == 'textDocument/definition' || req.method == 'textDocument/declaration' + lsp#processDefDeclReply(reply) + elseif req.method == 'textDocument/signatureHelp' + lsp#process_signatureHelp_reply(reply) endif enddef @@ -61,7 +116,7 @@ def lsp#process_server_msg(ftype: string): void if reply->has_key('error') echomsg "Error: request " .. req.method .. " failed (" .. reply.error.message .. ")" else - lsp#process_reply(req, reply) + lsp#process_reply(ftype, req, reply) endif endif @@ -70,13 +125,13 @@ def lsp#process_server_msg(ftype: string): void enddef def lsp#output_cb(ftype: string, chan: channel, msg: string): void - #writefile(split(msg, "\n"), 'lsp_server.out', 'a') + writefile(split(msg, "\n"), lsp_log_dir .. 'lsp_server.out', 'a') lsp_servers[ftype].data = lsp_servers[ftype].data .. msg lsp#process_server_msg(ftype) enddef def lsp#error_cb(ftype: string, chan: channel, emsg: string,): void - #writefile(split(emsg, "\n"), 'lsp_server.err', 'a') + writefile(split(emsg, "\n"), lsp_log_dir .. 'lsp_server.err', 'a') enddef def lsp#exit_cb(ftype: string, job: job, status: number): void @@ -96,7 +151,9 @@ def lsp#sendto_server(ftype: string, content: dict): void var msg = "Content-Length: " .. req_js->len() .. "\r\n\r\n" var ch = lsp_servers[ftype].job->job_getchannel() ch->ch_sendraw(msg) + call writefile(["req_js length = " .. req_js->len()], "lsp_trace.txt", 'a') ch->ch_sendraw(req_js) + call writefile(["After sending data"], "lsp_trace.txt", 'a') enddef def lsp#create_reqmsg(ftype: string, method: string): dict @@ -145,9 +202,10 @@ def lsp#start_server(ftype: string): number 'err_cb': function('lsp#error_cb', [ftype]), 'exit_cb': function('lsp#exit_cb', [ftype])} - #writefile([], 'lsp_server.out') - #writefile([], 'lsp_server.err') + writefile([], lsp_log_dir .. 'lsp_server.out') + writefile([], lsp_log_dir .. 'lsp_server.err') lsp_servers[ftype].data = '' + lsp_servers[ftype].caps = {} lsp_servers[ftype].nextID = 1 lsp_servers[ftype].requests = {} @@ -173,7 +231,7 @@ enddef # Send a 'exit' notification to the LSP server def lsp#exit_server(ftype: string): void - var req = lsp#create_notifmsg(ftype, 'exit') + var req: dict = lsp#create_notifmsg(ftype, 'exit') lsp#sendto_server(ftype, req) enddef @@ -200,7 +258,7 @@ enddef # Send a LSP "textDocument/didOpen" notification def lsp#textdoc_didopen(fname: string, ftype: string): void - var notif = lsp#create_notifmsg(ftype, 'textDocument/didOpen') + var notif: dict = lsp#create_notifmsg(ftype, 'textDocument/didOpen') # interface DidOpenTextDocumentParams # interface TextDocumentItem @@ -216,7 +274,7 @@ enddef # Send a LSP "textDocument/didClose" notification def lsp#textdoc_didclose(fname: string, ftype: string): void - var notif = lsp#create_notifmsg(ftype, 'textDocument/didClose') + var notif: dict = lsp#create_notifmsg(ftype, 'textDocument/didClose') # interface DidCloseTextDocumentParams # interface TextDocumentIdentifier @@ -279,6 +337,65 @@ def lsp#goto_declaration(fname: string, ftype: string, lnum: number, col: number lsp#sendto_server(ftype, req) enddef +# Get the signature using "textDocument/signatureHelp" LSP request +def lsp#show_signature(): string + + # first send all the changes to the current buffer to the LSP server + listener_flush() + + var fname: string = expand('%:p') + var ftype: string = &filetype + var lnum: number = line('.') - 1 + var col: number = col('.') - 1 + + if fname == '' || ftype == '' + return '' + endif + if !lsp_servers->has_key(ftype) + echomsg 'Error: LSP server for "' .. ftype .. '" filetype is not found' + return '' + endif + if !lsp_servers[ftype].running + echomsg 'Error: LSP server for "' .. ftype .. '" filetype is not running' + return '' + endif + + var req = lsp#create_reqmsg(ftype, 'textDocument/signatureHelp') + # interface SignatureHelpParams + # interface TextDocumentPositionParams + # interface TextDocumentIdentifier + req.params->extend({'textDocument': {'uri': 'file://' .. fname}}) + # interface Position + req.params->extend({'position': {'line': lnum, 'character': col}}) + + lsp#sendto_server(ftype, req) + return '' +enddef + +# buffer change notification listener +def lsp#bufchange_listener(bnum: number, start: number, end: number, added: number, changes: list>) + var ftype = getbufvar(bnum, '&filetype') + var notif: dict = lsp#create_notifmsg(ftype, 'textDocument/didChange') + + # interface DidChangeTextDocumentParams + # interface VersionedTextDocumentIdentifier + var vtdid: dict = {} + vtdid.uri = 'file://' .. fnamemodify(bufname(bnum), ':p') + # Use Vim 'changedtick' as the LSP document version number + vtdid.version = getbufvar(bnum, 'changedtick') + notif.params->extend({'textDocument': vtdid}) + # interface TextDocumentContentChangeEvent + var changeset: list> = [] + # Range + var range: dict> = {} + range.start = {'line': start - 1, 'character': 0} + range.end = {'line': end - 2, 'character': 0} + changeset->add({'range': range, 'text': getbufline(bnum, start, end - 1)->join("\n")}) + notif.params->extend({'contentChanges': changeset}) + + lsp#sendto_server(ftype, notif) +enddef + def lsp#add_file(fname: string, ftype: string): void if fname == '' || ftype == '' || !lsp_servers->has_key(ftype) return @@ -287,6 +404,12 @@ def lsp#add_file(fname: string, ftype: string): void lsp#start_server(ftype) endif lsp#textdoc_didopen(fname, ftype) + + # add a listener to track changes to this buffer + var bnum = bufnr(fname) + if bnum != -1 + listener_add(function('lsp#bufchange_listener'), bnum) + endif enddef def lsp#remove_file(fname: string, ftype: string): void @@ -315,6 +438,7 @@ def lsp#add_server(ftype: string, serverpath: string, args: list) 'job': v:none, 'data': '', 'nextID': 1, + 'caps': {}, 'requests': {} # outstanding LSP requests } lsp_servers->extend({[ftype]: sinfo}) diff --git a/plugin/lsp.vim b/plugin/lsp.vim index 442a724..811efdd 100644 --- a/plugin/lsp.vim +++ b/plugin/lsp.vim @@ -11,4 +11,5 @@ autocmd BufWipeOut * call lsp#remove_file(expand(':p'), &filetype) command! -nargs=0 LspGotoDefinition call lsp#goto_definition(expand('%:p'), &filetype, line('.') - 1, col('.') - 1) command! -nargs=0 LspGotoDeclaration call lsp#goto_declaration(expand('%:p'), &filetype, line('.') - 1, col('.') - 1) +command! -nargs=0 LspGetSignature call lsp#show_signature('') -- 2.48.1