refactor: deprecate checkhealth functions

The following functions are deprecated and will be removed in
Nvim v0.11:

- health#report_start()
- health#report_info()
- health#report_ok()
- health#report_warn()
- health#report_error()
- vim.health.report_start()
- vim.health.report_info()
- vim.health.report_ok()
- vim.health.report_warn()
- vim.health.report_error()

Users should instead use these:

- vim.health.start()
- vim.health.info()
- vim.health.ok()
- vim.health.warn()
- vim.health.error()
This commit is contained in:
dundargoc
2023-04-15 23:40:48 +02:00
committed by GitHub
parent c8667c8756
commit c08b030761
22 changed files with 418 additions and 442 deletions

View File

@@ -1,207 +1,38 @@
" Runs the specified healthchecks. function! s:deprecate(type) abort
" Runs all discovered healthchecks if a:plugin_names is empty. let deprecate = v:lua.vim.deprecate('health#report_' . a:type, 'vim.health.' . a:type, '0.11')
function! health#check(plugin_names) abort redraw | echo 'Running healthchecks...'
let healthchecks = empty(a:plugin_names) call v:lua.vim.health.warn(deprecate)
\ ? 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 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 function! health#report_start(name) abort
call s:collect_output(printf("\n%s ~", a:name)) call v:lua.vim.health.start(a:name)
call s:deprecate('start')
endfunction endfunction
" Indents lines *except* line 1 of a string if it contains newlines. function! health#report_info(msg) abort
function! s:indent_after_line1(s, columns) abort call v:lua.vim.health.info(a:msg)
let lines = split(a:s, "\n", 0) call s:deprecate('info')
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 endfunction
" Changes ':h clipboard' to ':help |clipboard|'. function! health#report_ok(msg) abort
function! s:help_to_link(s) abort call v:lua.vim.health.ok(a:msg)
return substitute(a:s, '\v:h%[elp] ([^|][^"\r\n ]+)', ':help |\1|', 'g') call s:deprecate('ok')
endfunction endfunction
" Format a message for a specific report item. function! health#report_warn(msg, ...) abort
" 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 if a:0 > 0
let advice = type(a:1) == type('') ? [a:1] : a:1 call v:lua.vim.health.warn(a:msg, a:1)
if type(advice) != type([]) else
throw 'a:1: expected String or List' call v:lua.vim.health.warn(a:msg)
endif endif
call s:deprecate('warn')
endfunction
" Report each suggestion function! health#report_error(msg, ...) abort
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 if a:0 > 0
call s:collect_output(s:format_report_message('WARNING', a:msg, a:1)) call v:lua.vim.health.error(a:msg, a:1)
else else
call s:collect_output(s:format_report_message('WARNING', a:msg)) call v:lua.vim.health.error(a:msg)
endif endif
endfunction " }}} call s:deprecate('error')
" 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 endfunction

View File

@@ -54,11 +54,11 @@ FUNCTIONS
- *buffer_name()* Obsolete name for |bufname()|. - *buffer_name()* Obsolete name for |bufname()|.
- *buffer_number()* Obsolete name for |bufnr()|. - *buffer_number()* Obsolete name for |bufnr()|.
- *file_readable()* Obsolete name for |filereadable()|. - *file_readable()* Obsolete name for |filereadable()|.
- *health#report_error* Use Lua |vim.health.report_error()| instead. - *health#report_error* *vim.health.report_error()* Use |vim.health.error()| instead.
- *health#report_info* Use Lua |vim.health.report_info()| instead. - *health#report_info* *vim.health.report_info()* Use |vim.health.info()| instead.
- *health#report_ok* Use Lua |vim.health.report_ok()| instead. - *health#report_ok* *vim.health.report_ok()* Use |vim.health.ok()| instead.
- *health#report_start* Use Lua |vim.health.report_start()| instead. - *health#report_start* *vim.health.report_start()* Use |vim.health.start()| instead.
- *health#report_warn* Use Lua |vim.health.report_warn()| instead. - *health#report_warn* *vim.health.report_warn()* Use |vim.health.warn()| instead.
- *highlight_exists()* Obsolete name for |hlexists()|. - *highlight_exists()* Obsolete name for |hlexists()|.
- *highlightID()* Obsolete name for |hlID()|. - *highlightID()* Obsolete name for |hlID()|.
- *inputdialog()* Use |input()| instead. - *inputdialog()* Use |input()| instead.

View File

@@ -62,7 +62,11 @@ DEPRECATIONS *news-deprecations*
The following functions are now deprecated and will be removed in the next The following functions are now deprecated and will be removed in the next
release. release.
... Checkhealth functions:
- |health#report_error|, |vim.health.report_error()| Use Lua |vim.health.error()| instead.
- |health#report_info|, |vim.health.report_info()| Use Lua |vim.health.info()| instead.
- |health#report_ok|, |vim.health.report_ok()| Use Lua |vim.health.ok()| instead.
- |health#report_start|, |vim.health.report_start()| Use Lua |vim.health.start()| instead.
- |health#report_warn|, |vim.health.report_warn()| Use Lua |vim.health.warn()| instead.
vim:tw=78:ts=8:sw=2:et:ft=help:norl: vim:tw=78:ts=8:sw=2:et:ft=help:norl:

View File

