From 116d713f63c7a81663fa53efa10e34649c9479e3 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sun, 29 Jan 2023 17:25:09 +0000 Subject: diagnostics: support sending ALE output to Neovim's diagnostics API (#4345) Support replacing ALE's display of problems with sending problems to the Neovim diagnostics API. :help g:ale_use_neovim_diagnostics_api Co-authored-by: David Balatero Co-authored-by: Georgi Angelchev Co-authored-by: w0rp --- autoload/ale/debugging.vim | 1 + autoload/ale/engine.vim | 27 +++++++++++++++--- autoload/ale/virtualtext.vim | 11 ++++---- doc/ale.txt | 25 +++++++++++++++-- lua/diagnostics.lua | 49 +++++++++++++++++++++++++++++++++ plugin/ale.vim | 9 ++++++ test/sign/test_linting_sets_signs.vader | 9 ++++++ test/test_ale_info.vader | 3 ++ test/test_virtualtext.vader | 15 ++++++++++ 9 files changed, 137 insertions(+), 12 deletions(-) create mode 100644 lua/diagnostics.lua diff --git a/autoload/ale/debugging.vim b/autoload/ale/debugging.vim index 31f3078c..c100d6e6 100644 --- a/autoload/ale/debugging.vim +++ b/autoload/ale/debugging.vim @@ -55,6 +55,7 @@ let s:global_variable_list = [ \ 'ale_sign_highlight_linenrs', \ 'ale_statusline_format', \ 'ale_type_map', +\ 'ale_use_neovim_diagnostics_api', \ 'ale_use_global_executables', \ 'ale_virtualtext_cursor', \ 'ale_warn_about_trailing_blank_lines', diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim index 150bbc82..185d54db 100644 --- a/autoload/ale/engine.vim +++ b/autoload/ale/engine.vim @@ -184,9 +184,13 @@ endfunction function! ale#engine#SetResults(buffer, loclist) abort let l:linting_is_done = !ale#engine#IsCheckingBuffer(a:buffer) + if g:ale_use_neovim_diagnostics_api + call ale#engine#SendResultsToNeovimDiagnostics(a:buffer, a:loclist) + endif + " Set signs first. This could potentially fix some line numbers. " The List could be sorted again here by SetSigns. - if g:ale_set_signs + if !g:ale_use_neovim_diagnostics_api && g:ale_set_signs call ale#sign#SetSigns(a:buffer, a:loclist) endif @@ -199,11 +203,12 @@ function! ale#engine#SetResults(buffer, loclist) abort call ale#statusline#Update(a:buffer, a:loclist) endif - if g:ale_set_highlights + if !g:ale_use_neovim_diagnostics_api && g:ale_set_highlights call ale#highlight#SetHighlights(a:buffer, a:loclist) endif - if g:ale_virtualtext_cursor is# 'all' || g:ale_virtualtext_cursor == 2 + if !g:ale_use_neovim_diagnostics_api + \&& (g:ale_virtualtext_cursor is# 'all' || g:ale_virtualtext_cursor == 2) call ale#virtualtext#SetTexts(a:buffer, a:loclist) endif @@ -214,7 +219,8 @@ function! ale#engine#SetResults(buffer, loclist) abort call ale#cursor#EchoCursorWarning() endif - if g:ale_virtualtext_cursor is# 'current' || g:ale_virtualtext_cursor == 1 + if !g:ale_use_neovim_diagnostics_api + \&& (g:ale_virtualtext_cursor is# 'current' || g:ale_virtualtext_cursor == 1) " Try and show the warning now. " This will only do something meaningful if we're in normal mode. call ale#virtualtext#ShowCursorWarning() @@ -238,6 +244,19 @@ function! ale#engine#SetResults(buffer, loclist) abort endif endfunction +function! ale#engine#SendResultsToNeovimDiagnostics(buffer, loclist) abort + if !has('nvim-0.6') + " We will warn the user on startup as well if they try to set + " g:ale_use_neovim_diagnostics_api outside of a Neovim context. + return + endif + + " Keep the Lua surface area really small in the VimL part of ALE, + " and just require the diagnostics.lua module on demand. + let l:SendDiagnostics = luaeval('require("diagnostics").sendAleResultsToDiagnostics') + call l:SendDiagnostics(a:buffer, a:loclist) +endfunction + function! s:RemapItemTypes(type_map, loclist) abort for l:item in a:loclist let l:key = l:item.type diff --git a/autoload/ale/virtualtext.vim b/autoload/ale/virtualtext.vim index 0f2b2173..4897154b 100644 --- a/autoload/ale/virtualtext.vim +++ b/autoload/ale/virtualtext.vim @@ -187,10 +187,8 @@ function! ale#virtualtext#ShowCursorWarning(...) abort let l:buffer = bufnr('') if mode(1) isnot# 'n' - return - endif - - if ale#ShouldDoNothing(l:buffer) + \|| g:ale_use_neovim_diagnostics_api + \|| ale#ShouldDoNothing(l:buffer) return endif @@ -210,12 +208,13 @@ function! ale#virtualtext#ShowCursorWarningWithDelay() abort return endif + call s:StopCursorTimer() + if mode(1) isnot# 'n' + \|| g:ale_use_neovim_diagnostics_api return endif - call s:StopCursorTimer() - let l:pos = getpos('.')[0:2] " Check the current buffer, line, and column number against the last diff --git a/doc/ale.txt b/doc/ale.txt index 2876b235..c9bebd05 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -121,6 +121,7 @@ circumstances. ALE will report problems with your code in the following ways, listed with their relevant options. +* Via the Neovim diagnostics API (Off by default) - |g:ale_use_neovim_diagnostics_api| * By updating loclist. (On by default) - |g:ale_set_loclist| * By updating quickfix. (Off by default) - |g:ale_set_quickfix| * By setting error highlights. - |g:ale_set_highlights| @@ -1235,7 +1236,7 @@ g:ale_floating_preview *g:ale_floating_preview* |g:ale_detail_to_floating_preview| to `1`. -g:ale_floating_preview_popup_opts *g:ale_floating_preview_popup_opts* +g:ale_floating_preview_popup_opts *g:ale_floating_preview_popup_opts* Type: |String| or |Dictionary| Default: `''` @@ -1258,7 +1259,7 @@ g:ale_floating_preview_popup_opts *g:ale_floating_preview_popup_opts < -g:ale_floating_window_border *g:ale_floating_window_border* +g:ale_floating_window_border *g:ale_floating_window_border* Type: |List| Default: `['|', '-', '+', '+', '+', '+', '|', '-']` @@ -2288,6 +2289,26 @@ g:ale_use_global_executables *g:ale_use_global_executables* options. +g:ale_use_neovim_diagnostics_api *g:ale_use_neovim_diagnostics_api* + + Type: |Number| + Default: `0` + + If enabled, this option will disable ALE's standard UI, and instead send + all linter output to Neovim's diagnostics API. This allows you to collect + errors from nvim-lsp, ALE, and anything else that uses diagnostics all in + one place. The following options are ignored when using the diagnostics API: + + - |g:ale_set_highlights| + - |g:ale_set_signs| + - |g:ale_virtualtext_cursor| + + To enable this option, set the value to `1`. + + This option requires Neovim 0.6+, as that version introduces the diagnostics + API. + + g:ale_virtualtext_cursor *g:ale_virtualtext_cursor* Type: |Number| diff --git a/lua/diagnostics.lua b/lua/diagnostics.lua new file mode 100644 index 00000000..1ac6eb57 --- /dev/null +++ b/lua/diagnostics.lua @@ -0,0 +1,49 @@ +local module = {} + +local ale_type_to_diagnostic_severity = { + E = vim.diagnostic.severity.ERROR, + W = vim.diagnostic.severity.WARN, + I = vim.diagnostic.severity.INFO +} + +module.sendAleResultsToDiagnostics = function(buffer, loclist) + local diagnostics = {} + + -- Convert all the ALE loclist items to the shape that Neovim's diagnostic + -- API is expecting. + for _, location in ipairs(loclist) do + table.insert( + diagnostics, + -- All line numbers from ALE are 1-indexed, but all line numbers + -- in the diagnostics API are 0-indexed, so we have to subtract 1 + -- to make this work. + { + lnum = location.lnum - 1, + -- Ending line number, or if we don't have one, just make it the same + -- as the starting line number + end_lnum = (location.end_lnum or location.lnum) - 1, + -- Which column does the error start on? + col = math.max((location.col or 1) - 1, 0), + -- end_col does *not* appear to need 1 subtracted, so we don't. + end_col = location.end_col, + -- Which severity: error, warning, or info? + severity = ale_type_to_diagnostic_severity[location.type] or "E", + -- The error message + message = location.text, + -- e.g. "rubocop" + source = location.linter_name, + } + ) + end + + local virtualtext_enabled_set = {['all'] = true, ['2'] = true, ['current'] = true, ['1'] = true} + + vim.diagnostic.set( + vim.api.nvim_create_namespace('ale'), + buffer, + diagnostics, + { virtual_text = virtualtext_enabled_set[vim.g.ale_virtualtext_cursor] ~= nil} + ) +end + +return module diff --git a/plugin/ale.vim b/plugin/ale.vim index 6aa115ea..b9786bb0 100644 --- a/plugin/ale.vim +++ b/plugin/ale.vim @@ -187,6 +187,15 @@ let g:ale_deno_executable = get(g:, 'ale_deno_executable', 'deno') " If 1, enable a popup menu for commands. let g:ale_popup_menu_enabled = get(g:, 'ale_popup_menu_enabled', has('gui_running')) +" If 1, disables ALE's built in error display. Instead, all errors are piped +" to the diagnostics API. +let g:ale_use_neovim_diagnostics_api = get(g:, 'ale_use_neovim_diagnostics_api', 0) + +if g:ale_use_neovim_diagnostics_api && !has('nvim-0.6') + " no-custom-checks + echoerr('Setting g:ale_use_neovim_diagnostics_api to 1 requires Neovim 0.6+.') +endif + if g:ale_set_balloons is 1 || g:ale_set_balloons is# 'hover' call ale#balloon#Enable() endif diff --git a/test/sign/test_linting_sets_signs.vader b/test/sign/test_linting_sets_signs.vader index 1624449a..6c1ac39f 100644 --- a/test/sign/test_linting_sets_signs.vader +++ b/test/sign/test_linting_sets_signs.vader @@ -11,6 +11,7 @@ Before: Save g:ale_set_quickfix Save g:ale_set_signs Save g:ale_command_wrapper + Save g:ale_use_neovim_diagnostics_api let g:ale_command_wrapper = '' let g:ale_buffer_info = {} @@ -22,6 +23,7 @@ Before: let g:ale_set_loclist = 0 let g:ale_set_highlights = 0 let g:ale_echo_cursor = 0 + let g:ale_use_neovim_diagnostics_api = 0 call ale#sign#Clear() @@ -74,3 +76,10 @@ Execute(The signs should be updated after linting is done): call ale#test#FlushJobs() AssertEqual [['1', 'ALEWarningSign'], ['2', 'ALEErrorSign']], CollectSigns() + +Execute(Signs should not be set when diagnostics API integration is enabled): + let g:ale_use_neovim_diagnostics_api = 1 + ALELint + call ale#test#FlushJobs() + + AssertEqual [], CollectSigns() diff --git a/test/test_ale_info.vader b/test/test_ale_info.vader index ded65a1f..4cedccc2 100644 --- a/test/test_ale_info.vader +++ b/test/test_ale_info.vader @@ -52,6 +52,7 @@ Before: Save g:ale_sign_highlight_linenrs Save g:ale_statusline_format Save g:ale_type_map + Save g:ale_use_neovim_diagnostics_api Save g:ale_use_global_executables Save g:ale_virtualtext_cursor Save g:ale_warn_about_trailing_blank_lines @@ -110,6 +111,7 @@ Before: let g:ale_sign_highlight_linenrs = 0 let g:ale_statusline_format = ['%d error(s)', '%d warning(s)', 'OK'] let g:ale_type_map = {} + let g:ale_use_neovim_diagnostics_api = 0 let g:ale_use_global_executables = v:null let g:ale_virtualtext_cursor = 'disabled' let g:ale_warn_about_trailing_blank_lines = 1 @@ -189,6 +191,7 @@ Before: \ 'let g:ale_sign_highlight_linenrs = 0', \ 'let g:ale_statusline_format = [''%d error(s)'', ''%d warning(s)'', ''OK'']', \ 'let g:ale_type_map = {}', + \ 'let g:ale_use_neovim_diagnostics_api = 0', \ 'let g:ale_use_global_executables = v:null', \ 'let g:ale_virtualtext_cursor = ''disabled''', \ 'let g:ale_warn_about_trailing_blank_lines = 1', diff --git a/test/test_virtualtext.vader b/test/test_virtualtext.vader index 8fc1ead5..1d34c036 100644 --- a/test/test_virtualtext.vader +++ b/test/test_virtualtext.vader @@ -4,6 +4,7 @@ Before: Save g:ale_virtualtext_delay Save g:ale_virtualtext_prefix Save b:ale_virtualtext_prefix + Save g:ale_use_neovim_diagnostics_api call ale#virtualtext#ResetDataForTests() @@ -36,6 +37,7 @@ Before: \ ], \ }, \} + let g:ale_use_neovim_diagnostics_api = 0 After: Restore @@ -177,3 +179,16 @@ Execute(We should set errors across all lines): \ map(prop_list(2), {_, v -> v.type}) endif endif + +Execute(We should not set cursor messages when Neovim diagnostics are enabled): + let g:ale_use_neovim_diagnostics_api = 1 + + if has('patch-9.0.0297') || has('nvim-0.8.0') + let g:ale_virtualtext_cursor = 'current' + call cursor(1, 1) + call ale#virtualtext#ShowCursorWarningWithDelay() + " Tick the timer. + sleep 1ms + + AssertEqual '', ale#virtualtext#GetLastMessageForTests() + endif -- cgit v1.2.3