diff --git a/autoload/go/config.vim b/autoload/go/config.vim index 321c98c422..dd12a28ea8 100644 --- a/autoload/go/config.vim +++ b/autoload/go/config.vim @@ -525,6 +525,10 @@ function! go#config#GoplsFuzzyMatching() abort return get(g:, 'go_gopls_fuzzy_matching', 1) endfunction +function! go#config#GoplsStaticCheck() abort + return get(g:, 'go_gopls_staticcheck', 0) +endfunction + function! go#config#GoplsUsePlaceholders() abort return get(g:, 'go_gopls_use_placeholders', 0) endfunction @@ -533,6 +537,10 @@ function! go#config#GoplsEnabled() abort return get(g:, 'go_gopls_enabled', 1) endfunction +function! go#config#DiagnosticsEnabled() abort + return get(g:, 'go_diagnostics_enabled', 0) +endfunction + " Set the default value. A value of "1" is a shortcut for this, for " compatibility reasons. if exists("g:go_gorename_prefill") && g:go_gorename_prefill == 1 diff --git a/autoload/go/lint.vim b/autoload/go/lint.vim index d78a92df88..cd7c8ee5bb 100644 --- a/autoload/go/lint.vim +++ b/autoload/go/lint.vim @@ -11,6 +11,7 @@ function! go#lint#Gometa(bang, autosave, ...) abort let l:metalinter = go#config#MetalinterCommand() + let cmd = [] if l:metalinter == 'golangci-lint' let cmd = s:metalintercmd(l:metalinter) if empty(cmd) @@ -22,7 +23,7 @@ function! go#lint#Gometa(bang, autosave, ...) abort for linter in linters let cmd += ["--enable=".linter] endfor - else + elseif l:metalinter != 'gopls' " the user wants something else, let us use it. let cmd = split(go#config#MetalinterCommand(), " ") endif @@ -45,17 +46,35 @@ function! go#lint#Gometa(bang, autosave, ...) abort let cmd += goargs - " Golangci-lint can output the following: - " ::: () - " This can be defined by the following errorformat: - let errformat = "%f:%l:%c:\ %m" + let errformat = s:errorformat(l:metalinter) - if go#util#has_job() - call s:lint_job({'cmd': cmd, 'statustype': l:metalinter, 'errformat': errformat}, a:bang, a:autosave) - return - endif + if l:metalinter == 'gopls' + if a:autosave + let l:messages = go#lsp#AnalyzeFile(expand('%:p')) + else + let l:import_paths = l:goargs + if len(l:import_paths) == 0 + let l:pkg = go#package#ImportPath() + if l:pkg == -1 + call go#util#EchoError('could not determine package name') + return + endif + + let l:import_paths = [l:pkg] + endif + let l:messages = call('go#lsp#Diagnostics', l:import_paths) + endif + + let l:err = len(l:messages) + else + if go#util#has_job() + call s:lint_job({'cmd': cmd, 'statustype': l:metalinter, 'errformat': errformat}, a:bang, a:autosave) + return + endif - let [l:out, l:err] = go#util#Exec(cmd) + let [l:out, l:err] = go#util#Exec(cmd) + let l:messages = split(out, "\n") + endif if a:autosave let l:listtype = go#list#Type("GoMetaLinterAutoSave") @@ -70,9 +89,7 @@ function! go#lint#Gometa(bang, autosave, ...) abort let l:winid = win_getid(winnr()) " Parse and populate our location list - let l:messages = split(out, "\n") - - if a:autosave + if a:autosave && l:metalinter != 'gopls' call s:metalinterautosavecomplete(fnamemodify(expand('%:p'), ":."), 0, 1, l:messages) endif call go#list#ParseFormat(l:listtype, errformat, l:messages, 'GoMetaLinter') @@ -88,6 +105,44 @@ function! go#lint#Gometa(bang, autosave, ...) abort endif endfunction +function! go#lint#Diagnostics(bang, ...) abort + if a:0 == 0 + let l:pkg = go#package#ImportPath() + if l:pkg == -1 + call go#util#EchoError('could not determine package name') + return + endif + + let l:import_paths = [l:pkg] + else + let l:import_paths = a:000 + endif + + let errformat = s:errorformat('gopls') + + let l:messages = call('go#lsp#Diagnostics', l:import_paths) + + let l:listtype = go#list#Type("GoDiagnostics") + + if len(l:messages) == 0 + call go#list#Clean(l:listtype) + call go#util#EchoSuccess('[diagnostics] PASS') + else + " Parse and populate the quickfix list + let l:winid = win_getid(winnr()) + call go#list#ParseFormat(l:listtype, errformat, l:messages, 'GoDiagnostics') + + let errors = go#list#Get(l:listtype) + call go#list#Window(l:listtype, len(errors)) + + if a:bang + call win_gotoid(l:winid) + return + endif + call go#list#JumpToFirst(l:listtype) + endif +endfunction + " Golint calls 'golint' on the current directory. Any warnings are populated in " the location list function! go#lint#Golint(bang, ...) abort @@ -275,18 +330,25 @@ function! s:metalinterautosavecomplete(filepath, job, exit_code, messages) let l:idx = len(a:messages) - 1 while l:idx >= 0 - " Go 1.13 changed how go vet output is formatted by prepending a leading - " 'vet :', so account for that, too. This function is really needed for - " gometalinter at all, so the check for Go 1.13's go vet output shouldn't - " be neeeded, but s:lint_job hooks this up even when the - " g:go_metalinter_command is golangci-lint. - if a:messages[l:idx] !~# '^' . a:filepath . ':' && a:messages[l:idx] !~# '^vet: \.[\\/]' . a:filepath . ':' + if a:messages[l:idx] !~# '^' . a:filepath . ':' call remove(a:messages, l:idx) endif let l:idx -= 1 endwhile endfunction +function! s:errorformat(metalinter) abort + if a:metalinter == 'golangci-lint' + " Golangci-lint can output the following: + " ::: () + " This can be defined by the following errorformat: + return '%f:%l:%c:\ %m' + elseif a:metalinter == 'gopls' + return '%f:%l:%c:%t:\ %m,%f:%l:%c::\ %m' + endif + +endfunction + " restore Vi compatibility settings let &cpo = s:cpo_save unlet s:cpo_save diff --git a/autoload/go/list.vim b/autoload/go/list.vim index c083c2b702..87ae539041 100644 --- a/autoload/go/list.vim +++ b/autoload/go/list.vim @@ -135,6 +135,7 @@ endfunction " in g:go_list_type_commands. let s:default_list_type_commands = { \ "GoBuild": "quickfix", + \ "GoDiagnostics": "quickfix", \ "GoDebug": "quickfix", \ "GoErrCheck": "quickfix", \ "GoFmt": "locationlist", diff --git a/autoload/go/lsp.vim b/autoload/go/lsp.vim index 5b8f9feadc..26fd63ab32 100644 --- a/autoload/go/lsp.vim +++ b/autoload/go/lsp.vim @@ -53,6 +53,18 @@ function! s:newlsp() abort " * handleResult takes a single argument, the result message received from gopls " * error takes a single argument, the error message received from gopls. " The error method is optional. + " workspaceDirectories is an array of named workspaces. + " wd is the working directory for gopls + " diagnostics is a dictionary whose keys are filenames and each value is a + " list of diagnostic messages for the file. + " diagnosticsQueue is a queue of diagnostics notifications that have been + " received, but not yet processed. + " fileVersions is a dictionary of filenames to versions. + " notificationQueue is a dictionary of filenames to functions. For a given + " filename, each notification will call the first function in the list of + " function values and remove it from the list. The functions should accept + " two arguments: an absolute path and a list of diagnotics messages for + " the file. let l:lsp = { \ 'job': '', \ 'ready': 0, @@ -62,6 +74,10 @@ function! s:newlsp() abort \ 'handlers': {}, \ 'workspaceDirectories': [], \ 'wd' : '', + \ 'diagnosticsQueue': [], + \ 'diagnostics': {}, + \ 'fileVersions': {}, + \ 'notificationQueue': {} \ } function! l:lsp.readMessage(data) dict abort @@ -152,13 +168,6 @@ function! s:newlsp() abort call self.write(l:msg) endfunction - function! l:lsp.handleNotification(req) dict abort - " TODO(bc): handle more notifications (e.g. window/showMessage). - if a:req.method == 'textDocument/publishDiagnostics' - call s:handleDiagnostics(a:req.params) - endif - endfunction - function! l:lsp.handleResponse(resp) dict abort if has_key(a:resp, 'id') && has_key(self.handlers, a:resp.id) try @@ -200,6 +209,76 @@ function! s:newlsp() abort endif endfunction + function! l:lsp.handleNotification(req) dict abort + " TODO(bc): handle more notifications (e.g. window/showMessage). + if a:req.method == 'textDocument/publishDiagnostics' + call self.handleDiagnostics(a:req.params) + endif + endfunction + + function! l:lsp.handleDiagnostics(data) dict abort + let self.diagnosticsQueue = add(self.diagnosticsQueue, a:data) + call self.updateDiagnostics() + endfunction + + " TODO(bc): process the queue asynchronously + function! l:lsp.updateDiagnostics() dict abort + for l:data in self.diagnosticsQueue + call remove(self.diagnosticsQueue, 0) + try + let l:diagnostics = [] + let l:errorMatches = [] + let l:warningMatches = [] + let l:fname = go#path#FromURI(l:data.uri) + " get the buffer name relative to the current directory, because + " Vim says that a buffer name can't be an absolute path. + let l:bufname = fnamemodify(l:fname, ':.') + + if len(l:data.diagnostics) > 0 && (go#config#DiagnosticsEnabled() || bufnr(l:bufname) == bufnr('')) + " make sure the buffer is listed and loaded before calling getbufline() on it + if !bufexists(l:bufname) + "let l:starttime = reltime() + call bufadd(l:bufname) + "echom printf('added %s (%s)', l:bufname, reltimestr(reltime(l:startime))) + endif + + if !bufloaded(l:bufname) + "let l:starttime = reltime() + call bufload(l:bufname) + "echom printf('loaded %s (%s)', l:bufname, reltimestr(reltime(l:starttime))) + endif + + for l:diag in l:data.diagnostics + let [l:error, l:matchpos] = s:errorFromDiagnostic(l:diag, l:bufname, l:fname) + let l:diagnostics = add(l:diagnostics, l:error) + + if empty(l:matchpos) + continue + endif + + if l:diag.severity == 1 + let l:errorMatches = add(l:errorMatches, l:matchpos) + elseif l:diag.severity == 2 + let l:warningMatches = add(l:warningMatches, l:matchpos) + endif + endfor + endif + + if bufnr(l:bufname) == bufnr('') + call s:highlightMatches(l:errorMatches, l:warningMatches) + endif + + let self.diagnostics[l:fname] = l:diagnostics + if has_key(self.notificationQueue, l:fname) && len(self.notificationQueue[l:fname]) > 0 + call call(self.notificationQueue[l:fname][0], copy(l:diagnostics)) + call remove(self.notificationQueue[l:fname], 0) + endif + catch + call go#util#EchoError(printf('%s: %s', v:throwpoint, v:exception)) + endtry + endfor + endfunction + function! l:lsp.handleInitializeResult(result) dict abort if go#config#EchoCommandInfo() call go#util#EchoProgress("initialized gopls") @@ -394,6 +473,7 @@ function! s:newHandlerState(statustype) abort \ 'winid': win_getid(winnr()), \ 'statustype': a:statustype, \ 'jobdir': getcwd(), + \ 'handleResult': funcref('s:noop'), \ } " explicitly bind requestComplete to state so that within it, self will @@ -509,9 +589,19 @@ function! go#lsp#DidOpen(fname) abort endif let l:lsp = s:lspfactory.get() - let l:msg = go#lsp#message#DidOpen(fnamemodify(a:fname, ':p'), join(go#util#GetLines(), "\n") . "\n") + let l:fname = fnamemodify(a:fname, ':p') + + if !has_key(l:lsp.notificationQueue, l:fname) + let l:lsp.notificationQueue[l:fname] = [] + endif + + if !has_key(l:lsp.fileVersions, l:fname) + let l:lsp.fileVersions[l:fname] = 0 + endif + let l:lsp.fileVersions[l:fname] = l:lsp.fileVersions[l:fname] + 1 + + let l:msg = go#lsp#message#DidOpen(l:fname, join(go#util#GetLines(), "\n") . "\n", l:lsp.fileVersions[l:fname]) let l:state = s:newHandlerState('') - let l:state.handleResult = funcref('s:noop') " TODO(bc): setting a buffer level variable here assumes that a:fname is the " current buffer. Change to a:fname first before setting it and then change @@ -536,9 +626,15 @@ function! go#lsp#DidChange(fname) abort call go#lsp#DidOpen(a:fname) let l:lsp = s:lspfactory.get() - let l:msg = go#lsp#message#DidChange(fnamemodify(a:fname, ':p'), join(go#util#GetLines(), "\n") . "\n") + + let l:fname = fnamemodify(a:fname, ':p') + if !has_key(l:lsp.fileVersions, l:fname) + let l:lsp.fileVersions[l:fname] = 0 + endif + let l:lsp.fileVersions[l:fname] = l:lsp.fileVersions[l:fname] + 1 + + let l:msg = go#lsp#message#DidChange(l:fname, join(go#util#GetLines(), "\n") . "\n", l:lsp.fileVersions[l:fname]) let l:state = s:newHandlerState('') - let l:state.handleResult = funcref('s:noop') return l:lsp.sendMessage(l:msg, l:state) endfunction @@ -554,7 +650,6 @@ function! go#lsp#DidClose(fname) abort let l:lsp = s:lspfactory.get() let l:msg = go#lsp#message#DidClose(fnamemodify(a:fname, ':p')) let l:state = s:newHandlerState('') - let l:state.handleResult = funcref('s:noop') " TODO(bc): setting a buffer level variable here assumes that a:fname is the " current buffer. Change to a:fname first before setting it and then change " back to active buffer. @@ -854,7 +949,6 @@ function! go#lsp#AddWorkspaceDirectory(...) abort let l:lsp = s:lspfactory.get() let l:state = s:newHandlerState('') - let l:state.handleResult = funcref('s:noop') let l:lsp.workspaceDirectories = extend(l:lsp.workspaceDirectories, l:workspaces) let l:msg = go#lsp#message#ChangeWorkspaceFolders(l:workspaces, []) call l:lsp.sendMessage(l:msg, l:state) @@ -883,7 +977,6 @@ function! go#lsp#CleanWorkspaces() abort endif let l:state = s:newHandlerState('') - let l:state.handleResult = funcref('s:noop') let l:msg = go#lsp#message#ChangeWorkspaceFolders([], l:missing) call l:lsp.sendMessage(l:msg, l:state) @@ -900,7 +993,6 @@ function! go#lsp#ResetWorkspaceDirectories() abort let l:lsp = s:lspfactory.get() let l:state = s:newHandlerState('') - let l:state.handleResult = funcref('s:noop') let l:msg = go#lsp#message#ChangeWorkspaceFolders(l:lsp.workspaceDirectories, l:lsp.workspaceDirectories) call l:lsp.sendMessage(l:msg, l:state) @@ -942,7 +1034,6 @@ function! s:exit(restart) abort let l:state = s:newHandlerState('exit') let l:msg = go#lsp#message#Shutdown() - let l:state.handleResult = funcref('s:noop') let l:retval = l:lsp.sendMessage(l:msg, l:state) let l:msg = go#lsp#message#Exit() @@ -1007,58 +1098,117 @@ function! s:compareLocations(left, right) abort return 1 endfunction -function! s:handleDiagnostics(data) abort - if !exists("*matchaddpos") - return 0 +function! go#lsp#Diagnostics(...) abort + if a:0 == 0 + return [] endif - try - let l:fname = go#path#FromURI(a:data.uri) - if bufnr(l:fname) == bufnr('') - let l:errorMatches = [] - let l:warningMatches = [] - for l:diag in a:data.diagnostics - if !(l:diag.severity == 1 || l:diag.severity == 2) - continue - endif - let l:range = l:diag.range - if l:range.start.line != l:range.end.line - continue - endif + let l:dirsToPackages = {} - let l:line = l:range.start.line + 1 - let l:col = go#lsp#lsp#PositionOf(getline(l:line), l:range.start.character) - let l:lastcol = go#lsp#lsp#PositionOf(getline(l:line), l:range.end.character) + let l:lsp = s:lspfactory.get() - let l:pos = [l:line, l:col, l:lastcol - l:col + 1] - if l:diag.severity == 1 - let l:errorMatches = add(l:errorMatches, l:pos) - elseif l:diag.severity == 2 - let l:warningMatches = add(l:warningMatches, l:pos) - endif - endfor + let l:diagnostics = [] + for [l:key, l:val] in items(l:lsp.diagnostics) + let l:dir = fnamemodify(l:key, ':h') - if hlexists('goDiagnosticError') - " clear the old matches just before adding the new ones to keep flicker - " to a minimum. - call go#util#ClearGroupFromMatches('goDiagnosticError') - if go#config#HighlightDiagnosticErrors() - call matchaddpos('goDiagnosticError', l:errorMatches) - endif + if !has_key(l:dirsToPackages, l:dir) + let l:pkg = go#package#FromPath(l:dir) + let l:dirsToPackages[l:dir] = l:pkg + else + let l:pkg = l:dirsToPackages[l:dir] + endif + + if type(l:pkg) == type(0) + continue + endif + + for l:arg in a:000 + if l:arg == l:pkg || l:arg == 'all' + let l:diagnostics = extend(l:diagnostics, l:val) endif + endfor + endfor - if hlexists('goDiagnosticError') - " clear the old matches just before adding the new ones to keep flicker - " to a minimum. - call go#util#ClearGroupFromMatches('goDiagnosticWarning') - if go#config#HighlightDiagnosticWarnings() - call matchaddpos('goDiagnosticWarning', l:warningMatches) - endif + return sort(l:diagnostics) +endfunction + +function! go#lsp#AnalyzeFile(fname) abort + if !filereadable(a:fname) + return [] + endif + + let l:lsp = s:lspfactory.get() + let l:fname = fnamemodify(a:fname, ':p') + + let l:version = l:lsp.fileVersions[l:fname] + + call go#lsp#DidChange(a:fname) + + let l:diagnostics = go#promise#New(function('s:setDiagnostics', []), 10000, get(l:lsp.diagnostics, l:fname, [])) + let l:lsp.notificationQueue[l:fname] = add(l:lsp.notificationQueue[l:fname], l:diagnostics.wrapper) + return l:diagnostics.await() +endfunction + +function! s:setDiagnostics(...) abort + return a:000 +endfunction + +" s:processDiagnostic converts a diagnostic into an error string. It returns +" the errors string and the match position described in the diagnostic. The +" match position will be an empty list when bufname is not a valid name for +" the current buffer. +function! s:errorFromDiagnostic(diagnostic, bufname, fname) abort + let l:range = a:diagnostic.range + + let l:line = l:range.start.line + 1 + let l:col = go#lsp#lsp#PositionOf(getbufline(a:bufname, l:line)[0], l:range.start.character) + let l:error = printf('%s:%s:%s:%s: %s', a:fname, l:line, l:col, go#lsp#lsp#SeverityToErrorType(a:diagnostic.severity), a:diagnostic.message) + + if !(a:diagnostic.severity == 1 || a:diagnostic.severity == 2) + return [l:error, []] + endif + + " return when the diagnostic is not for the current buffer. + if bufnr(a:bufname) != bufnr('') + return [l:error, []] + end + + let l:endline = l:range.end.line + 1 + " don't bother trying to highlight errors or warnings that span + " the whole file (e.g when there's missing package documentation). + if l:line == 1 && (l:endline) == line('$') + return [l:error, []] + endif + let l:endcol = go#lsp#lsp#PositionOf(getline(l:endline), l:range.end.character) + + " the length of the match is the number of bytes between the start of + " the match and the end of the match. + let l:matchLength = line2byte(l:endline) + l:endcol - (line2byte(l:line) + l:col) + let l:pos = [l:line, l:col, l:matchLength] + + return [l:error, l:pos] +endfunction + +function! s:highlightMatches(errorMatches, warningMatches) abort + if exists("*matchaddpos") + if hlexists('goDiagnosticError') + " clear the old matches just before adding the new ones to keep flicker + " to a minimum. + call go#util#ClearGroupFromMatches('goDiagnosticError') + if go#config#HighlightDiagnosticErrors() + call matchaddpos('goDiagnosticError', a:errorMatches) endif endif - catch - call go#util#EchoError(v:exception) - endtry + + if hlexists('goDiagnosticWarning') + " clear the old matches just before adding the new ones to keep flicker + " to a minimum. + call go#util#ClearGroupFromMatches('goDiagnosticWarning') + if go#config#HighlightDiagnosticWarnings() + call matchaddpos('goDiagnosticWarning', a:warningMatches) + endif + endif + endif endfunction " restore Vi compatibility settings diff --git a/autoload/go/lsp/lsp.vim b/autoload/go/lsp/lsp.vim index 56a2da4cdc..c0cf3e082c 100644 --- a/autoload/go/lsp/lsp.vim +++ b/autoload/go/lsp/lsp.vim @@ -30,13 +30,13 @@ endfunction " go#lsp#PositionOf returns len(content[0:units]) where units is utf-16 code " units. This is mostly useful for converting LSP text position to vim " position. -function! go#lsp#lsp#PositionOf(content, units) abort +function! go#lsp#lsp#PositionOf(content, units, ...) abort if a:units == 0 return 1 endif let l:remaining = a:units - let l:str = "" + let l:str = '' for l:rune in split(a:content, '\zs') if l:remaining < 0 break @@ -51,6 +51,20 @@ function! go#lsp#lsp#PositionOf(content, units) abort return len(l:str) endfunction +function! go#lsp#lsp#SeverityToErrorType(severity) abort + if a:severity == 1 + return 'E' + elseif a:severity == 2 + return 'W' + elseif a:severity == 3 + return 'I' + elseif a:severity == 4 + return 'I' + endif + + return '' +endfunction + " restore Vi compatibility settings let &cpo = s:cpo_save unlet s:cpo_save diff --git a/autoload/go/lsp/message.vim b/autoload/go/lsp/message.vim index 57e634fe7f..d7122ca6a5 100644 --- a/autoload/go/lsp/message.vim +++ b/autoload/go/lsp/message.vim @@ -85,7 +85,7 @@ function! go#lsp#message#TypeDefinition(file, line, col) abort \ } endfunction -function! go#lsp#message#DidOpen(file, content) abort +function! go#lsp#message#DidOpen(file, content, version) abort return { \ 'notification': 1, \ 'method': 'textDocument/didOpen', @@ -94,18 +94,20 @@ function! go#lsp#message#DidOpen(file, content) abort \ 'uri': go#path#ToURI(a:file), \ 'languageId': 'go', \ 'text': a:content, + \ 'version': a:version, \ } \ } \ } endfunction -function! go#lsp#message#DidChange(file, content) abort +function! go#lsp#message#DidChange(file, content, version) abort return { \ 'notification': 1, \ 'method': 'textDocument/didChange', \ 'params': { \ 'textDocument': { \ 'uri': go#path#ToURI(a:file), + \ 'version': a:version, \ }, \ 'contentChanges': [ \ { @@ -198,6 +200,7 @@ function! go#lsp#message#ConfigurationResult(items) abort \ 'deepCompletion': go#config#GoplsDeepCompletion() ? v:true : v:false, \ 'fuzzyMatching': go#config#GoplsFuzzyMatching() ? v:true : v:false, \ 'completeUnimported': go#config#GoplsCompleteUnimported() ? v:true : v:false, + \ 'staticcheck': go#config#GoplsStaticCheck() ? v:true : v:false, \ 'usePlaceholders': go#config#GoplsUsePlaceholders() ? v:true : v:false, \ } let l:buildtags = go#config#BuildTags() diff --git a/doc/vim-go.txt b/doc/vim-go.txt index eb98113c72..4433e5ef35 100644 --- a/doc/vim-go.txt +++ b/doc/vim-go.txt @@ -658,6 +658,18 @@ CTRL-t use the variable |'g:go_metalinter_command'|. To override the maximum linters execution time use |'g:go_metalinter_deadline'| variable. + If [!] is not given the first error is jumped to. + + *:GoDiagnostics* +:GoDiagnostics! [packages] + + Displays the diagnostics from `gopls` for the given packages in a + |quickfix| window. The diagnostics for the current package are displayed + when no package is given. The diagnostics for all packages will be + displayed when `all` is as an argument. + + Disabled when |'g:go_diagnostics_enabled'| is not set. + If [!] is not given the first error is jumped to. *:GoBuildTags* @@ -1566,8 +1578,10 @@ it's using `vet`, `golint` and `errcheck`. *'g:go_metalinter_command'* Overrides the command to be executed when |:GoMetaLinter| is called. By -default it's `golangci-lint`. It can also be used as an advanced setting -for users who want to have more control over the metalinter. +default it's `golangci-lint`. Valid options are `golangci-lint` and `gopls`. +When the value is `gopls`, users may want to consider setting +`g:go_gopls_staticcheck`. It can also be used as an advanced setting for +users who want to have more control over the metalinter. > let g:go_metalinter_command = "golangci-lint" < @@ -1761,6 +1775,14 @@ By default it is enabled. let g:go_gopls_fuzzy_matching = 1 < + *'g:go_gopls_staticcheck'* + +Specifies whether `gopls` should run staticcheck checks. By default it is +disabled. +> + let g:go_gopls_staticcheck = 0 +< + *'g:go_gopls_use_placeholders'* Specifies whether `gopls` can provide placeholders for function parameters and @@ -1771,6 +1793,15 @@ snippets if UltiSnips is installed and configured to be used as let g:go_gopls_use_placeholders = 0 < + *'g:go_diagnostics_enabled'* + +Specifies whether `gopls` diagnostics are enabled. Only the diagnostics for +the current buffer will be processed when it is not set; all others will be +ignored. By default it is disabled. +> + let g:go_diagnostics_enabled = 0 +< + *'g:go_template_autocreate'* When a new Go file is created, vim-go automatically fills the buffer content diff --git a/ftplugin/go/commands.vim b/ftplugin/go/commands.vim index 7c7f611189..2fdeeffc2f 100644 --- a/ftplugin/go/commands.vim +++ b/ftplugin/go/commands.vim @@ -119,6 +119,7 @@ command! -nargs=0 GoIfErr call go#iferr#Generate() " -- lsp command! -nargs=+ -complete=dir GoAddWorkspace call go#lsp#AddWorkspaceDirectory() command! -nargs=0 GoLSPDebugBrowser call go#lsp#DebugBrowser() +command! -nargs=* -bang GoDiagnostics call go#lint#Diagnostics(0, ) " -- term command! GoToggleTermCloseOnExit call go#term#ToggleCloseOnExit() diff --git a/ftplugin/go/mappings.vim b/ftplugin/go/mappings.vim index 986aadadfc..f8fb00b148 100644 --- a/ftplugin/go/mappings.vim +++ b/ftplugin/go/mappings.vim @@ -83,4 +83,6 @@ nnoremap (go-alternate-split) :call go#alternate#Switch(0, " nnoremap (go-iferr) :call go#iferr#Generate() +nnoremap (go-diagnostics) :call go#lint#Diagnostics(!g:go_jump_to_error) + " vim: sw=2 ts=2 et