1" Runs the specified healthchecks. 2" Runs all discovered healthchecks if a:plugin_names is empty. 3function! health#check(plugin_names) abort 4 let healthchecks = empty(a:plugin_names) 5 \ ? s:discover_healthchecks() 6 \ : s:get_healthcheck(a:plugin_names) 7 8 " create scratch-buffer 9 execute 'tab sbuffer' nvim_create_buf(v:true, v:true) 10 setfiletype checkhealth 11 12 if empty(healthchecks) 13 call setline(1, 'ERROR: No healthchecks found.') 14 else 15 redraw|echo 'Running healthchecks...' 16 for name in sort(keys(healthchecks)) 17 let [func, type] = healthchecks[name] 18 let s:output = [] 19 try 20 if func == '' 21 throw 'healthcheck_not_found' 22 endif 23 eval type == 'v' ? call(func, []) : luaeval(func) 24 catch 25 let s:output = [] " Clear the output 26 if v:exception =~# 'healthcheck_not_found' 27 call health#report_error('No healthcheck found for "'.name.'" plugin.') 28 else 29 call health#report_error(printf( 30 \ "Failed to run healthcheck for \"%s\" plugin. Exception:\n%s\n%s", 31 \ name, v:throwpoint, v:exception)) 32 endif 33 endtry 34 let header = [name. ': ' . func, repeat('=', 72)] 35 " remove empty line after header from report_start 36 let s:output = s:output[0] == '' ? s:output[1:] : s:output 37 let s:output = header + s:output + [''] 38 call append('$', s:output) 39 redraw 40 endfor 41 endif 42 43 " needed for plasticboy/vim-markdown, because it uses fdm=expr 44 normal! zR 45 redraw|echo '' 46endfunction 47 48function! s:collect_output(output) 49 let s:output += split(a:output, "\n", 1) 50endfunction 51 52" Starts a new report. 53function! health#report_start(name) abort 54 call s:collect_output("\n## " . a:name) 55endfunction 56 57" Indents lines *except* line 1 of a string if it contains newlines. 58function! s:indent_after_line1(s, columns) abort 59 let lines = split(a:s, "\n", 0) 60 if len(lines) < 2 " We do not indent line 1, so nothing to do. 61 return a:s 62 endif 63 for i in range(1, len(lines)-1) " Indent lines after the first. 64 let lines[i] = substitute(lines[i], '^\s*', repeat(' ', a:columns), 'g') 65 endfor 66 return join(lines, "\n") 67endfunction 68 69" Changes ':h clipboard' to ':help |clipboard|'. 70function! s:help_to_link(s) abort 71 return substitute(a:s, '\v:h%[elp] ([^|][^"\r\n ]+)', ':help |\1|', 'g') 72endfunction 73 74" Format a message for a specific report item. 75" a:1: Optional advice (string or list) 76function! s:format_report_message(status, msg, ...) abort " {{{ 77 let output = ' - ' . a:status . ': ' . s:indent_after_line1(a:msg, 4) 78 79 " Optional parameters 80 if a:0 > 0 81 let advice = type(a:1) == type('') ? [a:1] : a:1 82 if type(advice) != type([]) 83 throw 'a:1: expected String or List' 84 endif 85 86 " Report each suggestion 87 if !empty(advice) 88 let output .= "\n - ADVICE:" 89 for suggestion in advice 90 let output .= "\n - " . s:indent_after_line1(suggestion, 10) 91 endfor 92 endif 93 endif 94 95 return s:help_to_link(output) 96endfunction " }}} 97 98" Use {msg} to report information in the current section 99function! health#report_info(msg) abort " {{{ 100 call s:collect_output(s:format_report_message('INFO', a:msg)) 101endfunction " }}} 102 103" Reports a successful healthcheck. 104function! health#report_ok(msg) abort " {{{ 105 call s:collect_output(s:format_report_message('OK', a:msg)) 106endfunction " }}} 107 108" Reports a health warning. 109" a:1: Optional advice (string or list) 110function! health#report_warn(msg, ...) abort " {{{ 111 if a:0 > 0 112 call s:collect_output(s:format_report_message('WARNING', a:msg, a:1)) 113 else 114 call s:collect_output(s:format_report_message('WARNING', a:msg)) 115 endif 116endfunction " }}} 117 118" Reports a failed healthcheck. 119" a:1: Optional advice (string or list) 120function! health#report_error(msg, ...) abort " {{{ 121 if a:0 > 0 122 call s:collect_output(s:format_report_message('ERROR', a:msg, a:1)) 123 else 124 call s:collect_output(s:format_report_message('ERROR', a:msg)) 125 endif 126endfunction " }}} 127 128" From a path return a list [{name}, {func}, {type}] representing a healthcheck 129function! s:filepath_to_healthcheck(path) abort 130 if a:path =~# 'vim$' 131 let name = matchstr(a:path, '\zs[^\/]*\ze\.vim$') 132 let func = 'health#'.name.'#check' 133 let type = 'v' 134 else 135 let base_path = substitute(a:path, 136 \ '.*lua[\/]\(.\{-}\)[\/]health\([\/]init\)\?\.lua$', 137 \ '\1', '') 138 let name = substitute(base_path, '[\/]', '.', 'g') 139 let func = 'require("'.name.'.health").check()' 140 let type = 'l' 141 endif 142 return [name, func, type] 143endfunction 144 145function! s:discover_healthchecks() abort 146 return s:get_healthcheck('*') 147endfunction 148 149" Returns Dictionary {name: [func, type], ..} representing healthchecks 150function! s:get_healthcheck(plugin_names) abort 151 let health_list = s:get_healthcheck_list(a:plugin_names) 152 let healthchecks = {} 153 for c in health_list 154 let normalized_name = substitute(c[0], '-', '_', 'g') 155 let existent = get(healthchecks, normalized_name, []) 156 " Prefer Lua over vim entries 157 if existent != [] && existent[2] == 'l' 158 continue 159 else 160 let healthchecks[normalized_name] = c 161 endif 162 endfor 163 let output = {} 164 for v in values(healthchecks) 165 let output[v[0]] = v[1:] 166 endfor 167 return output 168endfunction 169 170" Returns list of lists [ [{name}, {func}, {type}] ] representing healthchecks 171function! s:get_healthcheck_list(plugin_names) abort 172 let healthchecks = [] 173 let plugin_names = type('') == type(a:plugin_names) 174 \ ? split(a:plugin_names, ' ', v:false) 175 \ : a:plugin_names 176 for p in plugin_names 177 " support vim/lsp/health{/init/}.lua as :checkhealth vim.lsp 178 let p = substitute(p, '\.', '/', 'g') 179 let p = substitute(p, '*$', '**', 'g') " find all submodule e.g vim* 180 let paths = nvim_get_runtime_file('autoload/health/'.p.'.vim', v:true) 181 \ + nvim_get_runtime_file('lua/**/'.p.'/health/init.lua', v:true) 182 \ + nvim_get_runtime_file('lua/**/'.p.'/health.lua', v:true) 183 if len(paths) == 0 184 let healthchecks += [[p, '', '']] " healthchek not found 185 else 186 let healthchecks += map(uniq(sort(paths)), 187 \'<SID>filepath_to_healthcheck(v:val)') 188 end 189 endfor 190 return healthchecks 191endfunction 192