vim9script
var opt = {}
+var util = {}
if has('patch-8.2.4019')
import './lspoptions.vim' as opt_import
+ import './util.vim' as util_import
+
opt.lspOptions = opt_import.lspOptions
+ util.WarnMsg = util_import.WarnMsg
+ util.GetLineByteFromPos = util_import.GetLineByteFromPos
else
import lspOptions from './lspoptions.vim'
+ import {WarnMsg,
+ LspUriToFile,
+ GetLineByteFromPos} from './util.vim'
+
opt.lspOptions = lspOptions
+ util.WarnMsg = WarnMsg
+ util.LspUriToFile = LspUriToFile
+ util.GetLineByteFromPos = GetLineByteFromPos
endif
+# Remove the diagnostics stored for buffer 'bnr'
+export def DiagRemoveFile(lspserver: dict<any>, bnr: number)
+ if lspserver.diagsMap->has_key(bnr)
+ lspserver.diagsMap->remove(bnr)
+ endif
+enddef
+
def s:lspDiagSevToSignName(severity: number): string
var typeMap: list<string> = ['LspDiagError', 'LspDiagWarning',
'LspDiagInfo', 'LspDiagHint']
# New LSP diagnostic messages received from the server for a file.
# Update the signs placed in the buffer for this file
-export def LspDiagsUpdated(lspserver: dict<any>, bnr: number)
+export def UpdateDiags(lspserver: dict<any>, bnr: number)
if !opt.lspOptions.autoHighlightDiags
return
endif
signs->sign_placelist()
enddef
+# process a diagnostic notification message from the LSP server
+# Notification: textDocument/publishDiagnostics
+# Param: PublishDiagnosticsParams
+export def DiagNotification(lspserver: dict<any>, uri: string, diags: list<dict<any>>): void
+ var fname: string = util.LspUriToFile(uri)
+ var bnr: number = fname->bufnr()
+ if bnr == -1
+ # Is this condition possible?
+ return
+ endif
+
+ # TODO: Is the buffer (bnr) always a loaded buffer? Should we load it here?
+ var lastlnum: number = bnr->getbufinfo()[0].linecount
+ var lnum: number
+
+ # store the diagnostic for each line separately
+ var diag_by_lnum: dict<dict<any>> = {}
+ for d in diags
+ lnum = d.range.start.line + 1
+ if lnum > lastlnum
+ # Make sure the line number is a valid buffer line number
+ lnum = lastlnum
+ endif
+ diag_by_lnum[lnum] = d
+ endfor
+
+ lspserver.diagsMap->extend({['' .. bnr]: diag_by_lnum})
+ UpdateDiags(lspserver, bnr)
+enddef
+
+# get the count of error in the current buffer
+export def DiagsGetErrorCount(lspserver: dict<any>): dict<number>
+ var res = {'Error': 0, 'Warn': 0, 'Info': 0, 'Hint': 0}
+
+ var bnr: number = bufnr()
+ if lspserver.diagsMap->has_key(bnr)
+ for item in lspserver.diagsMap[bnr]->values()
+ if item->has_key('severity')
+ if item.severity == 1
+ res.Error = res.Error + 1
+ elseif item.severity == 2
+ res.Warn = res.Warn + 1
+ elseif item.severity == 3
+ res.Info = res.Info + 1
+ elseif item.severity == 4
+ res.Hint = res.Hint + 1
+ endif
+ endif
+ endfor
+ endif
+
+ return res
+enddef
+
+# Map the LSP DiagnosticSeverity to a quickfix type character
+def s:lspDiagSevToQfType(severity: number): string
+ var typeMap: list<string> = ['E', 'W', 'I', 'N']
+
+ if severity > 4
+ return ''
+ endif
+
+ return typeMap[severity - 1]
+enddef
+
+# Display the diagnostic messages from the LSP server for the current buffer
+# in a location list
+export def ShowAllDiags(lspserver: dict<any>): void
+ var fname: string = expand('%:p')
+ if fname == ''
+ return
+ endif
+ var bnr: number = bufnr()
+
+ if !lspserver.diagsMap->has_key(bnr) || lspserver.diagsMap[bnr]->empty()
+ util.WarnMsg('No diagnostic messages found for ' .. fname)
+ return
+ endif
+
+ var qflist: list<dict<any>> = []
+ var text: string
+
+ 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,
+ 'col': util.GetLineByteFromPos(bnr, diag.range.start) + 1,
+ 'text': text,
+ 'type': s:lspDiagSevToQfType(diag.severity)})
+ endfor
+ setloclist(0, [], ' ', {'title': 'Language Server Diagnostics',
+ 'items': qflist})
+ :lopen
+enddef
+
+# Show the diagnostic message for the current line
+export def ShowCurrentDiag(lspserver: dict<any>)
+ var bnr: number = bufnr()
+ var lnum: number = line('.')
+ var diag: dict<any> = lspserver.getDiagByLine(bnr, lnum)
+ if diag->empty()
+ util.WarnMsg('No diagnostic messages found for current line')
+ else
+ echo diag.message
+ endif
+enddef
+
+# Get the diagnostic from the LSP server for a particular line in a file
+export def GetDiagByLine(lspserver: dict<any>, bnr: number, lnum: number): dict<any>
+ if lspserver.diagsMap->has_key(bnr) &&
+ lspserver.diagsMap[bnr]->has_key(lnum)
+ return lspserver.diagsMap[bnr][lnum]
+ endif
+ return {}
+enddef
+
+# sort the diaganostics messages for a buffer by line number
+def s:getSortedDiagLines(lspsrv: dict<any>, bnr: number): list<number>
+ # create a list of line numbers from the diag map keys
+ var lnums: list<number> =
+ lspsrv.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
+export def LspDiagsJump(lspserver: dict<any>, which: string): void
+ var fname: string = expand('%:p')
+ if fname == ''
+ return
+ endif
+ var bnr: number = bufnr()
+
+ if !lspserver.diagsMap->has_key(bnr) || lspserver.diagsMap[bnr]->empty()
+ util.WarnMsg('No diagnostic messages found for ' .. fname)
+ return
+ endif
+
+ # sort the diagnostics by line number
+ var sortedDiags: list<number> = s:getSortedDiagLines(lspserver, bnr)
+
+ if which == 'first'
+ cursor(sortedDiags[0], 1)
+ return
+ endif
+
+ # Find the entry just before the current line (binary search)
+ var curlnum: number = line('.')
+ for lnum in (which == 'next') ? sortedDiags : sortedDiags->reverse()
+ if (which == 'next' && lnum > curlnum)
+ || (which == 'prev' && lnum < curlnum)
+ cursor(lnum, 1)
+ return
+ endif
+ endfor
+
+ util.WarnMsg('Error: No more diagnostics found')
+enddef
+
# vim: shiftwidth=2 softtabstop=2
util.TraceLog = util_import.TraceLog
util.LspUriToFile = util_import.LspUriToFile
util.GetLineByteFromPos = util_import.GetLineByteFromPos
- diag.LspDiagsUpdated = diag_import.LspDiagsUpdated
+ diag.DiagNotification = diag_import.DiagNotification
else
import lspOptions from './lspoptions.vim'
import {WarnMsg,
TraceLog,
LspUriToFile,
GetLineByteFromPos} from './util.vim'
- import {LspDiagsUpdated} from './diag.vim'
+ import {DiagNotification} from './diag.vim'
opt.lspOptions = lspOptions
util.WarnMsg = WarnMsg
util.TraceLog = TraceLog
util.LspUriToFile = LspUriToFile
util.GetLineByteFromPos = GetLineByteFromPos
- diag.LspDiagsUpdated = LspDiagsUpdated
+ diag.DiagNotification = DiagNotification
endif
# process the 'initialize' method reply from the LSP server
var i_n = [B[0], numlines - 1]->min()
if i_0 < 0 || i_0 >= numlines || i_n < 0 || i_n >= numlines
- util.WarnMsg("set_lines: Invalid range, A = " .. A->string()
- .. ", B = " .. B->string() .. ", numlines = " .. numlines
- .. ", new lines = " .. new_lines->string())
+ #util.WarnMsg("set_lines: Invalid range, A = " .. A->string()
+ # .. ", B = " .. B->string() .. ", numlines = " .. numlines
+ # .. ", new lines = " .. new_lines->string())
+ var msg = "set_lines: Invalid range, A = " .. A->string()
+ msg ..= ", B = " .. B->string() .. ", numlines = " .. numlines
+ msg ..= ", new lines = " .. new_lines->string()
+ util.WarnMsg(msg)
return lines
endif
# Notification: textDocument/publishDiagnostics
# Param: PublishDiagnosticsParams
def s:processDiagNotif(lspserver: dict<any>, reply: dict<any>): void
- var fname: string = util.LspUriToFile(reply.params.uri)
- var bnr: number = fname->bufnr()
- if bnr == -1
- # Is this condition possible?
- return
- endif
-
- # TODO: Is the buffer (bnr) always a loaded buffer? Should we load it here?
- var lastlnum: number = bnr->getbufinfo()[0].linecount
- var lnum: number
-
- # store the diagnostic for each line separately
- var diag_by_lnum: dict<dict<any>> = {}
- for diag in reply.params.diagnostics
- lnum = diag.range.start.line + 1
- if lnum > lastlnum
- # Make sure the line number is a valid buffer line number
- lnum = lastlnum
- endif
- diag_by_lnum[lnum] = diag
- endfor
-
- lspserver.diagsMap->extend({['' .. bnr]: diag_by_lnum})
- diag.LspDiagsUpdated(lspserver, bnr)
+ diag.DiagNotification(lspserver, reply.params.uri, reply.params.diagnostics)
enddef
# process a show notification message from the LSP server
util.ClearTraceLogs = util_import.ClearTraceLogs
util.GetLineByteFromPos = util_import.GetLineByteFromPos
util.PushCursorToTagStack = util_import.PushCursorToTagStack
- diag.LspDiagsUpdated = diag_import.LspDiagsUpdated
+ diag.UpdateDiags = diag_import.UpdateDiags
+ diag.DiagsGetErrorCount = diag_import.DiagsGetErrorCount
+ diag.ShowAllDiags = diag_import.ShowAllDiags
+ diag.ShowCurrentDiag = diag_import.ShowCurrentDiag
+ diag.LspDiagsJump = diag_import.LspDiagsJump
+ diag.DiagRemoveFile = diag_import.DiagRemoveFile
else
import {lspOptions, LspOptionsSet} from './lspoptions.vim'
import NewLspServer from './lspserver.vim'
ClearTraceLogs,
GetLineByteFromPos,
PushCursorToTagStack} from './util.vim'
- import {LspDiagsUpdated} from './diag.vim'
+ import {DiagRemoveFile,
+ UpdateDiags,
+ DiagsGetErrorCount,
+ ShowAllDiags,
+ ShowCurrentDiag,
+ LspDiagsJump} from './diag.vim'
opt.LspOptionsSet = LspOptionsSet
opt.lspOptions = lspOptions
util.ClearTraceLogs = ClearTraceLogs
util.GetLineByteFromPos = GetLineByteFromPos
util.PushCursorToTagStack = PushCursorToTagStack
- diag.LspDiagsUpdated = LspDiagsUpdated
+ diag.DiagRemoveFile = DiagRemoveFile
+ diag.UpdateDiags = UpdateDiags
+ diag.DiagsGetErrorCount = DiagsGetErrorCount
+ diag.ShowAllDiags = ShowAllDiags
+ diag.ShowCurrentDiag = ShowCurrentDiag
+ diag.LspDiagsJump = LspDiagsJump
endif
# Needs Vim 8.2.2342 and higher
if lspserver->empty() || !lspserver.running
return
endif
- diag.LspDiagsUpdated(lspserver, bufnr())
+ diag.UpdateDiags(lspserver, bufnr())
enddef
# A new buffer is opened. If LSP is supported for this buffer, then add it
return
endif
lspserver.textdocDidClose(bnr)
- if lspserver.diagsMap->has_key(bnr)
- lspserver.diagsMap->remove(bnr)
- endif
+ diag.DiagRemoveFile(lspserver, bnr)
bufnrToServer->remove(bnr)
enddef
lspserver.setTrace(traceVal)
enddef
-# Map the LSP DiagnosticSeverity to a quickfix type character
-def s:lspDiagSevToQfType(severity: number): string
- var typeMap: list<string> = ['E', 'W', 'I', 'N']
-
- if severity > 4
- return ''
- endif
-
- return typeMap[severity - 1]
-enddef
-
# Display the diagnostic messages from the LSP server for the current buffer
# in a quickfix list
def lsp#showDiagnostics(): void
var ftype = &filetype
- if ftype == ''
+ if ftype == '' || @% == ''
return
endif
return
endif
- var fname: string = expand('%:p')
- if fname == ''
- return
- endif
- var bnr: number = bufnr()
-
- if !lspserver.diagsMap->has_key(bnr) || lspserver.diagsMap[bnr]->empty()
- util.WarnMsg('No diagnostic messages found for ' .. fname)
- return
- endif
-
- var qflist: list<dict<any>> = []
- var text: string
-
- 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,
- 'col': util.GetLineByteFromPos(bnr, diag.range.start) + 1,
- 'text': text,
- 'type': s:lspDiagSevToQfType(diag.severity)})
- endfor
- setloclist(0, [], ' ', {'title': 'Language Server Diagnostics',
- 'items': qflist})
- :lopen
+ diag.ShowAllDiags(lspserver)
enddef
# Show the diagnostic message for the current line
def lsp#showCurrentDiag()
var ftype = &filetype
- if ftype == ''
+ if ftype == '' || @% == ''
return
endif
return
endif
- var bnr: number = bufnr()
- var lnum: number = line('.')
- var diag: dict<any> = lspserver.getDiagByLine(bnr, lnum)
- if diag->empty()
- util.WarnMsg('No diagnostic messages found for current line')
- else
- echo diag.message
- endif
+ diag.ShowCurrentDiag(lspserver)
enddef
# get the count of error in the current buffer
return res
endif
- var bnr: number = bufnr()
- if lspserver.diagsMap->has_key(bnr)
- for item in lspserver.diagsMap[bnr]->values()
- if item->has_key('severity')
- if item.severity == 1
- res.Error = res.Error + 1
- elseif item.severity == 2
- res.Warn = res.Warn + 1
- elseif item.severity == 3
- res.Info = res.Info + 1
- elseif item.severity == 4
- res.Hint = res.Hint + 1
- endif
- endif
- endfor
- endif
-
- return res
-enddef
-
-# sort the diaganostics messages for a buffer by line number
-def s:getSortedDiagLines(lspsrv: dict<any>, bnr: number): list<number>
- # create a list of line numbers from the diag map keys
- var lnums: list<number> =
- lspsrv.diagsMap[bnr]->keys()->mapnew((_, v) => v->str2nr())
- return lnums->sort((a, b) => a - b)
+ return diag.DiagsGetErrorCount(lspserver)
enddef
# jump to the next/previous/first diagnostic message in the current buffer
def lsp#jumpToDiag(which: string): void
var ftype = &filetype
- if ftype == ''
+ if ftype == '' || @% == ''
return
endif
return
endif
- var fname: string = expand('%:p')
- if fname == ''
- return
- endif
- var bnr: number = bufnr()
-
- if !lspserver.diagsMap->has_key(bnr) || lspserver.diagsMap[bnr]->empty()
- util.WarnMsg('No diagnostic messages found for ' .. fname)
- return
- endif
-
- # sort the diagnostics by line number
- var sortedDiags: list<number> = s:getSortedDiagLines(lspserver, bnr)
-
- if which == 'first'
- cursor(sortedDiags[0], 1)
- return
- endif
-
- # Find the entry just before the current line (binary search)
- var curlnum: number = line('.')
- for lnum in (which == 'next') ? sortedDiags : sortedDiags->reverse()
- if (which == 'next' && lnum > curlnum)
- || (which == 'prev' && lnum < curlnum)
- cursor(lnum, 1)
- return
- endif
- endfor
-
- util.WarnMsg('Error: No more diagnostics found')
+ diag.LspDiagsJump(lspserver, which)
enddef
# Insert mode completion handler. Used when 24x7 completion is enabled
# for the Language Server Protocol (LSP) specificaiton.
var handlers = {}
+var diag = {}
var util = {}
if has('patch-8.2.4019')
import './handlers.vim' as handlers_import
import './util.vim' as util_import
+ import './diag.vim' as diag_import
handlers.ProcessReply = handlers_import.ProcessReply
handlers.ProcessNotif = handlers_import.ProcessNotif
handlers.ProcessRequest = handlers_import.ProcessRequest
util.WarnMsg = util_import.WarnMsg
util.ErrMsg = util_import.ErrMsg
util.TraceLog = util_import.TraceLog
- util.LspUriToFile = util_import.LspUriToFile
util.LspBufnrToUri = util_import.LspBufnrToUri
util.LspFileToUri = util_import.LspFileToUri
util.PushCursorToTagStack = util_import.PushCursorToTagStack
+ diag.GetDiagByLine = diag_import.GetDiagByLine
else
import {ProcessReply,
ProcessNotif,
ProcessRequest,
ProcessMessages} from './handlers.vim'
+ import {GetDiagByLine} from './diag.vim'
import {WarnMsg,
ErrMsg,
TraceLog,
- LspUriToFile,
LspBufnrToUri,
LspFileToUri,
PushCursorToTagStack} from './util.vim'
util.WarnMsg = WarnMsg
util.ErrMsg = ErrMsg
util.TraceLog = TraceLog
- util.LspUriToFile = LspUriToFile
util.LspBufnrToUri = LspBufnrToUri
util.LspFileToUri = LspFileToUri
util.PushCursorToTagStack = PushCursorToTagStack
+ diag.GetDiagByLine = GetDiagByLine
endif
# LSP server standard output handler
tabSize: tabsz,
insertSpaces: &expandtab ? true : false,
}
- req.params->extend({textDocument: {uri: util.LspFileToUri(fname)},
- options: fmtopts})
+ #req.params->extend({textDocument: {uri: util.LspFileToUri(fname)},
+ # options: fmtopts})
+ req.params->extend({textDocument: {uri: util.LspFileToUri(fname)}, options: fmtopts})
if rangeFormat
var r: dict<dict<number>> = {
start: {line: start_lnum - 1, character: 0},
lspserver.sendMessage(req)
enddef
-# Get the diagnostic from the LSP server for a particular line in a file
-def s:getDiagByLine(lspserver: dict<any>, bnr: number, lnum: number): dict<any>
- if lspserver.diagsMap->has_key(bnr) &&
- lspserver.diagsMap[bnr]->has_key(lnum)
- return lspserver.diagsMap[bnr][lnum]
- endif
- return {}
-enddef
-
# Request: "textDocument/codeAction"
# Param: CodeActionParams
def s:codeAction(lspserver: dict<any>, fname_arg: string)
start: {line: line('.') - 1, character: charcol('.') - 1},
end: {line: line('.') - 1, character: charcol('.') - 1}}
req.params->extend({textDocument: {uri: util.LspFileToUri(fname)}, range: r})
- var diag: list<dict<any>> = []
+ var d: list<dict<any>> = []
var lnum = line('.')
- var diagInfo: dict<any> = lspserver.getDiagByLine(bnr, lnum)
+ var diagInfo: dict<any> = diag.GetDiagByLine(lspserver, bnr, lnum)
if !diagInfo->empty()
- diag->add(diagInfo)
+ d->add(diagInfo)
endif
- req.params->extend({context: {diagnostics: diag}})
+ req.params->extend({context: {diagnostics: d}})
lspserver.sendMessage(req)
enddef
processNotif: function(handlers.ProcessNotif, [lspserver]),
processRequest: function(handlers.ProcessRequest, [lspserver]),
processMessages: function(handlers.ProcessMessages, [lspserver]),
- getDiagByLine: function('s:getDiagByLine', [lspserver]),
+ getDiagByLine: function(diag.GetDiagByLine, [lspserver]),
textdocDidOpen: function('s:textdocDidOpen', [lspserver]),
textdocDidClose: function('s:textdocDidClose', [lspserver]),
textdocDidChange: function('s:textdocDidChange', [lspserver]),
return uri_decoded
enddef
-# Convert a Vim filenmae to an LSP URI (file://<absolute_path>)
+# Convert a Vim filename to an LSP URI (file://<absolute_path>)
export def LspFileToUri(fname: string): string
var uri: string = fnamemodify(fname, ':p')
:%bw!
enddef
+def Test_lsp_diags()
+ :silent! edit Xtest.c
+ var lines: list<string> =<< trim END
+ void blueFunc()
+ {
+ int count, j:
+ count = 20;
+ j <= count;
+ j = 10;
+ MyFunc();
+ }
+ END
+ setline(1, lines)
+ :sleep 1
+ var bnr: number = bufnr()
+ :redraw!
+ :LspDiagShow
+ var qfl: list<dict<any>> = getloclist(0)
+ assert_equal('quickfix', getwinvar(winnr('$'), '&buftype'))
+ assert_equal(bnr, qfl[0].bufnr)
+ assert_equal(3, qfl->len())
+ assert_equal([3, 14, 'E'], [qfl[0].lnum, qfl[0].col, qfl[0].type])
+ assert_equal([5, 2, 'W'], [qfl[1].lnum, qfl[1].col, qfl[1].type])
+ assert_equal([7, 2, 'W'], [qfl[2].lnum, qfl[2].col, qfl[2].type])
+ close
+ normal gg
+ var output = execute('LspDiagCurrent')->split("\n")
+ assert_equal('No diagnostic messages found for current line', output[0])
+ :LspDiagFirst
+ assert_equal([3, 1], [line('.'), col('.')])
+ output = execute('LspDiagCurrent')->split("\n")
+ assert_equal("Expected ';' at end of declaration (fix available)", output[0])
+ :LspDiagNext
+ assert_equal([5, 1], [line('.'), col('.')])
+ :LspDiagNext
+ assert_equal([7, 1], [line('.'), col('.')])
+ output = execute('LspDiagNext')->split("\n")
+ assert_equal('Error: No more diagnostics found', output[0])
+ :LspDiagPrev
+ :LspDiagPrev
+ :LspDiagPrev
+ output = execute('LspDiagPrev')->split("\n")
+ assert_equal('Error: No more diagnostics found', output[0])
+ :%d
+ setline(1, ['void blueFunc()', '{', '}'])
+ sleep 1
+ output = execute('LspDiagShow')->split("\n")
+ assert_match('No diagnostic messages found for', output[0])
+
+ :%bw!
+enddef
+
def LspRunTests()
# Edit a dummy C file to start the LSP server
:edit Xtest.c
->map("v:val->substitute('^def <SNR>\\d\\+_', '', '')")
for f in fns
v:errors = []
+ v:errmsg = ''
try
exe f
catch
- echomsg "Error: Test " .. f .. " failed with exception " .. v:exception
+ call add(v:errors, "Error: Test " .. f .. " failed with exception " .. v:exception)
endtry
+ if v:errmsg != ''
+ call add(v:errors, "Error: Test " .. f .. " generated error " .. v:errmsg)
+ endif
if v:errors->len() != 0
new Lsp-Test-Results
setline(1, ["Error: Test " .. f .. " failed"]->extend(v:errors))