endif
var signs: list<dict<any>> = []
- for [lnum, diag] in items(lspserver.diagsMap[bnr])
+ for [lnum, diag] in lspserver.diagsMap[bnr]->items()
signs->add({id: 0, buffer: str2nr(bnr), group: 'LSPDiag',
lnum: str2nr(lnum),
name: s:lspDiagSevToSignName(diag.severity)})
var file = LspUriToFile(result.uri)
var wid = file->bufwinid()
if wid != -1
- win_gotoid(wid)
+ wid->win_gotoid()
else
exe 'split ' .. file
endif
startcol = text->stridx(label)
endif
endif
- var popupID = popup_atcursor(text, {})
- prop_type_add('signature', {bufnr: winbufnr(popupID), highlight: 'Title'})
+ var popupID = text->popup_atcursor({})
+ prop_type_add('signature', {bufnr: popupID->winbufnr(), highlight: 'Title'})
if hllen > 0
- prop_add(1, startcol + 1, {bufnr: winbufnr(popupID), length: hllen, type: 'signature'})
+ prop_add(1, startcol + 1, {bufnr: popupID->winbufnr(), length: hllen, type: 'signature'})
endif
enddef
endif
var items: list<dict<any>>
- if type(reply.result) == v:t_list
+ if reply.result->type() == v:t_list
items = reply.result
else
items = reply.result.items
var hoverText: list<string>
- if type(reply.result.contents) == v:t_dict
+ if reply.result.contents->type() == v:t_dict
if reply.result.contents->has_key('kind')
# MarkupContent
if reply.result.contents.kind == 'plaintext'
ErrMsg('Error: Unsupported hover contents (' .. reply.result.contents .. ')')
return
endif
- elseif type(reply.result.contents) == v:t_list
+ elseif reply.result.contents->type() == v:t_list
# interface MarkedString[]
for e in reply.result.contents
- if type(e) == v:t_string
+ if e->type() == v:t_string
hoverText->extend(e->split("\n"))
else
hoverText->extend(e.value->split("\n"))
endif
endfor
- elseif type(reply.result.contents) == v:t_string
+ elseif reply.result.contents->type() == v:t_string
if reply.result.contents->empty()
return
endif
setqflist([], ' ', {title: 'Language Server', items: qflist})
var save_winid = win_getid()
copen
- win_gotoid(save_winid)
+ save_winid->win_gotoid()
enddef
# process the 'textDocument/documentHighlight' reply from the LSP server
endif
var fname: string = LspUriToFile(req.params.textDocument.uri)
- var bnr = bufnr(fname)
+ var bnr = fname->bufnr()
for docHL in reply.result
var kind: number = docHL->get('kind', 1)
var i_n = [B[0], numlines - 1]->min()
if i_0 < 0 || i_0 >= numlines || i_n < 0 || i_n >= numlines
- WarnMsg("set_lines: Invalid range, A = " .. string(A)
- .. ", B = " .. string(B) .. ", numlines = " .. numlines
- .. ", new lines = " .. string(new_lines))
+ WarnMsg("set_lines: Invalid range, A = " .. A->string()
+ .. ", B = " .. B->string() .. ", numlines = " .. numlines
+ .. ", new lines = " .. new_lines->string())
return lines
endif
endif
var save_cursor: list<number> = getcurpos()
- for [uri, changes] in items(workspaceEdit.changes)
+ for [uri, changes] in workspaceEdit.changes->items()
var fname: string = LspUriToFile(uri)
- var bnr: number = bufnr(fname)
+ var bnr: number = fname->bufnr()
if bnr == -1
# file is already removed
continue
# result: TextEdit[]
var fname: string = LspUriToFile(req.params.textDocument.uri)
- var bnr: number = bufnr(fname)
+ var bnr: number = fname->bufnr()
if bnr == -1
# file is already removed
return
if lsp_reply_handlers->has_key(req.method)
lsp_reply_handlers[req.method](lspserver, req, reply)
else
- ErrMsg("Error: Unsupported reply received from LSP server: " .. string(reply))
+ ErrMsg("Error: Unsupported reply received from LSP server: " .. reply->string())
endif
enddef
# Param: PublishDiagnosticsParams
def s:processDiagNotif(lspserver: dict<any>, reply: dict<any>): void
var fname: string = LspUriToFile(reply.params.uri)
- var bnr: number = bufnr(fname)
+ var bnr: number = fname->bufnr()
if bnr == -1
# Is this condition possible?
return
# process unsupported notification messages
def s:processUnsupportedNotif(lspserver: dict<any>, reply: dict<any>)
- ErrMsg('Error: Unsupported notification message received from the LSP server (' .. lspserver.path .. '), message = ' .. string(reply))
+ ErrMsg('Error: Unsupported notification message received from the LSP server (' .. lspserver.path .. '), message = ' .. reply->string())
enddef
# process notification messages from the LSP server
if lsp_notif_handlers->has_key(reply.method)
lsp_notif_handlers[reply.method](lspserver, reply)
else
- ErrMsg('Error: Unsupported notification received from LSP server ' .. string(reply))
+ ErrMsg('Error: Unsupported notification received from LSP server ' .. reply->string())
endif
enddef
enddef
def s:processUnsupportedReq(lspserver: dict<any>, request: dict<any>)
- ErrMsg('Error: Unsupported request message received from the LSP server (' .. lspserver.path .. '), message = ' .. string(request))
+ ErrMsg('Error: Unsupported request message received from the LSP server (' .. lspserver.path .. '), message = ' .. request->string())
enddef
# process a request message from the server
lspRequestHandlers[request.method](lspserver, request)
else
ErrMsg('Error: Unsupported request received from LSP server ' ..
- string(request))
+ request->string())
endif
enddef
if msg->has_key('result') || msg->has_key('error')
# response message from the server
- var req = lspserver.requests->get(string(msg.id))
+ var req = lspserver.requests->get(msg.id->string())
# Remove the corresponding stored request message
- lspserver.requests->remove(string(msg.id))
+ lspserver.requests->remove(msg.id->string())
if msg->has_key('result')
lspserver.processReply(req, msg)
var emsg: string = msg.error.message
emsg ..= ', code = ' .. msg.error.code
if msg.error->has_key('data')
- emsg = emsg .. ', data = ' .. string(msg.error.data)
+ emsg = emsg .. ', data = ' .. msg.error.data->string()
endif
ErrMsg("Error: request " .. req.method .. " failed (" .. emsg .. ")")
endif
import {WarnMsg,
ErrMsg,
lsp_server_trace,
+ ClearTraceLogs,
GetLineByteFromPos} from './util.vim'
# Needs Vim 8.2.2342 and higher
enddef
def lsp#enableServerTrace()
+ ClearTraceLogs()
lsp_server_trace = v:true
enddef
# Show information about all the LSP servers
def lsp#showServers()
- for [ftype, lspserver] in items(ftypeServerMap)
+ for [ftype, lspserver] in ftypeServerMap->items()
var msg = ftype .. " "
if lspserver.running
msg ..= 'running'
return
endif
- var fname: string = bufname(bnr)
+ var fname: string = bnr->bufname()
var ftype: string = bnr->getbufvar('&filetype')
if fname == '' || ftype == ''
return
if lspserver.diagsMap->has_key(bnr)
lspserver.diagsMap->remove(bnr)
endif
- remove(bufnrToServer, bnr)
+ bufnrToServer->remove(bnr)
enddef
# Stop all the LSP servers
continue
endif
- if !file_readable(server.path)
+ if !server.path->filereadable()
ErrMsg('Error: LSP server ' .. server.path .. ' is not found')
return
endif
- if type(server.args) != v:t_list
+ if server.args->type() != v:t_list
ErrMsg('Error: Arguments for LSP server ' .. server.path .. ' is not a List')
return
endif
var lspserver: dict<any> = NewLspServer(server.path, server.args)
- if type(server.filetype) == v:t_string
+ if server.filetype->type() == v:t_string
s:lspAddServer(server.filetype, lspserver)
- elseif type(server.filetype) == v:t_list
+ elseif server.filetype->type() == v:t_list
for ftype in server.filetype
s:lspAddServer(ftype, lspserver)
endfor
else
ErrMsg('Error: Unsupported file type information "' ..
- string(server.filetype) .. '" in LSP server registration')
+ server.filetype->string() .. '" in LSP server registration')
continue
endif
endfor
var qflist: list<dict<any>> = []
var text: string
- for [lnum, diag] in items(lspserver.diagsMap[bnr])
+ for [lnum, diag] in lspserver.diagsMap[bnr]->items()
text = diag.message->substitute("\n\\+", "\n", 'g')
qflist->add({'filename': fname,
'lnum': diag.range.start.line + 1,
# sort the diaganostics messages for a buffer by line number
def s:getSortedDiagLines(lspserver: dict<any>, bnr: number): list<number>
+ # create a list of line numbers from the diag map keys
var lnums: list<number> =
- lspserver.diagsMap[bnr]->keys()->mapnew((_, v) => str2nr(v))
+ lspserver.diagsMap[bnr]->keys()->mapnew((_, v) => v->str2nr())
return lnums->sort((a, b) => a - b)
enddef
+# jump to the next/previous/first diagnostic message in the current buffer
def lsp#jumpToDiag(which: string): void
var ftype = &filetype
if ftype == ''
# Find the entry just before the current line (binary search)
var curlnum: number = line('.')
- for lnum in (which == 'next') ? sortedDiags : reverse(sortedDiags)
+ for lnum in (which == 'next') ? sortedDiags : sortedDiags->reverse()
if (which == 'next' && lnum > curlnum)
|| (which == 'prev' && lnum < curlnum)
cursor(lnum, 1)
# Highlight the selected symbol
prop_remove({type: 'LspOutlineHighlight'})
- var col: number = match(getline('.'), '\S') + 1
+ var col: number = getline('.')->match('\S') + 1
prop_add(line('.'), col, {type: 'LspOutlineHighlight',
length: w:lspSymbols.lnumTable[lnum].name->len()})
lnumMap: list<dict<any>>,
children: bool)
var prefix: string = pfx .. ' '
- for [symType, symbols] in items(symbolTypeTable)
+ for [symType, symbols] in symbolTypeTable->items()
if !children
# Add an empty line for the top level symbol types. For types in the
# children symbols, don't add the empty line.
skipOutlineRefresh = true
var prevWinID: number = win_getid()
- win_gotoid(wid)
+ wid->win_gotoid()
# if the file displayed in the outline window is same as the new file, then
# save and restore the cursor position
- var symbols = getwinvar(wid, 'lspSymbols', {})
+ var symbols = wid->getwinvar('lspSymbols', {})
var saveCursor: list<number> = []
if !symbols->empty() && symbols.filename == fname
saveCursor = getcurpos()
:setlocal modifiable
:silent! :%d _
setline(1, ['# LSP Outline View',
- '# ' .. fnamemodify(fname, ':t') .. ' ('
- .. fnamemodify(fname, ':h') .. ')'])
+ '# ' .. fname->fnamemodify(':t') .. ' ('
+ .. fname->fnamemodify(':h') .. ')'])
# First two lines in the buffer display comment information
var lnumMap: list<dict<any>> = [{}, {}]
:setlocal nomodifiable
if !saveCursor->empty()
- setpos('.', saveCursor)
+ saveCursor->setpos('.')
endif
- win_gotoid(prevWinID)
+ prevWinID->win_gotoid()
# Highlight the current symbol
s:outlineHighlightCurrentSymbol()
enddef
def s:outlineHighlightCurrentSymbol()
- var fname: string = fnamemodify(expand('%'), ':p')
+ var fname: string = expand('%')->fnamemodify(':p')
if fname == '' || &filetype == ''
return
endif
# Check whether the symbols for this file are displayed in the outline
# window
- var lspSymbols = getwinvar(wid, 'lspSymbols', {})
+ var lspSymbols = wid->getwinvar('lspSymbols', {})
if lspSymbols->empty() || lspSymbols.filename != fname
return
endif
autocmd CursorHold * call s:outlineHighlightCurrentSymbol()
augroup END
- win_gotoid(prevWinID)
+ prevWinID->win_gotoid()
enddef
def s:requestDocSymbols()
|| key == "\<C-P>"
# scroll the popup window
var cmd: string = 'normal! ' .. (key == "\<C-N>" ? 'j' : key == "\<C-P>" ? 'k' : key)
- cmd->win_execute(popupID)
+ win_execute(popupID, cmd)
key_handled = true
elseif key == "\<Up>" || key == "\<Down>"
# Use native Vim handling for these keys
return
endif
- echomsg 'Workspace Folders: ' .. string(lspserver.workspaceFolders)
+ echomsg 'Workspace Folders: ' .. lspserver.workspaceFolders->string()
enddef
# Add a workspace folder. Default is to use the current folder.
ProcessMessages} from './handlers.vim'
import {WarnMsg,
ErrMsg,
- ClearTraceLogs,
TraceLog,
LspUriToFile,
+ LspBufnrToUri,
LspFileToUri} from './util.vim'
# LSP server standard output handler
err_cb: function('s:error_cb', [lspserver]),
exit_cb: function('s:exit_cb', [lspserver])}
- ClearTraceLogs()
lspserver.data = ''
lspserver.caps = {}
lspserver.nextID = 1
initparams.processId = getpid()
initparams.clientInfo = {
name: 'Vim',
- version: string(v:versionlong),
+ version: v:versionlong->string(),
}
var curdir: string = getcwd()
initparams.rootPath = curdir
req.params = {}
# Save the request, so that the corresponding response can be processed
- lspserver.requests->extend({[string(req.id)]: req})
+ lspserver.requests->extend({[req.id->string()]: req})
return req
enddef
# send a response message to the server
def s:sendResponse(lspserver: dict<any>, request: dict<any>, result: dict<any>, error: dict<any>)
var resp: dict<any> = lspserver.createResponse(request.id)
- if type(result) != v:t_none
+ if result->type() != v:t_none
resp->extend({result: result})
else
resp->extend({error: error})
# interface DidOpenTextDocumentParams
# interface TextDocumentItem
var tdi = {}
- tdi.uri = LspFileToUri(bufname(bnr))
+ tdi.uri = LspBufnrToUri(bnr)
tdi.languageId = ftype
tdi.version = 1
tdi.text = getbufline(bnr, 1, '$')->join("\n") .. "\n"
# interface DidCloseTextDocumentParams
# interface TextDocumentIdentifier
var tdid = {}
- tdid.uri = LspFileToUri(bufname(bnr))
+ tdid.uri = LspBufnrToUri(bnr)
notif.params->extend({textDocument: tdid})
lspserver.sendMessage(notif)
# interface DidChangeTextDocumentParams
# interface VersionedTextDocumentIdentifier
var vtdid: dict<any> = {}
- vtdid.uri = LspFileToUri(bufname(bnr))
+ vtdid.uri = LspBufnrToUri(bnr)
# Use Vim 'changedtick' as the LSP document version number
vtdid.version = bnr->getbufvar('changedtick')
notif.params->extend({textDocument: vtdid})
var notif: dict<any> = lspserver.createNotification('textDocument/didSave')
# interface: DidSaveTextDocumentParams
- notif.params->extend({textDocument: {uri: LspFileToUri(bufname(bnr))}})
+ notif.params->extend({textDocument: {uri: LspBufnrToUri(bnr)}})
lspserver.sendMessage(notif)
enddef
# interface CodeActionParams
var fname: string = fnamemodify(fname_arg, ':p')
- var bnr: number = bufnr(fname_arg)
+ var bnr: number = fname_arg->bufnr()
req.params->extend({textDocument: {uri: LspFileToUri(fname)}})
var r: dict<dict<number>> = {
start: {line: line('.') - 1, character: charcol('.') - 1},
return uri
enddef
+# Convert a Vim buffer number to an LSP URI (file://<absolute_path>)
+export def LspBufnrToUri(bnr: number): string
+ return LspFileToUri(bnr->bufname())
+enddef
+
# Returns the byte number of the specified LSP position in buffer 'bnr'.
# LSP's line and characters are 0-indexed.
# Vim's line and columns are 1-indexed.
==============================================================================
4. Configuration *lsp-configuration*
-To register a LSP server, add the following lines to your .vimrc file:
->
+To register one or more LSP servers, use the lsp#addServer() function with a
+list of LSP server details in the .vimrc file.
+
+For example, to add the LSP servers for the Javascript, Typescript and Python
+file types, add the following commands to the .vimrc file: >
+
let lspServers = [
- \ {
- \ 'filetype': ['c', 'cpp'],
- \ 'path': '/usr/local/bin/clangd',
- \ 'args': ['--background-index']
- \ },
\ {
\ 'filetype': ['javascript', 'typescript'],
\ 'path': '/usr/local/bin/typescript-language-server',
\ 'args': ['--stdio']
\ }
\ {
- \ 'filetype': 'sh',
- \ 'path': '/usr/local/bin/bash-language-server',
- \ 'args': ['start']
- \ },
+ \ 'filetype': 'python',
+ \ 'path': '/usr/local/bin/pyls',
+ \ 'args': ['--check-parent-process', '-v']
+ \ }
\ ]
call lsp#addServer(lspServers)
<
-The above lines add the LSP servers for C, C++, Javascript, Typescript and
-Shell script file types.
+Depending on the location of the typescript and python pyls language servers
+installed in your system, update the 'path' in the above snippet
+appripriately.
+
+Another example, for adding the LSP servers for the C, C++, Shell script and
+Vim file types: >
+ let lspServers = [
+ \ #{
+ \ filetype: ['c', 'cpp'],
+ \ path: '/usr/local/bin/clangd',
+ \ args: ['--background-index']
+ \ },
+ \ #{
+ \ filetype: 'sh',
+ \ path: '/usr/local/bin/bash-language-server',
+ \ args: ['start']
+ \ },
+ \ #{
+ \ filetype: ['vim'],
+ \ path: '/usr/local/bin/vim-language-server',
+ \ args: ['--stdio']
+ \ }
+ \ ]
+ call lsp#addServer(lspServers)
+<
To add a LSP server, the following information is needed:
filetype One or more file types supported by the LSP server.
:%bw!
enddef
+# Test for showing all the references of a symbol in a file using LSP
+def Test_lsp_show_references()
+ :silent! edit Xtest.c
+ var lines: list<string> =<< trim END
+ int count;
+ void redFunc()
+ {
+ int count, i;
+ count = 10;
+ i = count;
+ }
+ void blueFunc()
+ {
+ int count, j;
+ count = 20;
+ j = count;
+ }
+ END
+ setline(1, lines)
+ :redraw!
+ cursor(5, 2)
+ var bnr: number = bufnr()
+ :LspShowReferences
+ :sleep 1
+ var qfl: list<dict<any>> = getqflist()
+ assert_equal('quickfix', getwinvar(winnr('$'), '&buftype'))
+ assert_equal(bnr, qfl[0].bufnr)
+ assert_equal(3, qfl->len())
+ assert_equal([4, 6], [qfl[0].lnum, qfl[0].col])
+ assert_equal([5, 2], [qfl[1].lnum, qfl[1].col])
+ assert_equal([6, 6], [qfl[2].lnum, qfl[2].col])
+ :only
+ cursor(1, 5)
+ :LspShowReferences
+ :sleep 1
+ qfl = getqflist()
+ assert_equal(1, qfl->len())
+ assert_equal([1, 5], [qfl[0].lnum, qfl[0].col])
+
+ :%bw!
+enddef
+
def LspRunTests()
# Edit a dummy C file to start the LSP server
:edit Xtest.c
:sleep 1
:%bw!
+ # Get the list of test functions in this file and call them
var fns: list<string> = execute('function /Test_')
->split("\n")
->map("v:val->substitute('^def <SNR>\\d\\+_', '', '')")
endif
endfor
- echomsg "Success: All LSP tests have passed"
+ echomsg "Success: All the LSP tests have passed"
enddef
LspRunTests()