@@ -52,22 +52,22 @@ Functions *health-functions* *vim.health*
The Lua "health" module can be used to create new healthchecks. To get started The Lua "health" module can be used to create new healthchecks. To get started
see |health-dev|. see |health-dev|.
vim.health.report_start({name}) *vim.health.report_start()* vim.health.start({name}) *vim.health.start()*
Starts a new report. Most plugins should call this only once, but if Starts a new report. Most plugins should call this only once, but if
you want different sections to appear in your report, call this once you want different sections to appear in your report, call this once
per section. per section.
vim.health.report_info({msg}) *vim.health.report_info()* vim.health.info({msg}) *vim.health.info()*
Reports an informational message. Reports an informational message.
vim.health.report_ok({msg}) *vim.health.report_ok()* vim.health.ok({msg}) *vim.health.ok()*
Reports a "success" message. Reports a "success" message.
vim.health.report_warn({msg} [, {advice}]) *vim.health.report_warn()* vim.health.warn({msg} [, {advice}]) *vim.health.warn()*
Reports a warning. {advice} is an optional list of suggestions to Reports a warning. {advice} is an optional list of suggestions to
present to the user. present to the user.
vim.health.report_error({msg} [, {advice}]) *vim.health.report_error()* vim.health.error({msg} [, {advice}]) *vim.health.error()*
Reports an error. {advice} is an optional list of suggestions to Reports an error. {advice} is an optional list of suggestions to
present to the user. present to the user.
@@ -105,12 +105,12 @@ with your plugin name: >
local M = {} local M = {}
M.check = function() M.check = function()
vim.health.report_start("foo report") vim.health.start("foo report")
-- make sure setup function parameters are ok -- make sure setup function parameters are ok
if check_setup() then if check_setup() then
vim.health.report_ok("Setup is correct") vim.health.ok("Setup is correct")
else else
vim.health.report_error("Setup is incorrect") vim.health.error("Setup is incorrect")
end end
-- do some more checking -- do some more checking
-- ... -- ...

View File

