"============================================================================= " checkers.vim --- SpaceVim checkers layer " Copyright (c) 2016-2023 Wang Shidong & Contributors " Author: Wang Shidong < wsdjeg@outlook.com > " URL: https://spacevim.org " License: GPLv3 "============================================================================= "" " @section checkers, layers-checkers " @parentsection layers " The `checkers` layer provides syntax lint feature. The default lint engine " is |neomake|, this can be changed by `lint_engine` option: " > " [options] " lint_engine = 'ale' " < " " @subsection options " " - `lint_on_the_fly`: Syntax checking on the fly feature, disabled by default. " - `lint_on_save`: Run syntax checking when saving a file. " - `show_cursor_error`: Enable/Disable displaying error below current line. " - `lint_exclude_filetype`: Set the filetypes which does not enable syntax " checking. " - `open_error_list`: Open the language checking windows. when set to 0, the " windows will not be opened automatically. Defaults to 2. if exists('s:show_cursor_error') finish endif if has('timers') let s:show_cursor_error = 1 else let s:show_cursor_error = 0 endif let s:lint_exclude_filetype = [] let s:lint_on_the_fly = 0 let s:lint_on_save = 1 let s:open_error_list = 0 let s:SIG = SpaceVim#api#import('vim#signatures') let s:STRING = SpaceVim#api#import('data#string') function! SpaceVim#layers#checkers#plugins() abort let plugins = [] if exists('g:spacevim_enable_neomake') || exists('g:spacevim_enable_ale') call SpaceVim#logger#warn('enable_neomake and enable_ale is duplecated') call SpaceVim#logger#warn('please read :h spacevim-options-lint_engine for more info!') endif if g:spacevim_lint_engine ==# 'neomake' call add(plugins, [g:_spacevim_root_dir . 'bundle/neomake', { \ 'merged' : 0, \ 'loadconf' : 1, \ 'on_cmd' : ['Neomake'], \ 'on_func' : ['neomake#GetCurrentErrorMsg', 'neomake#statusline#LoclistCounts'], \ 'loadconf_before' : 1}]) elseif g:spacevim_lint_engine ==# 'ale' call add(plugins, [g:_spacevim_root_dir . 'bundle/ale', {'merged' : 0, 'loadconf' : 1 , 'loadconf_before' : 1}]) elseif g:spacevim_lint_engine ==# 'syntastic' call add(plugins, [g:_spacevim_root_dir . 'bundle/syntastic', {'merged' : 0, 'loadconf' : 1 , 'loadconf_before' : 1}]) endif return plugins endfunction function! SpaceVim#layers#checkers#loadable() abort return 1 endfunction function! SpaceVim#layers#checkers#set_variable(var) abort let s:show_cursor_error = get(a:var, 'show_cursor_error', 1) let s:lint_on_the_fly = get(a:var, 'lint_on_the_fly', 0) let s:lint_on_save = get(a:var, 'lint_on_save', 1) let s:lint_exclude_filetype = get(a:var, 'lint_exclude_filetype', []) if s:show_cursor_error && !has('timers') call SpaceVim#logger#warn('show_cursor_error in checkers layer needs timers feature') let s:show_cursor_error = 0 endif let s:open_error_list = get(a:var, 'open_error_list', s:open_error_list) endfunction function! SpaceVim#layers#checkers#get_options() abort return ['show_cursor_error'] endfunction function! SpaceVim#layers#checkers#get_lint_option() abort return { \ 'lint_on_the_fly' : s:lint_on_the_fly, \ 'lint_on_save' : s:lint_on_save, \ } endfunction function! SpaceVim#layers#checkers#config() abort " neomake config if g:spacevim_lint_engine ==# 'neomake' let g:neomake_echo_current_error = get(g:, 'neomake_echo_current_error', !s:show_cursor_error) let g:neomake_cursormoved_delay = get(g:, 'neomake_cursormoved_delay', 300) let g:neomake_virtualtext_current_error = get(g:, 'neomake_virtualtext_current_error', !s:show_cursor_error) " exclude filetypes: for ft in s:lint_exclude_filetype let g:neomake_{ft}_enabled_makers = [] endfor let g:neomake_open_list = s:open_error_list elseif g:spacevim_lint_engine ==# 'ale' let g:ale_echo_delay = get(g:, 'ale_echo_delay', 300) endif call SpaceVim#mapping#space#def('nnoremap', ['e', 'c'], 'call call(' \ . string(s:_function('s:clear_errors')) . ', [])', \ 'clear-all-errors', 1) call SpaceVim#mapping#space#def('nnoremap', ['e', 'h'], '', 'describe-a-syntax-checker', 1) call SpaceVim#mapping#space#def('nnoremap', ['e', 'v'], '', 'verify-syntax-checker-setup', 1) call SpaceVim#mapping#space#def('nnoremap', ['e', 'n'], 'call call(' \ . string(s:_function('s:jump_to_next_error')) . ', [])', \ 'next-error', 1) call SpaceVim#mapping#space#def('nnoremap', ['e', 'l'], 'call call(' \ . string(s:_function('s:toggle_show_error')) . ', [0])', \ 'toggle-showing-the-error-list', 1) call SpaceVim#mapping#space#def('nnoremap', ['e', 'L'], 'call call(' \ . string(s:_function('s:toggle_show_error')) . ', [1])', \ 'toggle-showing-the-error-list', 1) call SpaceVim#mapping#space#def('nnoremap', ['e', 'p'], 'call call(' \ . string(s:_function('s:jump_to_previous_error')) . ', [])', \ 'previous-error', 1) call SpaceVim#mapping#space#def('nnoremap', ['e', 'N'], 'call call(' \ . string(s:_function('s:jump_to_previous_error')) . ', [])', \ 'previous-error', 1) call SpaceVim#mapping#space#def('nnoremap', ['e', 'v'], 'call call(' \ . string(s:_function('s:verify_syntax_setup')) . ', [])', \ 'verify-syntax-setup', 1) call SpaceVim#mapping#space#def('nnoremap', ['e', '.'], 'call call(' \ . string(s:_function('s:error_transient_state')) . ', [])', \ 'error-transient-state', 1) call SpaceVim#mapping#space#def('nnoremap', ['t', 's'], 'call SpaceVim#layers#core#statusline#toggle_mode("syntax-checking")', \ 'toggle-syntax-checker', 1) call SpaceVim#layers#core#statusline#register_mode( \ { \ 'key' : 'syntax-checking', \ 'func' : s:_function('s:toggle_syntax_checker'), \ } \ ) call SpaceVim#mapping#space#def('nnoremap', ['e', 'e'], 'call call(' \ . string(s:_function('s:explain_the_error')) . ', [])', \ 'explain-the-error', 1) augroup SpaceVim_layer_checker autocmd! if g:spacevim_lint_engine ==# 'neomake' if SpaceVim#layers#isLoaded('core#statusline') autocmd User NeomakeFinished nested \ let &l:statusline = SpaceVim#layers#core#statusline#get(1) endif if s:show_cursor_error " when move cursor, the error message will be shown below current line " after a delay autocmd CursorMoved * call neomake_cursor_move_delay() " when switch to Insert mode, stop timer and clear the signature if exists('##CmdLineEnter') autocmd InsertEnter,WinLeave * \ call neomake_signatures_clear() autocmd CmdLineEnter * \ call neomake_signatures_clear() else autocmd InsertEnter,WinLeave * call neomake_signatures_clear() endif endif elseif g:spacevim_lint_engine ==# 'ale' && SpaceVim#layers#isLoaded('core#statusline') autocmd User ALELintPost \ let &l:statusline = SpaceVim#layers#core#statusline#get(1) endif augroup END endfunction function! SpaceVim#layers#checkers#health() abort call SpaceVim#layers#checkers#plugins() call SpaceVim#layers#checkers#set_variable({}) call SpaceVim#layers#checkers#get_options() call SpaceVim#layers#checkers#config() return 1 endfunction function! s:neomake_cursor_move_delay() abort call s:neomake_signatures_clear() let s:neomake_cursormoved_timer = timer_start(g:neomake_cursormoved_delay, \ function('s:neomake_signatures_current_error')) endfunction function! s:toggle_show_error(...) abort if SpaceVim#lsp#buf_server_ready() call SpaceVim#lsp#diagnostic_set_loclist() else " if buf_server_ready return false, the language server loclist " should be cleared. if get(getloclist(0, {'title': 0}), 'title', '') ==# 'Language Server' \ || get(getloclist(0, {'title': 0}), 'title', '') ==# 'Diagnostics' call setloclist(0, [], 'r') endif let llist = getloclist(0, {'size' : 1, 'winid' : 1}) let qlist = getqflist({'size' : 1, 'winid' : 1}) if llist.size == 0 && qlist.size == 0 echohl WarningMsg echon 'There is no errors!' echohl None return endif if llist.winid > 0 lclose elseif qlist.winid > 0 cclose elseif llist.size > 0 botright lopen elseif qlist.size > 0 botright copen endif if a:1 == 1 wincmd w endif endif endfunction function! s:jump_to_next_error() abort if SpaceVim#lsp#buf_server_ready() call SpaceVim#lsp#diagnostic_goto_next() else try lnext catch try ll catch try cnext catch try cc catch echohl WarningMsg echon 'There is no errors!' echohl None endtry endtry endtry endtry endif endfunction function! s:jump_to_previous_error() abort if SpaceVim#lsp#buf_server_ready() call SpaceVim#lsp#diagnostic_goto_prev() else try lprevious catch try ll catch try cprevious catch try cc catch echohl WarningMsg echon 'There is no errors!' echohl None endtry endtry endtry endtry endif endfunction let s:last_echoed_error = '' function! s:neomake_signatures_current_error(...) abort call s:neomake_signatures_clear() try let message = neomake#GetCurrentErrorMsg() catch /^Vim\%((\a\+)\)\=:E117/ let message = '' endtry if empty(message) if exists('s:last_echoed_error') unlet s:last_echoed_error endif return endif if exists('s:last_echoed_error') \ && s:last_echoed_error == message return endif let s:last_echoed_error = message if len(line('.') + 1) > len(message) let message = s:STRING.fill(message, len(line('.') + 1)) endif call s:SIG.info(line('.') + 1, 1, message) endfunction function! s:neomake_signatures_clear() abort if exists('s:neomake_cursormoved_timer') && s:neomake_cursormoved_timer != 0 call timer_stop(s:neomake_cursormoved_timer) endif let s:last_echoed_error = '' call s:SIG.clear() endfunction function! s:verify_syntax_setup() abort if g:spacevim_lint_engine ==# 'neomake' NeomakeInfo elseif g:spacevim_lint_engine ==# 'ale' else endif endfunction function! s:toggle_syntax_checker() abort call SpaceVim#layers#core#statusline#toggle_section('syntax checking') if g:spacevim_lint_engine ==# 'neomake' verbose NeomakeToggle elseif g:spacevim_lint_engine ==# 'ale' ALEToggle endif return 1 endfunction function! s:explain_the_error() abort if g:spacevim_lint_engine ==# 'neomake' try let message = neomake#GetCurrentErrorMsg() catch /^Vim\%((\a\+)\)\=:E117/ let message = '' endtry elseif g:spacevim_lint_engine ==# 'ale' try "@bug wrong func to get ale error message let message = neomake#GetCurrentErrorMsg() catch /^Vim\%((\a\+)\)\=:E117/ let message = '' endtry endif if !empty(message) echo message else echo 'no error message at this point!' endif endfunction function! s:error_transient_state() abort if g:spacevim_lint_engine ==# 'neomake' let num_errors = neomake#statusline#LoclistCounts() elseif g:spacevim_lint_engine ==# 'ale' let counts = ale#statusline#Count(buffer_name('%')) let num_errors = counts.error + counts.warning + counts.style_error \ + counts.style_warning else let num_errors = 0 endif if empty(num_errors) echo 'no buffers contain error message locations' return endif let state = SpaceVim#api#import('transient_state') call state.set_title('Error Transient State') call state.defind_keys( \ { \ 'layout' : 'vertical split', \ 'left' : [ \ { \ 'key' : 'n', \ 'desc' : 'next error', \ 'func' : '', \ 'cmd' : 'try | lnext | catch | endtry', \ 'exit' : 0, \ }, \ ], \ 'right' : [ \ { \ 'key' : ['p', 'N'], \ 'desc' : 'previous error', \ 'func' : '', \ 'cmd' : 'try | lprevious | catch | endtry', \ 'exit' : 0, \ }, \ { \ 'key' : 'q', \ 'desc' : 'quit', \ 'func' : '', \ 'cmd' : '', \ 'exit' : 1, \ }, \ ], \ } \ ) call state.open() endfunction " function() wrapper if v:version > 703 || v:version == 703 && has('patch1170') function! s:_function(fstr) abort return function(a:fstr) endfunction else function! s:_SID() abort return matchstr(expand(''), '\zs\d\+\ze__SID$') endfunction let s:_s = '' . s:_SID() . '_' function! s:_function(fstr) abort return function(substitute(a:fstr, 's:', s:_s, 'g')) endfunction endif " TODO clear errors function! s:clear_errors() abort if SpaceVim#lsp#buf_server_ready() call SpaceVim#lsp#diagnostic_clear() if get(getloclist(0, {'title': 0}), 'title', '') ==# 'Language Server' call setloclist(0, [], 'r') endif else sign unplace * endif endfunction