]> Sergey Matveev's repositories - vim-lsp.git/commitdiff
Add signs for each of the LSP diagnostic line
authorYegappan Lakshmanan <yegappan@yahoo.com>
Wed, 6 Jan 2021 15:07:24 +0000 (07:07 -0800)
committerYegappan Lakshmanan <yegappan@yahoo.com>
Wed, 6 Jan 2021 15:07:24 +0000 (07:07 -0800)
autoload/buf.vim [new file with mode: 0644]
autoload/handlers.vim
autoload/lsp.vim
autoload/lspserver.vim

diff --git a/autoload/buf.vim b/autoload/buf.vim
new file mode 100644 (file)
index 0000000..5410310
--- /dev/null
@@ -0,0 +1,36 @@
+vim9script
+
+def s:lspDiagSevToSignName(severity: number): string
+  var typeMap: list<string> = ['LspDiagError', 'LspDiagWarning',
+                                               'LspDiagInfo', 'LspDiagHint']
+  if severity > 4
+    return 'LspDiagHint'
+  endif
+  return typeMap[severity - 1]
+enddef
+
+# 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)
+  if bnr == -1 || !lspserver.diagsMap->has_key(bnr)
+    return
+  endif
+
+  # Remove all the existing diagnostic signs
+  sign_unplace('LSPDiag')
+
+  if lspserver.diagsMap[bnr]->empty()
+    return
+  endif
+
+  var signs: list<dict<any>> = []
+  for [lnum, diag] in items(lspserver.diagsMap[bnr])
+    signs->add({id: 0, buffer: str2nr(bnr), group: 'LSPDiag',
+                               lnum: str2nr(lnum),
+                               name: s:lspDiagSevToSignName(diag.severity)})
+  endfor
+
+  signs->sign_placelist()
+enddef
+
+# vim: shiftwidth=2 softtabstop=2
index 4cfe3ab83e7eed678f36a2fb32ceb85f3763db43..d46295970d98fdd9b153f8923f31da12f1ec7a1f 100644 (file)
@@ -5,6 +5,7 @@ vim9script
 # for the Language Server Protocol (LSP) specificaiton.
 
 import {WarnMsg, ErrMsg, TraceLog, LspUriToFile} from './util.vim'
+import {LspDiagsUpdated} from './buf.vim'
 
 # process the 'initialize' method reply from the LSP server
 # Result: InitializeResult
@@ -760,6 +761,7 @@ def s:processFoldingRangeReply(lspserver: dict<any>, req: dict<any>, reply: dict
 enddef
 
 # process the 'workspace/executeCommand' reply from the LSP server
+# Result: any | null
 def s:processWorkspaceExecuteReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>)
   if reply.result->empty()
     return
@@ -864,20 +866,37 @@ export def ProcessReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>):
 enddef
 
 # process a diagnostic notification message from the LSP server
+# Notification: textDocument/publishDiagnostics
 # Param: PublishDiagnosticsParams
 def s:processDiagNotif(lspserver: dict<any>, reply: dict<any>): void
   var fname: string = LspUriToFile(reply.params.uri)
+  var bnr: number = bufnr(fname)
+  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
-    diag_by_lnum[diag.range.start.line + 1] = diag
+    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({[fname]: diag_by_lnum})
+  lspserver.diagsMap->extend({['' .. bnr]: diag_by_lnum})
+  LspDiagsUpdated(lspserver, bnr)
 enddef
 
 # process a show notification message from the LSP server
+# Notification: window/showMessage
 # Param: ShowMessageParams
 def s:processShowMsgNotif(lspserver: dict<any>, reply: dict<any>)
   var msgType: list<string> = ['', 'Error: ', 'Warning: ', 'Info: ', 'Log: ']
@@ -897,6 +916,7 @@ def s:processShowMsgNotif(lspserver: dict<any>, reply: dict<any>)
 enddef
 
 # process a log notification message from the LSP server
+# Notification: window/logMessage
 # Param: LogMessageParams
 def s:processLogMsgNotif(lspserver: dict<any>, reply: dict<any>)
   var msgType: list<string> = ['', 'Error: ', 'Warning: ', 'Info: ', 'Log: ']
@@ -908,13 +928,20 @@ def s:processLogMsgNotif(lspserver: dict<any>, reply: dict<any>)
   TraceLog(false, '[' .. mtype .. ']: ' .. reply.params.message)
 enddef
 
+# 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))
+enddef
+
 # process notification messages from the LSP server
 export def ProcessNotif(lspserver: dict<any>, reply: dict<any>): void
   var lsp_notif_handlers: dict<func> =
     {
-      'textDocument/publishDiagnostics': function('s:processDiagNotif'),
       'window/showMessage': function('s:processShowMsgNotif'),
-      'window/logMessage': function('s:processLogMsgNotif')
+      'window/logMessage': function('s:processLogMsgNotif'),
+      'textDocument/publishDiagnostics': function('s:processDiagNotif'),
+      '$/progress': function('s:processUnsupportedNotif'),
+      'telemetry/event': function('s:processUnsupportedNotif')
     }
 
   if lsp_notif_handlers->has_key(reply.method)