@@ -20,7 +20,7 @@ end
local suggest_faq = 'https://github.com/neovim/neovim/wiki/Building-Neovim#optimized-builds' local suggest_faq = 'https://github.com/neovim/neovim/wiki/Building-Neovim#optimized-builds'
local function check_runtime() local function check_runtime()
health.report_start('Runtime') health.start('Runtime')
-- Files from an old installation. -- Files from an old installation.
local bad_files = { local bad_files = {
['plugin/man.vim'] = false, ['plugin/man.vim'] = false,
@@ -37,10 +37,10 @@ local function check_runtime()
end end
local ok = (bad_files_msg == '') local ok = (bad_files_msg == '')
local info = ok and health.report_ok or health.report_info local info = ok and health.ok or health.info
info(string.format('$VIMRUNTIME: %s', vim.env.VIMRUNTIME)) info(string.format('$VIMRUNTIME: %s', vim.env.VIMRUNTIME))
if not ok then if not ok then
health.report_error( health.error(
string.format( string.format(
'$VIMRUNTIME has files from an old installation (this can cause weird behavior):\n%s', '$VIMRUNTIME has files from an old installation (this can cause weird behavior):\n%s',
bad_files_msg bad_files_msg
@@ -51,7 +51,7 @@ local function check_runtime()
end end
local function check_config() local function check_config()
health.report_start('Configuration') health.start('Configuration')
local ok = true local ok = true
local vimrc = ( local vimrc = (
@@ -60,7 +60,7 @@ local function check_config()
if not filereadable(vimrc) then if not filereadable(vimrc) then
ok = false ok = false
local has_vim = filereadable(vim.fn.expand('~/.vimrc')) local has_vim = filereadable(vim.fn.expand('~/.vimrc'))
health.report_warn( health.warn(
(-1 == vim.fn.getfsize(vimrc) and 'Missing' or 'Unreadable') .. ' user config file: ' .. vimrc, (-1 == vim.fn.getfsize(vimrc) and 'Missing' or 'Unreadable') .. ' user config file: ' .. vimrc,
{ has_vim and ':help nvim-from-vim' or ':help init.vim' } { has_vim and ':help nvim-from-vim' or ':help init.vim' }
) )
@@ -69,12 +69,12 @@ local function check_config()
-- If $VIM is empty we don't care. Else make sure it is valid. -- If $VIM is empty we don't care. Else make sure it is valid.
if not empty(vim.env.VIM) and not filereadable(vim.env.VIM .. '/runtime/doc/nvim.txt') then if not empty(vim.env.VIM) and not filereadable(vim.env.VIM .. '/runtime/doc/nvim.txt') then
ok = false ok = false
health.report_error('$VIM is invalid: ' .. vim.env.VIM) health.error('$VIM is invalid: ' .. vim.env.VIM)
end end
if vim.env.NVIM_TUI_ENABLE_CURSOR_SHAPE then if vim.env.NVIM_TUI_ENABLE_CURSOR_SHAPE then
ok = false ok = false
health.report_warn('$NVIM_TUI_ENABLE_CURSOR_SHAPE is ignored in Nvim 0.2+', { health.warn('$NVIM_TUI_ENABLE_CURSOR_SHAPE is ignored in Nvim 0.2+', {
"Use the 'guicursor' option to configure cursor shape. :help 'guicursor'", "Use the 'guicursor' option to configure cursor shape. :help 'guicursor'",
'https://github.com/neovim/neovim/wiki/Following-HEAD#20170402', 'https://github.com/neovim/neovim/wiki/Following-HEAD#20170402',
}) })
@@ -82,7 +82,7 @@ local function check_config()
if vim.v.ctype == 'C' then if vim.v.ctype == 'C' then
ok = false ok = false
health.report_error( health.error(
'Locale does not support UTF-8. Unicode characters may not display correctly.' 'Locale does not support UTF-8. Unicode characters may not display correctly.'
.. ('\n$LANG=%s $LC_ALL=%s $LC_CTYPE=%s'):format( .. ('\n$LANG=%s $LC_ALL=%s $LC_CTYPE=%s'):format(
vim.env.LANG, vim.env.LANG,
@@ -99,7 +99,7 @@ local function check_config()
if vim.o.paste == 1 then if vim.o.paste == 1 then
ok = false ok = false
health.report_error( health.error(
"'paste' is enabled. This option is only for pasting text.\nIt should not be set in your config.", "'paste' is enabled. This option is only for pasting text.\nIt should not be set in your config.",
{ {
'Remove `set paste` from your init.vim, if applicable.', 'Remove `set paste` from your init.vim, if applicable.',
@@ -132,7 +132,7 @@ local function check_config()
or (not empty(shadafile) and (not filereadable(shadafile) or not filewritable(shadafile))) or (not empty(shadafile) and (not filereadable(shadafile) or not filewritable(shadafile)))
then then
ok = false ok = false
health.report_error( health.error(
'shada file is not ' 'shada file is not '
.. ((not writeable or filereadable(shadafile)) and 'writeable' or 'readable') .. ((not writeable or filereadable(shadafile)) and 'writeable' or 'readable')
.. ':\n' .. ':\n'
@@ -141,22 +141,22 @@ local function check_config()
end end
if ok then if ok then
health.report_ok('no issues found') health.ok('no issues found')
end end
end end
local function check_performance() local function check_performance()
health.report_start('Performance') health.start('Performance')
-- Check buildtype -- Check buildtype
local buildtype = vim.fn.matchstr(vim.fn.execute('version'), [[\v\cbuild type:?\s*[^\n\r\t ]+]]) local buildtype = vim.fn.matchstr(vim.fn.execute('version'), [[\v\cbuild type:?\s*[^\n\r\t ]+]])
if empty(buildtype) then if empty(buildtype) then
health.report_error('failed to get build type from :version') health.error('failed to get build type from :version')
elseif vim.regex([[\v(MinSizeRel|Release|RelWithDebInfo)]]):match_str(buildtype) then elseif vim.regex([[\v(MinSizeRel|Release|RelWithDebInfo)]]):match_str(buildtype) then
health.report_ok(buildtype) health.ok(buildtype)
else else
health.report_info(buildtype) health.info(buildtype)
health.report_warn('Non-optimized debug build. Nvim will be slower.', { health.warn('Non-optimized debug build. Nvim will be slower.', {
'Install a different Nvim package, or rebuild with `CMAKE_BUILD_TYPE=RelWithDebInfo`.', 'Install a different Nvim package, or rebuild with `CMAKE_BUILD_TYPE=RelWithDebInfo`.',
suggest_faq, suggest_faq,
}) })
@@ -168,7 +168,7 @@ local function check_performance()
vim.fn.system('echo') vim.fn.system('echo')
local elapsed_time = vim.fn.reltimefloat(vim.fn.reltime(start_time)) local elapsed_time = vim.fn.reltimefloat(vim.fn.reltime(start_time))
if elapsed_time > slow_cmd_time then if elapsed_time > slow_cmd_time then
health.report_warn( health.warn(
'Slow shell invocation (took ' .. vim.fn.printf('%.2f', elapsed_time) .. ' seconds).' 'Slow shell invocation (took ' .. vim.fn.printf('%.2f', elapsed_time) .. ' seconds).'
) )
end end
@@ -176,7 +176,7 @@ end
-- Load the remote plugin manifest file and check for unregistered plugins -- Load the remote plugin manifest file and check for unregistered plugins
local function check_rplugin_manifest() local function check_rplugin_manifest()
health.report_start('Remote Plugins') health.start('Remote Plugins')
local existing_rplugins = {} local existing_rplugins = {}
for _, item in ipairs(vim.fn['remote#host#PluginsForHost']('python')) do for _, item in ipairs(vim.fn['remote#host#PluginsForHost']('python')) do
@@ -218,7 +218,7 @@ local function check_rplugin_manifest()
require_update = true require_update = true
end end
health.report_warn(msg) health.warn(msg)
end end
break break
@@ -231,9 +231,9 @@ local function check_rplugin_manifest()
end end
if require_update then if require_update then
health.report_warn('Out of date', { 'Run `:UpdateRemotePlugins`' }) health.warn('Out of date', { 'Run `:UpdateRemotePlugins`' })
else else
health.report_ok('Up to date') health.ok('Up to date')
end end
end end
@@ -247,21 +247,21 @@ local function check_tmux()
local out = vim.fn.system(vim.fn.split(cmd)) local out = vim.fn.system(vim.fn.split(cmd))
local val = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g') local val = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g')
if shell_error() then if shell_error() then
health.report_error('command failed: ' .. cmd .. '\n' .. out) health.error('command failed: ' .. cmd .. '\n' .. out)
return 'error' return 'error'
elseif empty(val) then elseif empty(val) then
cmd = 'tmux show-option -qvgs ' .. option -- try session scope cmd = 'tmux show-option -qvgs ' .. option -- try session scope
out = vim.fn.system(vim.fn.split(cmd)) out = vim.fn.system(vim.fn.split(cmd))
val = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g') val = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g')
if shell_error() then if shell_error() then
health.report_error('command failed: ' .. cmd .. '\n' .. out) health.error('command failed: ' .. cmd .. '\n' .. out)
return 'error' return 'error'
end end
end end
return val return val
end end
health.report_start('tmux') health.start('tmux')
-- check escape-time -- check escape-time
local suggestions = local suggestions =
@@ -269,14 +269,11 @@ local function check_tmux()
local tmux_esc_time = get_tmux_option('escape-time') local tmux_esc_time = get_tmux_option('escape-time')
if tmux_esc_time ~= 'error' then if tmux_esc_time ~= 'error' then
if empty(tmux_esc_time) then if empty(tmux_esc_time) then
health.report_error('`escape-time` is not set', suggestions) health.error('`escape-time` is not set', suggestions)
elseif tonumber(tmux_esc_time) > 300 then elseif tonumber(tmux_esc_time) > 300 then
health.report_error( health.error('`escape-time` (' .. tmux_esc_time .. ') is higher than 300ms', suggestions)
'`escape-time` (' .. tmux_esc_time .. ') is higher than 300ms',
suggestions
)
else else
health.report_ok('escape-time: ' .. tmux_esc_time) health.ok('escape-time: ' .. tmux_esc_time)
end end
end end
@@ -284,17 +281,17 @@ local function check_tmux()
local tmux_focus_events = get_tmux_option('focus-events') local tmux_focus_events = get_tmux_option('focus-events')
if tmux_focus_events ~= 'error' then if tmux_focus_events ~= 'error' then
if empty(tmux_focus_events) or tmux_focus_events ~= 'on' then if empty(tmux_focus_events) or tmux_focus_events ~= 'on' then
health.report_warn( health.warn(
"`focus-events` is not enabled. |'autoread'| may not work.", "`focus-events` is not enabled. |'autoread'| may not work.",
{ '(tmux 1.9+ only) Set `focus-events` in ~/.tmux.conf:\nset-option -g focus-events on' } { '(tmux 1.9+ only) Set `focus-events` in ~/.tmux.conf:\nset-option -g focus-events on' }
) )
else else
health.report_ok('focus-events: ' .. tmux_focus_events) health.ok('focus-events: ' .. tmux_focus_events)
end end
end end
-- check default-terminal and $TERM -- check default-terminal and $TERM
health.report_info('$TERM: ' .. vim.env.TERM) health.info('$TERM: ' .. vim.env.TERM)
local cmd = 'tmux show-option -qvg default-terminal' local cmd = 'tmux show-option -qvg default-terminal'
local out = vim.fn.system(vim.fn.split(cmd)) local out = vim.fn.system(vim.fn.split(cmd))
local tmux_default_term = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g') local tmux_default_term = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g')
@@ -305,15 +302,15 @@ local function check_tmux()
end end
if shell_error() then if shell_error() then
health.report_error('command failed: ' .. cmd .. '\n' .. out) health.error('command failed: ' .. cmd .. '\n' .. out)
elseif tmux_default_term ~= vim.env.TERM then elseif tmux_default_term ~= vim.env.TERM then
health.report_info('default-terminal: ' .. tmux_default_term) health.info('default-terminal: ' .. tmux_default_term)
health.report_error( health.error(
'$TERM differs from the tmux `default-terminal` setting. Colors might look wrong.', '$TERM differs from the tmux `default-terminal` setting. Colors might look wrong.',
{ '$TERM may have been set by some rc (.bashrc, .zshrc, ...).' } { '$TERM may have been set by some rc (.bashrc, .zshrc, ...).' }
) )
elseif not vim.regex([[\v(tmux-256color|screen-256color)]]):match_str(vim.env.TERM) then elseif not vim.regex([[\v(tmux-256color|screen-256color)]]):match_str(vim.env.TERM) then
health.report_error( health.error(
'$TERM should be "screen-256color" or "tmux-256color" in tmux. Colors might look wrong.', '$TERM should be "screen-256color" or "tmux-256color" in tmux. Colors might look wrong.',
{ {
'Set default-terminal in ~/.tmux.conf:\nset-option -g default-terminal "screen-256color"', 'Set default-terminal in ~/.tmux.conf:\nset-option -g default-terminal "screen-256color"',
@@ -333,7 +330,7 @@ local function check_tmux()
has_rgb = info:find(' Tc: (flag) true', 1, true) or info:find(' RGB: (flag) true', 1, true) has_rgb = info:find(' Tc: (flag) true', 1, true) or info:find(' RGB: (flag) true', 1, true)
end end
if not has_rgb then if not has_rgb then
health.report_warn( health.warn(
"Neither Tc nor RGB capability set. True colors are disabled. |'termguicolors'| won't work properly.", "Neither Tc nor RGB capability set. True colors are disabled. |'termguicolors'| won't work properly.",
{ {
"Put this in your ~/.tmux.conf and replace XXX by your $TERM outside of tmux:\nset-option -sa terminal-features ',XXX:RGB'", "Put this in your ~/.tmux.conf and replace XXX by your $TERM outside of tmux:\nset-option -sa terminal-features ',XXX:RGB'",
@@ -349,7 +346,7 @@ local function check_terminal()
return return
end end
health.report_start('terminal') health.start('terminal')
local cmd = 'infocmp -L' local cmd = 'infocmp -L'
local out = vim.fn.system(vim.fn.split(cmd)) local out = vim.fn.system(vim.fn.split(cmd))
local kbs_entry = vim.fn.matchstr(out, 'key_backspace=[^,[:space:]]*') local kbs_entry = vim.fn.matchstr(out, 'key_backspace=[^,[:space:]]*')
@@ -367,16 +364,16 @@ local function check_terminal()
) )
) )
then then
health.report_error('command failed: ' .. cmd .. '\n' .. out) health.error('command failed: ' .. cmd .. '\n' .. out)
else else
health.report_info( health.info(
vim.fn.printf( vim.fn.printf(
'key_backspace (kbs) terminfo entry: `%s`', 'key_backspace (kbs) terminfo entry: `%s`',
(empty(kbs_entry) and '? (not found)' or kbs_entry) (empty(kbs_entry) and '? (not found)' or kbs_entry)
) )
) )
health.report_info( health.info(
vim.fn.printf( vim.fn.printf(
'key_dc (kdch1) terminfo entry: `%s`', 'key_dc (kdch1) terminfo entry: `%s`',
(empty(kbs_entry) and '? (not found)' or kdch1_entry) (empty(kbs_entry) and '? (not found)' or kdch1_entry)
@@ -392,7 +389,7 @@ local function check_terminal()
'SSH_TTY', 'SSH_TTY',
}) do }) do
if vim.env[env_var] then if vim.env[env_var] then
health.report_info(vim.fn.printf('$%s="%s"', env_var, vim.env[env_var])) health.info(vim.fn.printf('$%s="%s"', env_var, vim.env[env_var]))
end end
end end
end end

View File

@@ -1,10 +1,10 @@
local M = {} local M = {}
local start = vim.health.report_start local start = vim.health.start
local ok = vim.health.report_ok local ok = vim.health.ok
local info = vim.health.report_info local info = vim.health.info
local warn = vim.health.report_warn local warn = vim.health.warn
local error = vim.health.report_error local error = vim.health.error
local iswin = vim.loop.os_uname().sysname == 'Windows_NT' local iswin = vim.loop.os_uname().sysname == 'Windows_NT'
local shell_error_code = 0 local shell_error_code = 0

View File

@@ -1,23 +1,196 @@
local M = {} local M = {}
function M.report_start(msg) local s_output = {}
vim.fn['health#report_start'](msg)
-- From a path return a list [{name}, {func}, {type}] representing a healthcheck
local function filepath_to_healthcheck(path)
path = vim.fs.normalize(path)
local name
local func
local filetype
if path:find('vim$') then
name = vim.fs.basename(path):gsub('%.vim$', '')
func = 'health#' .. name .. '#check'
filetype = 'v'
else
local subpath = path:gsub('.*lua/', '')
if vim.fs.basename(subpath) == 'health.lua' then
-- */health.lua
name = vim.fs.dirname(subpath)
else
-- */health/init.lua
name = vim.fs.dirname(vim.fs.dirname(subpath))
end
name = name:gsub('/', '.')
func = 'require("' .. name .. '.health").check()'
filetype = 'l'
end
return { name, func, filetype }
end end
-- Returns { {name, func, type}, ... } representing healthchecks
local function get_healthcheck_list(plugin_names)
local healthchecks = {}
plugin_names = vim.split(plugin_names, ' ')
for _, p in pairs(plugin_names) do
-- support vim/lsp/health{/init/}.lua as :checkhealth vim.lsp
p = p:gsub('%.', '/')
p = p:gsub('*', '**')
local paths = vim.api.nvim_get_runtime_file('autoload/health/' .. p .. '.vim', true)
vim.list_extend(
paths,
vim.api.nvim_get_runtime_file('lua/**/' .. p .. '/health/init.lua', true)
)
vim.list_extend(paths, vim.api.nvim_get_runtime_file('lua/**/' .. p .. '/health.lua', true))
if vim.tbl_count(paths) == 0 then
healthchecks[#healthchecks + 1] = { p, '', '' } -- healthcheck not found
else
local unique_paths = {}
for _, v in pairs(paths) do
unique_paths[v] = true
end
paths = {}
for k, _ in pairs(unique_paths) do
paths[#paths + 1] = k
end
for _, v in ipairs(paths) do
healthchecks[#healthchecks + 1] = filepath_to_healthcheck(v)
end
end
end
return healthchecks
end
-- Returns {name: [func, type], ..} representing healthchecks
local function get_healthcheck(plugin_names)
local health_list = get_healthcheck_list(plugin_names)
local healthchecks = {}
for _, c in pairs(health_list) do
if c[1] ~= 'vim' then
healthchecks[c[1]] = { c[2], c[3] }
end
end
return healthchecks
end
-- Indents lines *except* line 1 of a string if it contains newlines.
local function indent_after_line1(s, columns)
local lines = vim.split(s, '\n')
local indent = string.rep(' ', columns)
for i = 2, #lines do
lines[i] = indent .. lines[i]
end
return table.concat(lines, '\n')
end
-- Changes ':h clipboard' to ':help |clipboard|'.
local function help_to_link(s)
return vim.fn.substitute(s, [[\v:h%[elp] ([^|][^"\r\n ]+)]], [[:help |\1|]], [[g]])
end
-- Format a message for a specific report item.
-- Variable args: Optional advice (string or list)
local function format_report_message(status, msg, ...)
local output = '- ' .. status
if status ~= '' then
output = output .. ' '
end
output = output .. indent_after_line1(msg, 2)
local varargs = ...
-- Optional parameters
if varargs then
if type(varargs) == 'string' then
varargs = { varargs }
end
output = output .. '\n - ADVICE:'
-- Report each suggestion
for _, v in ipairs(varargs) do
if v then
output = output .. '\n - ' .. indent_after_line1(v, 6)
end
end
end
return help_to_link(output)
end
local function collect_output(output)
vim.list_extend(s_output, vim.split(output, '\n'))
end
-- Starts a new report.
function M.start(name)
local input = string.format('\n%s ~', name)
collect_output(input)
end
-- Reports a message in the current section.
function M.info(msg)
local input = format_report_message('', msg)
collect_output(input)
end
-- Reports a successful healthcheck.
function M.ok(msg)
local input = format_report_message('OK', msg)
collect_output(input)
end
-- Reports a health warning.
-- ...: Optional advice (string or table)
function M.warn(msg, ...)
local input = format_report_message('WARNING', msg, ...)
collect_output(input)
end
-- Reports a failed healthcheck.
-- ...: Optional advice (string or table)
function M.error(msg, ...)
local input = format_report_message('ERROR', msg, ...)
collect_output(input)
end
local function deprecate(type)
local before = string.format('vim.health.report_%s()', type)
local after = string.format('vim.health.%s()', type)
local message = vim.deprecate(before, after, '0.11')
if message then
M.warn(message)
end
vim.cmd.redraw()
vim.print('Running healthchecks...')
end
function M.report_start(name)
deprecate('start')
M.start(name)
end
function M.report_info(msg) function M.report_info(msg)
vim.fn['health#report_info'](msg) deprecate('info')
M.info(msg)
end end
function M.report_ok(msg) function M.report_ok(msg)
vim.fn['health#report_ok'](msg) deprecate('ok')
M.ok(msg)
end end
function M.report_warn(msg, ...) function M.report_warn(msg, ...)
vim.fn['health#report_warn'](msg, ...) deprecate('warn')
M.warn(msg, ...)
end end
function M.report_error(msg, ...) function M.report_error(msg, ...)
vim.fn['health#report_error'](msg, ...) deprecate('error')
M.error(msg, ...)
end end
local path2name = function(path) local path2name = function(path)
@@ -59,4 +232,77 @@ M._complete = function()
return vim.tbl_keys(unique) return vim.tbl_keys(unique)
end end
-- Runs the specified healthchecks.
-- Runs all discovered healthchecks if plugin_names is empty.
function M._check(plugin_names)
local healthchecks = plugin_names == '' and get_healthcheck('*') or get_healthcheck(plugin_names)
-- Create buffer and open in a tab, unless this is the default buffer when Nvim starts.
local emptybuf = vim.fn.bufnr('$') == 1 and vim.fn.getline(1) == '' and 1 == vim.fn.line('$')
local mod = emptybuf and 'buffer' or 'tab sbuffer'
local bufnr = vim.api.nvim_create_buf(true, true)
vim.cmd(mod .. ' ' .. bufnr)
if vim.fn.bufexists('health://') == 1 then
vim.cmd.bwipe('health://')
end
vim.cmd.file('health://')
vim.cmd.setfiletype('checkhealth')
if healthchecks == nil or next(healthchecks) == nil then
vim.fn.setline(1, 'ERROR: No healthchecks found.')
return
end
vim.cmd.redraw()
vim.print('Running healthchecks...')
for name, value in vim.spairs(healthchecks) do
local func = value[1]
local type = value[2]
s_output = {}
if func == '' then
s_output = {}
M.error('No healthcheck found for "' .. name .. '" plugin.')
end
if type == 'v' then
vim.cmd.call(func, {})
else
local f = assert(loadstring(func))
local ok, output = pcall(f)
if not ok then
M.error(
string.format('Failed to run healthcheck for "%s" plugin. Exception:\n%s\n', name, output)
)
end
end
-- in the event the healthcheck doesn't return anything
-- (the plugin author should avoid this possibility)
if next(s_output) == nil then
s_output = {}
M.error('The healthcheck report for "' .. name .. '" plugin is empty.')
end
local header = { string.rep('=', 78), name .. ': ' .. func, '' }
-- remove empty line after header from report_start
if s_output[1] == '' then
local tmp = {}
for i = 2, #s_output do
tmp[#tmp + 1] = s_output[i]
end
s_output = {}
for _, v in ipairs(tmp) do
s_output[#s_output + 1] = v
end
end
s_output[#s_output + 1] = ''
s_output = vim.list_extend(header, s_output)
vim.fn.append('$', s_output)
vim.cmd.redraw()
end
-- Clear the 'Running healthchecks...' message.
vim.cmd.redraw()
vim.print('')
end
return M return M

View File

@@ -2,8 +2,8 @@ local M = {}
--- Performs a healthcheck for LSP --- Performs a healthcheck for LSP
function M.check() function M.check()
local report_info = vim.health.report_info local report_info = vim.health.info
local report_warn = vim.health.report_warn local report_warn = vim.health.warn
local log = require('vim.lsp.log') local log = require('vim.lsp.log')
local current_log_level = log.get_level() local current_log_level = log.get_level()
@@ -29,7 +29,7 @@ function M.check()
report_fn(string.format('Log size: %d KB', log_size / 1000)) report_fn(string.format('Log size: %d KB', log_size / 1000))
local clients = vim.lsp.get_active_clients() local clients = vim.lsp.get_active_clients()
vim.health.report_start('vim.lsp: Active Clients') vim.health.start('vim.lsp: Active Clients')
if next(clients) then if next(clients) then
for _, client in pairs(clients) do for _, client in pairs(clients) do
report_info( report_info(

View File

@@ -6,14 +6,14 @@ local health = require('vim.health')
function M.check() function M.check()
local parsers = vim.api.nvim_get_runtime_file('parser/*', true) local parsers = vim.api.nvim_get_runtime_file('parser/*', true)
health.report_info(string.format('Nvim runtime ABI version: %d', ts.language_version)) health.info(string.format('Nvim runtime ABI version: %d', ts.language_version))
for _, parser in pairs(parsers) do for _, parser in pairs(parsers) do
local parsername = vim.fn.fnamemodify(parser, ':t:r') local parsername = vim.fn.fnamemodify(parser, ':t:r')
local is_loadable, err_or_nil = pcall(ts.language.add, parsername) local is_loadable, err_or_nil = pcall(ts.language.add, parsername)
if not is_loadable then if not is_loadable then
health.report_error( health.error(
string.format( string.format(
'Parser "%s" failed to load (path: %s): %s', 'Parser "%s" failed to load (path: %s): %s',
parsername, parsername,
@@ -23,7 +23,7 @@ function M.check()
) )
else else
local lang = ts.language.inspect(parsername) local lang = ts.language.inspect(parsername)
health.report_ok( health.ok(
string.format('Parser: %-10s ABI: %d, path: %s', parsername, lang._abi_version, parser) string.format('Parser: %-10s ABI: %d, path: %s', parsername, lang._abi_version, parser)
) )
end end

View File

@@ -8597,12 +8597,14 @@ void eval_fmt_source_name_line(char *buf, size_t bufsize)
/// ":checkhealth [plugins]" /// ":checkhealth [plugins]"
void ex_checkhealth(exarg_T *eap) void ex_checkhealth(exarg_T *eap)
{ {
bool found = !!find_func("health#check"); Error err = ERROR_INIT;
if (!found MAXSIZE_TEMP_ARRAY(args, 1);
&& script_autoload("health#check", sizeof("health#check") - 1, false)) { ADD_C(args, STRING_OBJ(cstr_as_string(eap->arg)));
found = !!find_func("health#check"); NLUA_EXEC_STATIC("return vim.health._check(...)", args, &err);
if (!ERROR_SET(&err)) {
return;
} }
if (!found) {
const char *vimruntime_env = os_getenv("VIMRUNTIME"); const char *vimruntime_env = os_getenv("VIMRUNTIME");
if (vimruntime_env == NULL) { if (vimruntime_env == NULL) {
emsg(_("E5009: $VIMRUNTIME is empty or unset")); emsg(_("E5009: $VIMRUNTIME is empty or unset"));
@@ -8614,16 +8616,8 @@ void ex_checkhealth(exarg_T *eap)
emsg(_("E5009: Invalid 'runtimepath'")); emsg(_("E5009: Invalid 'runtimepath'"));
} }
} }
return; semsg_multiline(err.msg);
} api_clear_error(&err);
size_t bufsize = strlen(eap->arg) + sizeof("call health#check('')");
char *buf = xmalloc(bufsize);
snprintf(buf, bufsize, "call health#check('%s')", eap->arg);
do_cmdline_cmd(buf);
xfree(buf);
} }
void invoke_prompt_callback(void) void invoke_prompt_callback(void)

View File

@@ -1,3 +0,0 @@
function! health#broken#check()
throw 'caused an error'
endfunction

View File

@@ -1,8 +0,0 @@
function! health#full_render#check()
call health#report_start("report 1")
call health#report_ok("life is fine")
call health#report_warn("no what installed", ["pip what", "make what"])
call health#report_start("report 2")
call health#report_info("stuff is stable")
call health#report_error("why no hardcopy", [":h :hardcopy", ":h :TOhtml"])
endfunction

View File

@@ -1,6 +0,0 @@
function! health#success1#check()
call health#report_start("report 1")
call health#report_ok("everything is fine")
call health#report_start("report 2")
call health#report_ok("nothing to see here")
endfunction

View File

@@ -1,4 +0,0 @@
function! health#success2#check()
call health#report_start("another 1")
call health#report_ok("ok")
endfunction

View File

@@ -1,3 +0,0 @@
function! health#success1#check()
call health#report_start("If you see this I'm broken")
endfunction

View File

@@ -0,0 +1,12 @@
local M = {}
M.check = function()
vim.health.start('report 1')
vim.health.ok('life is fine')
vim.health.warn('no what installed', { 'pip what', 'make what' })
vim.health.start('report 2')
vim.health.info('stuff is stable')
vim.health.error('why no hardcopy', { ':h :hardcopy', ':h :TOhtml' })
end
return M

View File

@@ -1,10 +1,10 @@
local M = {} local M = {}
M.check = function() M.check = function()
vim.health.report_start("report 1") vim.health.start('report 1')
vim.health.report_ok("everything is fine") vim.health.ok('everything is fine')
vim.health.report_start("report 2") vim.health.start('report 2')
vim.health.report_ok("nothing to see here") vim.health.ok('nothing to see here')
end end
return M return M

View File

@@ -1,10 +1,10 @@
local M = {} local M = {}
M.check = function() M.check = function()
vim.health.report_start("report 1") vim.health.start('report 1')
vim.health.report_ok("everything is fine") vim.health.ok('everything is fine')
vim.health.report_start("report 2") vim.health.start('report 2')
vim.health.report_ok("nothing to see here") vim.health.ok('nothing to see here')
end end
return M return M

View File

@@ -1,11 +0,0 @@
local M = {}
M.check = function()
vim.health.report_start("report 1")
vim.health.report_ok("everything is fine")
vim.health.report_warn("About to add a number to nil")
local a = nil + 2
return a
end
return M

View File

@@ -0,0 +1,10 @@
local M = {}
M.check = function()
vim.health.start('report 1')
vim.health.ok('everything is fine')
vim.health.start('report 2')
vim.health.ok('nothing to see here')
end
return M

View File

@@ -0,0 +1,8 @@
local M = {}
M.check = function()
vim.health.start('another 1')
vim.health.ok('ok')
end
return M

View File

@@ -1,5 +1,4 @@
local helpers = require('test.functional.helpers')(after_each) local helpers = require('test.functional.helpers')(after_each)
local global_helpers = require('test.helpers')
local Screen = require('test.functional.ui.screen') local Screen = require('test.functional.ui.screen')
local clear = helpers.clear local clear = helpers.clear
@@ -43,20 +42,17 @@ end)
describe('health.vim', function() describe('health.vim', function()
before_each(function() before_each(function()
clear{args={'-u', 'NORC'}} clear{args={'-u', 'NORC'}}
-- Provides functions: -- Provides healthcheck functions
-- health#broken#check()
-- health#success1#check()
-- health#success2#check()
command("set runtimepath+=test/functional/fixtures") command("set runtimepath+=test/functional/fixtures")
end) end)
describe(":checkhealth", function() describe(":checkhealth", function()
it("functions health#report_*() render correctly", function() it("functions report_*() render correctly", function()
command("checkhealth full_render") command("checkhealth full_render")
helpers.expect([[ helpers.expect([[
============================================================================== ==============================================================================
full_render: health#full_render#check test_plug.full_render: require("test_plug.full_render.health").check()
report 1 ~ report 1 ~
- OK life is fine - OK life is fine
@@ -79,7 +75,7 @@ describe('health.vim', function()
helpers.expect([[ helpers.expect([[
============================================================================== ==============================================================================
success1: health#success1#check test_plug: require("test_plug.health").check()
report 1 ~ report 1 ~
- OK everything is fine - OK everything is fine
@@ -88,36 +84,19 @@ describe('health.vim', function()
- OK nothing to see here - OK nothing to see here
============================================================================== ==============================================================================
success2: health#success2#check test_plug.success1: require("test_plug.success1.health").check()
report 1 ~
- OK everything is fine
report 2 ~
- OK nothing to see here
==============================================================================
test_plug.success2: require("test_plug.success2.health").check()
another 1 ~ another 1 ~
- OK ok - OK ok
==============================================================================
test_plug: require("test_plug.health").check()
report 1 ~
- OK everything is fine
report 2 ~
- OK nothing to see here
]])
end)
it("lua plugins, skips vimscript healthchecks with the same name", function()
command("checkhealth test_plug")
-- Existing file in test/functional/fixtures/lua/test_plug/autoload/health/test_plug.vim
-- and the Lua healthcheck is used instead.
helpers.expect([[
==============================================================================
test_plug: require("test_plug.health").check()
report 1 ~
- OK everything is fine
report 2 ~
- OK nothing to see here
]]) ]])
end) end)
@@ -136,57 +115,6 @@ describe('health.vim', function()
]]) ]])
end) end)
it("lua plugins submodules with expression '*'", function()
command("checkhealth test_plug*")
local buf_lines = helpers.curbuf('get_lines', 0, -1, true)
-- avoid dealing with path separators
local received = table.concat(buf_lines, '\n', 1, #buf_lines - 5)
local expected = helpers.dedent([[
==============================================================================
test_plug: require("test_plug.health").check()
report 1 ~
- OK everything is fine
report 2 ~
- OK nothing to see here
==============================================================================
test_plug.submodule: require("test_plug.submodule.health").check()
report 1 ~
- OK everything is fine
report 2 ~
- OK nothing to see here
==============================================================================
test_plug.submodule_empty: require("test_plug.submodule_empty.health").check()
- ERROR The healthcheck report for "test_plug.submodule_empty" plugin is empty.
==============================================================================
test_plug.submodule_failed: require("test_plug.submodule_failed.health").check()
- ERROR Failed to run healthcheck for "test_plug.submodule_failed" plugin. Exception:
function health#check, line 25]])
eq(expected, received)
end)
it("gracefully handles broken healthcheck", function()
command("checkhealth broken")
helpers.expect([[
==============================================================================
broken: health#broken#check
- ERROR Failed to run healthcheck for "broken" plugin. Exception:
function health#check[25]..health#broken#check, line 1
caused an error
]])
end)
it("... including empty reports", function() it("... including empty reports", function()
command("checkhealth test_plug.submodule_empty") command("checkhealth test_plug.submodule_empty")
helpers.expect([[ helpers.expect([[
@@ -198,25 +126,6 @@ describe('health.vim', function()
]]) ]])
end) end)
it("gracefully handles broken lua healthcheck", function()
command("checkhealth test_plug.submodule_failed")
local buf_lines = helpers.curbuf('get_lines', 0, -1, true)
local received = table.concat(buf_lines, '\n', 1, #buf_lines - 5)
-- avoid dealing with path separators
local lua_err = "attempt to perform arithmetic on a nil value"
local last_line = buf_lines[#buf_lines - 4]
assert(string.find(last_line, lua_err) ~= nil, "Lua error not present")
local expected = global_helpers.dedent([[
==============================================================================
test_plug.submodule_failed: require("test_plug.submodule_failed.health").check()
- ERROR Failed to run healthcheck for "test_plug.submodule_failed" plugin. Exception:
function health#check, line 25]])
eq(expected, received)
end)
it("highlights OK, ERROR", function() it("highlights OK, ERROR", function()
local screen = Screen.new(50, 12) local screen = Screen.new(50, 12)
screen:attach() screen:attach()
@@ -236,7 +145,7 @@ describe('health.vim', function()
- {Error:ERROR} No healthcheck found for "foo" plugin. | - {Error:ERROR} No healthcheck found for "foo" plugin. |
| |
{Bar:──────────────────────────────────────────────────}| {Bar:──────────────────────────────────────────────────}|
{Heading:success1: health#success1#check} | {Heading:test_plug.success1: require("test_plug.success1.he}|
| |
{Heading:report 1} | {Heading:report 1} |
- {Ok:OK} everything is fine | - {Ok:OK} everything is fine |