From ad7211ac8f62d7c1f2696edb304fccb9ec4fdef9 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 27 Apr 2025 10:32:25 -0700 Subject: [PATCH 1/2] feat(checkhealth): trigger FileType event after showing report Problem: `FileType` event is fired before checkhealth report is finished, so user can't override report settings or contents. https://github.com/neovim/neovim/pull/33172#issuecomment-2833513916 Solution: - Trigger FileType event later. - Document how to remove emojis. --- runtime/doc/health.txt | 65 +++++++++-------- runtime/doc/news.txt | 2 + runtime/lua/vim/health.lua | 98 ++++++++++++++------------ test/functional/plugin/health_spec.lua | 47 +++++++++--- 4 files changed, 129 insertions(+), 83 deletions(-) diff --git a/runtime/doc/health.txt b/runtime/doc/health.txt index 3d37b88321..a93eab6f6c 100644 --- a/runtime/doc/health.txt +++ b/runtime/doc/health.txt @@ -9,18 +9,18 @@ ============================================================================== Checkhealth *vim.health* *health* - vim.health is a minimal framework to help users troubleshoot configuration and any other environment conditions that a plugin might care about. Nvim ships with healthchecks for configuration, performance, python support, ruby support, clipboard support, and more. To run all healthchecks, use: >vim - - :checkhealth + :checkhealth < + Plugin authors are encouraged to write new healthchecks. |health-dev| + COMMANDS *health-commands* *:che* *:checkhealth* @@ -56,7 +56,6 @@ Local mappings in the healthcheck buffer: q Closes the window. Global configuration: - *g:health* g:health Dictionary with the following optional keys: - `style` (`'float'|nil`) Set to "float" to display :checkhealth in @@ -65,16 +64,26 @@ g:health Dictionary with the following optional keys: Example: >lua vim.g.health = { style = 'float' } + +Local configuration: + +Checkhealth sets its buffer filetype to "checkhealth". You can customize the +buffer by handling the |FileType| event. For example if you don't want emojis +in the health report: >vim + autocmd FileType checkhealth :set modifiable | silent! %s/\v( ?[^\x00-\x7F])//g +< + + -------------------------------------------------------------------------------- Create a healthcheck *health-dev* Healthchecks are functions that check the user environment, configuration, or any other prerequisites that a plugin cares about. Nvim ships with healthchecks in: - - $VIMRUNTIME/autoload/health/ - - $VIMRUNTIME/lua/vim/lsp/health.lua - - $VIMRUNTIME/lua/vim/treesitter/health.lua - - and more... +• $VIMRUNTIME/autoload/health/ +• $VIMRUNTIME/lua/vim/lsp/health.lua +• $VIMRUNTIME/lua/vim/treesitter/health.lua +• and more... To add a new healthcheck for your own plugin, simply create a "health.lua" module on 'runtimepath' that returns a table with a "check()" function. Then @@ -82,35 +91,35 @@ module on 'runtimepath' that returns a table with a "check()" function. Then For example if your plugin is named "foo", define your healthcheck module at one of these locations (on 'runtimepath'): - - lua/foo/health/init.lua - - lua/foo/health.lua +• lua/foo/health/init.lua +• lua/foo/health.lua -If your plugin also provides a submodule named "bar" for which you want -a separate healthcheck, define the healthcheck at one of these locations: - - lua/foo/bar/health/init.lua - - lua/foo/bar/health.lua +If your plugin also provides a submodule named "bar" for which you want a +separate healthcheck, define the healthcheck at one of these locations: +• lua/foo/bar/health/init.lua +• lua/foo/bar/health.lua All such health modules must return a Lua table containing a `check()` function. Copy this sample code into `lua/foo/health.lua`, replacing "foo" in the path with your plugin name: >lua + local M = {} - local M = {} + M.check = function() + vim.health.start("foo report") + -- make sure setup function parameters are ok + if check_setup() then + vim.health.ok("Setup is correct") + else + vim.health.error("Setup is incorrect") + end + -- do some more checking + -- ... + end - M.check = function() - vim.health.start("foo report") - -- make sure setup function parameters are ok - if check_setup() then - vim.health.ok("Setup is correct") - else - vim.health.error("Setup is incorrect") - end - -- do some more checking - -- ... - end - - return M + return M +< error({msg}, {...}) *vim.health.error()* diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 435c3a6ef4..cdf2db8032 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -343,6 +343,8 @@ PLUGINS • 'commentstring' values can now be specified in a Treesitter capture's `bo.commentstring` metadata field, providing finer grained support for languages like `JSX`. +• Customize :checkhealth by handling a `FileType checkhealth` event. + |health-usage| STARTUP diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index c95499e500..215bafc582 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -1,16 +1,16 @@ --- @brief ----
help
---- vim.health is a minimal framework to help users troubleshoot configuration and
---- any other environment conditions that a plugin might care about. Nvim ships
---- with healthchecks for configuration, performance, python support, ruby
---- support, clipboard support, and more.
 ---