@@ -925,6 +952,7 @@ export def ProcessNotif(lspserver: dict<any>, reply: dict<any>): void
 enddef
 
 # process the workspace/applyEdit LSP server request
+# Request: "workspace/applyEdit"
 # Param: ApplyWorkspaceEditParams
 def s:processApplyEditReq(lspserver: dict<any>, request: dict<any>)
   # interface ApplyWorkspaceEditParams
@@ -940,11 +968,22 @@ def s:processApplyEditReq(lspserver: dict<any>, request: dict<any>)
   lspserver.sendResponse(request, {applied: v:true}, v:null)
 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))
+enddef
+
 # process a request message from the server
 export def ProcessRequest(lspserver: dict<any>, request: dict<any>)
   var lspRequestHandlers: dict<func> =
     {
-      'workspace/applyEdit': function('s:processApplyEditReq')
+      'workspace/applyEdit': function('s:processApplyEditReq'),
+      'window/workDoneProgress/create': function('s:processUnsupportedReq'),
+      'client/registerCapability': function('s:processUnsupportedReq'),
+      'client/unregisterCapability': function('s:processUnsupportedReq'),
+      'workspace/workspaceFolders': function('s:processUnsupportedReq'),
+      'workspace/configuration': function('s:processUnsupportedReq'),
+      'workspace/codeLens/refresh': function('s:processUnsupportedReq'),
+      'workspace/semanticTokens/refresh': function('s:processUnsupportedReq')
     }
 
   if lspRequestHandlers->has_key(request.method)
@@ -998,7 +1037,7 @@ export def ProcessMessages(lspserver: dict<any>): void
       else
        # request failed
        var emsg: string = msg.error.message
-       emsg ..= ', code = ' .. msg.code
+       emsg ..= ', code = ' .. msg.error.code
        if msg.error->has_key('data')
          emsg = emsg .. ', data = ' .. string(msg.error.data)
        endif
index f983c2f402bc7b8e3d78fa1ab8f7fd28ad61cb01..b33d5a5bfc703a05d441f9a108c961d9239e874d 100644 (file)
@@ -19,12 +19,25 @@ var ftypeServerMap: dict<dict<any>> = {}
 # Buffer number to LSP server map
 var bufnrToServer: dict<dict<any>> = {}
 
-# List of diagnostics for each opened file
-#var diagsMap: dict<dict<any>> = {}
-
-prop_type_add('LspTextRef', {'highlight': 'Search'})
-prop_type_add('LspReadRef', {'highlight': 'DiffChange'})
-prop_type_add('LspWriteRef', {'highlight': 'DiffDelete'})
+var lspInitializedOnce = false
+
+def s:lspInitOnce()
+  # Signs used for LSP diagnostics
+  sign_define([{name: 'LspDiagError', text: 'E ', texthl: 'ErrorMsg',
+                                               linehl: 'MatchParen'},
+               {name: 'LspDiagWarning', text: 'W ', texthl: 'Search',
+                                               linehl: 'MatchParen'},
+               {name: 'LspDiagInfo', text: 'I ', texthl: 'Pmenu',
+                                               linehl: 'MatchParen'},
+               {name: 'LspDiagHint', text: 'H ', texthl: 'Question',
+                                               linehl: 'MatchParen'}])
+
+  prop_type_add('LspTextRef', {'highlight': 'Search'})
+  prop_type_add('LspReadRef', {'highlight': 'DiffChange'})
+  prop_type_add('LspWriteRef', {'highlight': 'DiffDelete'})
+  set ballooneval balloonevalterm
+  lspInitializedOnce = true
+enddef
 
 # Return the LSP server for the a specific filetype. Returns a null dict if
 # the server is not found.
@@ -187,6 +200,25 @@ def s:lspSavedFile()
   lspserver.didSaveFile(bnr)
 enddef
 
+# Return the diagnostic text from the LSP server for the current mouse line to
+# display in a balloon
+var lspDiagPopupID: number = 0
+var lspDiagPopupInfo: dict<any> = {}
+def g:LspDiagExpr(): string
+  var lspserver: dict<any> = bufnrToServer->get(v:beval_bufnr, {})
+  if lspserver->empty() || !lspserver.running
+    return ''
+  endif
+
+  var diagInfo: dict<any> = lspserver.getDiagByLine(v:beval_bufnr, v:beval_lnum)
+  if diagInfo->empty()
+    # No diagnostic for the current cursor location
+    return ''
+  endif
+
+  return diagInfo.message
+enddef
+
 # A new buffer is opened. If LSP is supported for this buffer, then add it
 def lsp#addFile(bnr: number): void
   if bufnrToServer->has_key(bnr)
@@ -203,6 +235,9 @@ def lsp#addFile(bnr: number): void
     return
   endif
   if !lspserver.running
+    if !lspInitializedOnce
+      s:lspInitOnce()
+    endif
     lspserver.startServer()
   endif
   lspserver.textdocDidOpen(bnr, ftype)
