mirror of
https://github.com/neovim/neovim.git
synced 2024-12-20 11:15:14 -07:00
4d2373f5f6
- If Nvim was just started, don't create a new tab. - Name the buffer "health://". - Use "help" syntax instead of "markdown". It fits better, and eliminates various workarounds. - Simplfy formatting, avoid visual noise. - Don't print a "INFO" status, it is noisy. - Drop the ":" after statuses, they are already UPPERCASE and highlighted.
208 lines
6.8 KiB
VimL
208 lines
6.8 KiB
VimL
" Runs the specified healthchecks.
|
|
" Runs all discovered healthchecks if a:plugin_names is empty.
|
|
function! health#check(plugin_names) abort
|
|
let healthchecks = empty(a:plugin_names)
|
|
\ ? s:discover_healthchecks()
|
|
\ : s:get_healthcheck(a:plugin_names)
|
|
|
|
" Create buffer and open in a tab, unless this is the default buffer when Nvim starts.
|
|
let emptybuf = (bufnr('$') == 1 && empty(getline(1)) && 1 == line('$'))
|
|
execute (emptybuf ? 'buffer' : 'tab sbuffer') nvim_create_buf(v:true, v:true)
|
|
if bufexists('health://')
|
|
bwipe health://
|
|
endif
|
|
file health://
|
|
setfiletype checkhealth
|
|
|
|
if empty(healthchecks)
|
|
call setline(1, 'ERROR: No healthchecks found.')
|
|
else
|
|
redraw|echo 'Running healthchecks...'
|
|
for name in sort(keys(healthchecks))
|
|
let [func, type] = healthchecks[name]
|
|
let s:output = []
|
|
try
|
|
if func == ''
|
|
throw 'healthcheck_not_found'
|
|
endif
|
|
eval type == 'v' ? call(func, []) : luaeval(func)
|
|
" in the event the healthcheck doesn't return anything
|
|
" (the plugin author should avoid this possibility)
|
|
if len(s:output) == 0
|
|
throw 'healthcheck_no_return_value'
|
|
endif
|
|
catch
|
|
let s:output = [] " Clear the output
|
|
if v:exception =~# 'healthcheck_not_found'
|
|
call health#report_error('No healthcheck found for "'.name.'" plugin.')
|
|
elseif v:exception =~# 'healthcheck_no_return_value'
|
|
call health#report_error('The healthcheck report for "'.name.'" plugin is empty.')
|
|
else
|
|
call health#report_error(printf(
|
|
\ "Failed to run healthcheck for \"%s\" plugin. Exception:\n%s\n%s",
|
|
\ name, v:throwpoint, v:exception))
|
|
endif
|
|
endtry
|
|
let header = [repeat('=', 78), name .. ': ' .. func, '']
|
|
" remove empty line after header from report_start
|
|
let s:output = s:output[0] == '' ? s:output[1:] : s:output
|
|
let s:output = header + s:output + ['']
|
|
call append('$', s:output)
|
|
redraw
|
|
endfor
|
|
endif
|
|
|
|
" Clear the 'Running healthchecks...' message.
|
|
redraw|echo ''
|
|
endfunction
|
|
|
|
function! s:collect_output(output)
|
|
let s:output += split(a:output, "\n", 1)
|
|
endfunction
|
|
|
|
" Starts a new report.
|
|
function! health#report_start(name) abort
|
|
call s:collect_output(printf("\n%s ~", a:name))
|
|
endfunction
|
|
|
|
" Indents lines *except* line 1 of a string if it contains newlines.
|
|
function! s:indent_after_line1(s, columns) abort
|
|
let lines = split(a:s, "\n", 0)
|
|
if len(lines) < 2 " We do not indent line 1, so nothing to do.
|
|
return a:s
|
|
endif
|
|
for i in range(1, len(lines)-1) " Indent lines after the first.
|
|
let lines[i] = substitute(lines[i], '^\s*', repeat(' ', a:columns), 'g')
|
|
endfor
|
|
return join(lines, "\n")
|
|
endfunction
|
|
|
|
" Changes ':h clipboard' to ':help |clipboard|'.
|
|
function! s:help_to_link(s) abort
|
|
return substitute(a:s, '\v:h%[elp] ([^|][^"\r\n ]+)', ':help |\1|', 'g')
|
|
endfunction
|
|
|
|
" Format a message for a specific report item.
|
|
" a:1: Optional advice (string or list)
|
|
function! s:format_report_message(status, msg, ...) abort " {{{
|
|
let output = '- ' .. a:status .. (empty(a:status) ? '' : ' ') .. s:indent_after_line1(a:msg, 2)
|
|
|
|
" Optional parameters
|
|
if a:0 > 0
|
|
let advice = type(a:1) == type('') ? [a:1] : a:1
|
|
if type(advice) != type([])
|
|
throw 'a:1: expected String or List'
|
|
endif
|
|
|
|
" Report each suggestion
|
|
if !empty(advice)
|
|
let output .= "\n - ADVICE:"
|
|
for suggestion in advice
|
|
let output .= "\n - " . s:indent_after_line1(suggestion, 6)
|
|
endfor
|
|
endif
|
|
endif
|
|
|
|
return s:help_to_link(output)
|
|
endfunction " }}}
|
|
|
|
" Reports a message as a listitem in the current section.
|
|
function! health#report_info(msg) abort " {{{
|
|
call s:collect_output(s:format_report_message('', a:msg))
|
|
endfunction " }}}
|
|
|
|
" Reports a successful healthcheck.
|
|
function! health#report_ok(msg) abort " {{{
|
|
call s:collect_output(s:format_report_message('OK', a:msg))
|
|
endfunction " }}}
|
|
|
|
" Reports a health warning.
|
|
" a:1: Optional advice (string or list)
|
|
function! health#report_warn(msg, ...) abort " {{{
|
|
if a:0 > 0
|
|
call s:collect_output(s:format_report_message('WARNING', a:msg, a:1))
|
|
else
|
|
call s:collect_output(s:format_report_message('WARNING', a:msg))
|
|
endif
|
|
endfunction " }}}
|
|
|
|
" Reports a failed healthcheck.
|
|
" a:1: Optional advice (string or list)
|
|
function! health#report_error(msg, ...) abort " {{{
|
|
if a:0 > 0
|
|
call s:collect_output(s:format_report_message('ERROR', a:msg, a:1))
|
|
else
|
|
call s:collect_output(s:format_report_message('ERROR', a:msg))
|
|
endif
|
|
endfunction " }}}
|
|
|
|
" From a path return a list [{name}, {func}, {type}] representing a healthcheck
|
|
function! s:filepath_to_healthcheck(path) abort
|
|
if a:path =~# 'vim$'
|
|
let name = matchstr(a:path, '\zs[^\/]*\ze\.vim$')
|
|
let func = 'health#'.name.'#check'
|
|
let type = 'v'
|
|
else
|
|
let base_path = substitute(a:path,
|
|
\ '.*lua[\/]\(.\{-}\)[\/]health\([\/]init\)\?\.lua$',
|
|
\ '\1', '')
|
|
let name = substitute(base_path, '[\/]', '.', 'g')
|
|
let func = 'require("'.name.'.health").check()'
|
|
let type = 'l'
|
|
endif
|
|
return [name, func, type]
|
|
endfunction
|
|
|
|
function! s:discover_healthchecks() abort
|
|
return s:get_healthcheck('*')
|
|
endfunction
|
|
|
|
" Returns Dictionary {name: [func, type], ..} representing healthchecks
|
|
function! s:get_healthcheck(plugin_names) abort
|
|
let health_list = s:get_healthcheck_list(a:plugin_names)
|
|
let healthchecks = {}
|
|
for c in health_list
|
|
let normalized_name = substitute(c[0], '-', '_', 'g')
|
|
let existent = get(healthchecks, normalized_name, [])
|
|
" Prefer Lua over vim entries
|
|
if existent != [] && existent[2] == 'l'
|
|
continue
|
|
else
|
|
let healthchecks[normalized_name] = c
|
|
endif
|
|
endfor
|
|
let output = {}
|
|
for v in values(healthchecks)
|
|
let output[v[0]] = v[1:]
|
|
endfor
|
|
try
|
|
" vim.health is not a healthcheck, skip it
|
|
call remove(output, 'vim')
|
|
catch
|
|
endtry
|
|
return output
|
|
endfunction
|
|
|
|
" Returns list of lists [ [{name}, {func}, {type}] ] representing healthchecks
|
|
function! s:get_healthcheck_list(plugin_names) abort
|
|
let healthchecks = []
|
|
let plugin_names = type('') == type(a:plugin_names)
|
|
\ ? split(a:plugin_names, ' ', v:false)
|
|
\ : a:plugin_names
|
|
for p in plugin_names
|
|
" support vim/lsp/health{/init/}.lua as :checkhealth vim.lsp
|
|
let p = substitute(p, '\.', '/', 'g')
|
|
let p = substitute(p, '*$', '**', 'g') " find all submodule e.g vim*
|
|
let paths = nvim_get_runtime_file('autoload/health/'.p.'.vim', v:true)
|
|
\ + nvim_get_runtime_file('lua/**/'.p.'/health/init.lua', v:true)
|
|
\ + nvim_get_runtime_file('lua/**/'.p.'/health.lua', v:true)
|
|
if len(paths) == 0
|
|
let healthchecks += [[p, '', '']] " healthcheck not found
|
|
else
|
|
let healthchecks += map(uniq(sort(paths)),
|
|
\'<SID>filepath_to_healthcheck(v:val)')
|
|
end
|
|
endfor
|
|
return healthchecks
|
|
endfunction
|