---- To run all healthchecks, use: >vim
+--- vim.health is a minimal framework to help users troubleshoot configuration and any other
+--- environment conditions that a plugin might care about. Nvim ships with healthchecks for
+--- configuration, performance, python support, ruby support, clipboard support, and more.
 ---
----         :checkhealth
---- <
+--- To run all healthchecks, use:
+--- ```vim
+--- :checkhealth
+--- ```
 --- Plugin authors are encouraged to write new healthchecks. |health-dev|
 ---
+---
help
 --- COMMANDS                                *health-commands*
 ---
 ---                                                              *:che* *:checkhealth*
@@ -46,7 +46,6 @@
 --- q               Closes the window.
 ---
 --- Global configuration:
----
 ---                                                              *g:health*
 --- g:health  Dictionary with the following optional keys:
 ---           - `style` (`'float'|nil`) Set to "float" to display :checkhealth in
@@ -55,53 +54,64 @@
 ---           Example: >lua
 ---             vim.g.health = { style = 'float' }
 ---
+---
+--- +--- Local configuration: +--- +--- Checkhealth sets its buffer filetype to "checkhealth". You can customize the buffer by handling +--- the |FileType| event. For example if you don't want emojis in the health report: +--- ```vim +--- autocmd FileType checkhealth :set modifiable | silent! %s/\v( ?[^\x00-\x7F])//g +--- ``` +--- +---
help
 --- --------------------------------------------------------------------------------
 --- Create a healthcheck                                    *health-dev*