@@ -217,6 +252,7 @@ def lsp#addFile(bnr: number): void
   setbufvar(bnr, '&completefunc', 'lsp#completeFunc')
   setbufvar(bnr, '&completeopt', 'menuone,popup,noinsert,noselect')
   setbufvar(bnr, '&completepopup', 'border:off')
+  setbufvar(bnr, '&balloonexpr', 'LspDiagExpr()')
 
   # map characters that trigger signature help
   if lspserver.caps->has_key('signatureHelpProvider')
@@ -255,8 +291,8 @@ def lsp#removeFile(bnr: number): void
     return
   endif
   lspserver.textdocDidClose(bnr)
-  if lspserver.diagsMap->has_key(fname)
-    lspserver.diagsMap->remove(fname)
+  if lspserver.diagsMap->has_key(bnr)
+    lspserver.diagsMap->remove(bnr)
   endif
   remove(bufnrToServer, bnr)
 enddef
@@ -329,8 +365,8 @@ def lsp#setTraceServer(traceVal: string)
   lspserver.setTrace(traceVal)
 enddef
 
-# Map the LSP DiagnosticSeverity to a type character
-def LspDiagSevToType(severity: number): string
+# 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
@@ -361,8 +397,9 @@ def lsp#showDiagnostics(): void
   if fname == ''
     return
   endif
+  var bnr: number = bufnr()
 
-  if !lspserver.diagsMap->has_key(fname) || lspserver.diagsMap[fname]->empty()
+  if !lspserver.diagsMap->has_key(bnr) || lspserver.diagsMap[bnr]->empty()
     WarnMsg('No diagnostic messages found for ' .. fname)
     return
   endif
@@ -370,13 +407,13 @@ def lsp#showDiagnostics(): void
   var qflist: list<dict<any>> = []
   var text: string
 
-  for [lnum, diag] in items(lspserver.diagsMap[fname])
+  for [lnum, diag] in items(lspserver.diagsMap[bnr])
     text = diag.message->substitute("\n\\+", "\n", 'g')
     qflist->add({'filename': fname,
                    'lnum': diag.range.start.line + 1,
                    'col': diag.range.start.character + 1,
                    'text': text,
-                   'type': LspDiagSevToType(diag.severity)})
+                   'type': s:lspDiagSevToQfType(diag.severity)})
   endfor
   setqflist([], ' ', {'title': 'Language Server Diagnostics', 'items': qflist})
   :copen
@@ -584,7 +621,7 @@ def s:addSymbolText(symbolTypeTable: dict<list<dict<any>>>,
       lnumMap->add({name: s.name, lnum: s.range.start.line + 1,
       col: s.range.start.character + 1})
       s.outlineLine = lnumMap->len()
-      if !s.children->empty()
+      if s->has_key('children') && !s.children->empty()
        s:addSymbolText(s.children, prefix, text, lnumMap, true)
       endif
     endfor
@@ -976,7 +1013,6 @@ def s:jumpToWorkspaceSymbol(popupID: number, result: number): void
   endif
 
   var symTbl: list<dict<any>> = popupID->getwinvar('LspSymbolTable')
-  echomsg symTbl
   try
     # if the selected file is already present in a window, then jump to it
     var fname: string = symTbl[result - 1].file
index 6e29885298d51714bd663adb4f7d7b7b34e94c81..90a0009f820825e2745611d602c079b376b198fd 100644 (file)
@@ -626,6 +626,15 @@ def s:renameSymbol(lspserver: dict<any>, newName: string)
   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)
@@ -640,6 +649,7 @@ def s:codeAction(lspserver: dict<any>, fname_arg: string)
 
   # interface CodeActionParams
   var fname: string = fnamemodify(fname_arg, ':p')
+  var bnr: number = bufnr(fname_arg)
   req.params->extend({textDocument: {uri: LspFileToUri(fname)}})
   var r: dict<dict<number>> = {
                  start: {line: line('.') - 1, character: col('.') - 1},
@@ -647,9 +657,9 @@ def s:codeAction(lspserver: dict<any>, fname_arg: string)
   req.params->extend({range: r})
   var diag: list<dict<any>> = []
   var lnum = line('.')
-  if lspserver.diagsMap->has_key(fname) &&
-                               lspserver.diagsMap[fname]->has_key(lnum)
-    diag->add(lspserver.diagsMap[fname][lnum])
+  var diagInfo: dict<any> = lspserver.getDiagByLine(bnr, lnum)
+  if !diagInfo->empty()
+    diag->add(diagInfo)
   endif
   req.params->extend({context: {diagnostics: diag}})
 
@@ -797,6 +807,7 @@ export def NewLspServer(path: string, args: list<string>): dict<any>
     processNotif: function('ProcessNotif', [lspserver]),
     processRequest: function('ProcessRequest', [lspserver]),
     processMessages: function('ProcessMessages', [lspserver]),
+    getDiagByLine: function('s:getDiagByLine', [lspserver]),
     textdocDidOpen: function('s:textdocDidOpen', [lspserver]),
     textdocDidClose: function('s:textdocDidClose', [lspserver]),
     textdocDidChange: function('s:textdocDidChange', [lspserver]),