+---
--- ---- Healthchecks are functions that check the user environment, configuration, or ---- any other prerequisites that a plugin cares about. Nvim ships with ---- healthchecks in: ---- - $VIMRUNTIME/autoload/health/ ---- - $VIMRUNTIME/lua/vim/lsp/health.lua ---- - $VIMRUNTIME/lua/vim/treesitter/health.lua ---- - and more... +--- Healthchecks are functions that check the user environment, configuration, or any other +--- prerequisites that a plugin cares about. Nvim ships with healthchecks in: +--- - $VIMRUNTIME/autoload/health/ +--- - $VIMRUNTIME/lua/vim/lsp/health.lua +--- - $VIMRUNTIME/lua/vim/treesitter/health.lua +--- - and more... --- ---- To add a new healthcheck for your own plugin, simply create a "health.lua" ---- module on 'runtimepath' that returns a table with a "check()" function. Then ---- |:checkhealth| will automatically find and invoke the function. +--- To add a new healthcheck for your own plugin, simply create a "health.lua" module on +--- 'runtimepath' that returns a table with a "check()" function. Then |:checkhealth| will +--- automatically find and invoke the function. --- --- For example if your plugin is named "foo", define your healthcheck module at --- one of these locations (on 'runtimepath'): ---- - lua/foo/health/init.lua ---- - lua/foo/health.lua +--- - lua/foo/health/init.lua +--- - lua/foo/health.lua --- ---- If your plugin also provides a submodule named "bar" for which you want ---- a separate healthcheck, define the healthcheck at one of these locations: ---- - lua/foo/bar/health/init.lua ---- - lua/foo/bar/health.lua +--- If your plugin also provides a submodule named "bar" for which you want a separate healthcheck, +--- define the healthcheck at one of these locations: +--- - lua/foo/bar/health/init.lua +--- - lua/foo/bar/health.lua --- ---- All such health modules must return a Lua table containing a `check()` ---- function. +--- All such health modules must return a Lua table containing a `check()` function. --- ---- Copy this sample code into `lua/foo/health.lua`, replacing "foo" in the path ---- with your plugin name: >lua +--- Copy this sample code into `lua/foo/health.lua`, replacing "foo" in the path with your plugin +--- name: --- ---- local M = {} +--- ```lua +--- local M = {} --- ---- M.check = function() ---- vim.health.start("foo report") ---- -- make sure setup function parameters are ok ---- if check_setup() then ---- vim.health.ok("Setup is correct") ---- else ---- vim.health.error("Setup is incorrect") ---- end ---- -- do some more checking ---- -- ... ---- end +--- M.check = function() +--- vim.health.start("foo report") +--- -- make sure setup function parameters are ok +--- if check_setup() then +--- vim.health.ok("Setup is correct") +--- else +--- vim.health.error("Setup is incorrect") +--- end +--- -- do some more checking +--- -- ... +--- end --- ---- return M ----
+--- return M +--- ``` local M = {} @@ -385,7 +395,6 @@ function M._check(mods, plugin_names) vim.cmd.bwipe('health://') end vim.cmd.file('health://') - vim.cmd.setfiletype('checkhealth') -- This should only happen when doing `:checkhealth vim` if next(healthchecks) == nil then @@ -463,6 +472,7 @@ function M._check(mods, plugin_names) -- Once we're done writing checks, set nomodifiable. vim.bo[bufnr].modifiable = false + vim.cmd.setfiletype('checkhealth') end return M diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index 58634460c8..c22823b747 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_spec.lua @@ -78,6 +78,15 @@ describe(':checkhealth', function() ]]) ) end) + + it("vim.provider works with a misconfigured 'shell'", function() + clear() + command([[set shell=echo\ WRONG!!!]]) + command('let g:loaded_perl_provider = 0') + command('let g:loaded_python3_provider = 0') + command('checkhealth vim.provider') + eq(nil, string.match(curbuf_contents(), 'WRONG!!!')) + end) end) describe('vim.health', function() @@ -109,6 +118,33 @@ describe('vim.health', function() ]]) end) + it('user FileType handler can modify report', function() + -- Define a FileType autocmd that removes emoji chars. + source [[ + autocmd FileType checkhealth :set modifiable | silent! %s/\v( ?[^\x00-\x7F])//g + checkhealth full_render + ]] + n.expect([[ + + ============================================================================== + test_plug.full_render: 1 1 + + report 1 ~ + - OK life is fine + - WARNING no what installed + - ADVICE: + - pip what + - make what + + report 2 ~ + - stuff is stable + - ERROR why no hardcopy + - ADVICE: + - :help |:hardcopy| + - :help |:TOhtml| + ]]) + end) + it('concatenates multiple reports', function() command('checkhealth success1 success2 test_plug') n.expect([[ @@ -245,17 +281,6 @@ describe('vim.health', function() end) end) -describe(':checkhealth vim.provider', function() - it("works correctly with a wrongly configured 'shell'", function() - clear() - command([[set shell=echo\ WRONG!!!]]) - command('let g:loaded_perl_provider = 0') - command('let g:loaded_python3_provider = 0') - command('checkhealth vim.provider') - eq(nil, string.match(curbuf_contents(), 'WRONG!!!')) - end) -end) - describe(':checkhealth window', function() before_each(function() clear { args = { '-u', 'NORC', '+set runtimepath+=test/functional/fixtures' } } From f25f6c8d132db2174805a6cda8171a84b8817918 Mon Sep 17 00:00:00 2001 From: Yochem van Rosmalen Date: Wed, 9 Apr 2025 13:13:20 +0200 Subject: [PATCH 2/2] feat(health): summary in section heading #33388 Problem: As checkhealth grows, it is increasingly hard to quickly glance through the information. Solution: Show a summary of ok, warn, and error outputs per section. --- runtime/doc/news.txt | 1 + runtime/lua/vim/health.lua | 28 +++++++++++++++++--- test/functional/plugin/health_spec.lua | 36 +++++++++++++------------- 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index cdf2db8032..4026475f63 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -439,6 +439,7 @@ UI • |ui-messages| content chunks now also contain the highlight group ID. • |:checkhealth| can display in a floating window, controlled by the |g:health| variable. +• |:checkhealth| shows a summary in the header for every healthcheck. VIMSCRIPT diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index 215bafc582..4646d9d038 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -116,6 +116,7 @@ local M = {} local s_output = {} ---@type string[] +local check_summary = { warn = 0, error = 0 } -- From a path return a list [{name}, {func}, {type}] representing a healthcheck local function filepath_to_healthcheck(path) @@ -296,6 +297,7 @@ end function M.warn(msg, ...) local input = format_report_message('⚠️ WARNING', msg, ...) collect_output(input) + check_summary['warn'] = check_summary['warn'] + 1 end --- Reports an error. @@ -305,6 +307,7 @@ end function M.error(msg, ...) local input = format_report_message('❌ ERROR', msg, ...) collect_output(input) + check_summary['error'] = check_summary['error'] + 1 end local path2name = function(path) @@ -351,6 +354,23 @@ M._complete = function() return rv end +--- Gets the results heading for the current report section. +--- +---@return string +local function get_summary() + local s = '' + local errors = check_summary['error'] + local warns = check_summary['warn'] + + s = s .. (warns > 0 and (' %2d ⚠️'):format(warns) or '') + s = s .. (errors > 0 and (' %2d ❌'):format(errors) or '') + if errors == 0 and warns == 0 then + s = s .. '✅' + end + + return s +end + --- Runs the specified healthchecks. --- Runs all discovered healthchecks if plugin_names is empty. --- @@ -408,9 +428,9 @@ function M._check(mods, plugin_names) local func = value[1] local type = value[2] s_output = {} + check_summary = { warn = 0, error = 0 } if func == '' then - s_output = {} M.error('No healthcheck found for "' .. name .. '" plugin.') end if type == 'v' then @@ -431,10 +451,12 @@ function M._check(mods, plugin_names) M.error('The healthcheck report for "' .. name .. '" plugin is empty.') end + local report = get_summary() + local replen = vim.fn.strwidth(report) local header = { string.rep('=', 78), - -- Example: `foo.health: [ …] require("foo.health").check()` - ('%s: %s%s'):format(name, (' '):rep(76 - name:len() - func:len()), func), + -- Example: `foo.health: [ …] 1 ⚠️ 5 ❌` + ('%s: %s%s'):format(name, (' '):rep(76 - name:len() - replen), report), '', } diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index c22823b747..0a1db297c2 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_spec.lua @@ -100,7 +100,7 @@ describe('vim.health', function() n.expect([[ ============================================================================== - test_plug.full_render: require("test_plug.full_render.health").check() + test_plug.full_render: 1 ⚠️ 1 ❌ report 1 ~ - ✅ OK life is fine @@ -150,7 +150,7 @@ describe('vim.health', function() n.expect([[ ============================================================================== - test_plug: require("test_plug.health").check() + test_plug: ✅ report 1 ~ - ✅ OK everything is fine @@ -159,7 +159,7 @@ describe('vim.health', function() - ✅ OK nothing to see here ============================================================================== - test_plug.success1: require("test_plug.success1.health").check() + test_plug.success1: ✅ report 1 ~ - ✅ OK everything is fine @@ -168,7 +168,7 @@ describe('vim.health', function() - ✅ OK nothing to see here ============================================================================== - test_plug.success2: require("test_plug.success2.health").check() + test_plug.success2: ✅ another 1 ~ - ✅ OK ok @@ -180,7 +180,7 @@ describe('vim.health', function() n.expect([[ ============================================================================== - test_plug.submodule: require("test_plug.submodule.health").check() + test_plug.submodule: ✅ report 1 ~ - ✅ OK everything is fine @@ -195,7 +195,7 @@ describe('vim.health', function() n.expect([[ ============================================================================== - test_plug.submodule_empty: require("test_plug.submodule_empty.health").check() + test_plug.submodule_empty: 1 ❌ - ❌ ERROR The healthcheck report for "test_plug.submodule_empty" plugin is empty. ]]) @@ -221,7 +221,7 @@ describe('vim.health', function() - ❌ {Error:ERROR} No healthcheck found for "foo" plugin. | | {Bar: }| - {h1:test_plug.success1: require("test_pl}| + {h1:test_plug.success1: }| | {h2:report 1} | - ✅ {Ok:OK} everything is fine | @@ -236,7 +236,7 @@ describe('vim.health', function() n.expect([[ ============================================================================== - non_existent_healthcheck: + non_existent_healthcheck: 1 ❌ - ❌ ERROR No healthcheck found for "non_existent_healthcheck" plugin. ]]) @@ -254,7 +254,7 @@ describe('vim.health', function() n.expect([[ ============================================================================== - test_plug.lua: require("test_plug.lua.health").check() + test_plug.lua: ✅ nested lua/ directory ~ - ✅ OK everything is ok @@ -272,7 +272,7 @@ describe('vim.health', function() n.expect([[ ============================================================================== - nest: require("nest.health").check() + nest: ✅ healthy pack ~ - ✅ OK healthy ok @@ -306,14 +306,14 @@ describe(':checkhealth window', function() ^ | {14: }| {14: } | - {h1:test_plug.success1: }| - {h1:require("test_plug.success1.health").check()} | + {h1:test_plug. }| + {h1:success1: }| + {h1: ✅} | | {h2:report 1} | - ✅ {32:OK} everything is fine | | {h2:report 2} | - - ✅ {32:OK} nothing to see here | ## grid 3 | ]], @@ -349,8 +349,8 @@ describe(':checkhealth window', function() {14: } | {h1:test_plug. }| {h1:success1: }| - {h1:require("test_plug. }| - {h1:success1.health").check()}| + {h1: }| + {h1: ✅} | | {h2:report 1} | - ✅ {32:OK} everything is | @@ -408,15 +408,15 @@ describe(':checkhealth window', function() ^ | | | - test_plug.success1: | - require("test_plug.success1.health").check() | + test_plug. | + success1: | + ✅ | | report 1 | - ✅ OK everything is fine | | report 2 | - ✅ OK nothing to see here | - | ]]):format( top and [[