mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-03 17:24:29 +00:00 
			
		
		
		
	Problem:    Some plugins have structure `lua/nvim-someplugin/..`
            Since `-` is not allowed in vim function names, healthcheck names in
            lua and in vim can not have the same name (typically vim will use `_`
            instead of `-`).
Solution:   Normalize the names before checking for duplicates.
		
	
		
			
				
	
	
		
			222 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			VimL
		
	
	
	
	
	
			
		
		
	
	
			222 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			VimL
		
	
	
	
	
	
function! s:enhance_syntax() abort
 | 
						|
  syntax case match
 | 
						|
 | 
						|
  syntax keyword healthError ERROR[:]
 | 
						|
        \ containedin=markdownCodeBlock,mkdListItemLine
 | 
						|
  highlight default link healthError Error
 | 
						|
 | 
						|
  syntax keyword healthWarning WARNING[:]
 | 
						|
        \ containedin=markdownCodeBlock,mkdListItemLine
 | 
						|
  highlight default link healthWarning WarningMsg
 | 
						|
 | 
						|
  syntax keyword healthSuccess OK[:]
 | 
						|
        \ containedin=markdownCodeBlock,mkdListItemLine
 | 
						|
  highlight default healthSuccess guibg=#5fff00 guifg=#080808 ctermbg=82 ctermfg=232
 | 
						|
 | 
						|
  syntax match healthHelp "|.\{-}|" contains=healthBar
 | 
						|
        \ containedin=markdownCodeBlock,mkdListItemLine
 | 
						|
  syntax match healthBar  "|" contained conceal
 | 
						|
  highlight default link healthHelp Identifier
 | 
						|
 | 
						|
  " We do not care about markdown syntax errors in :checkhealth output.
 | 
						|
  highlight! link markdownError Normal
 | 
						|
endfunction
 | 
						|
 | 
						|
" 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)
 | 
						|
 | 
						|
  tabnew
 | 
						|
  setlocal wrap breakindent linebreak
 | 
						|
  setlocal filetype=markdown
 | 
						|
  setlocal conceallevel=2 concealcursor=nc
 | 
						|
  setlocal keywordprg=:help
 | 
						|
  let &l:iskeyword='!-~,^*,^|,^",192-255'
 | 
						|
  call s:enhance_syntax()
 | 
						|
 | 
						|
  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)
 | 
						|
      catch
 | 
						|
        let s:output = []  " Clear the output
 | 
						|
        if v:exception =~# 'healthcheck_not_found'
 | 
						|
          call health#report_error('No healthcheck found for "'.name.'" plugin.')
 | 
						|
        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 = [name. ': ' . func, repeat('=', 72)]
 | 
						|
      " 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
 | 
						|
 | 
						|
  " needed for plasticboy/vim-markdown, because it uses fdm=expr
 | 
						|
  normal! zR
 | 
						|
  setlocal nomodified
 | 
						|
  setlocal bufhidden=hide
 | 
						|
  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("\n## " . 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 . ': ' . s:indent_after_line1(a:msg, 4)
 | 
						|
 | 
						|
  " 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, 10)
 | 
						|
      endfor
 | 
						|
    endif
 | 
						|
  endif
 | 
						|
 | 
						|
  return s:help_to_link(output)
 | 
						|
endfunction " }}}
 | 
						|
 | 
						|
" Use {msg} to report information in the current section
 | 
						|
function! health#report_info(msg) abort " {{{
 | 
						|
  call s:collect_output(s:format_report_message('INFO', 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
 | 
						|
  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, '', '']]  " healthchek not found
 | 
						|
    else
 | 
						|
      let healthchecks += map(uniq(sort(paths)),
 | 
						|
            \'<SID>filepath_to_healthcheck(v:val)')
 | 
						|
    end
 | 
						|
  endfor
 | 
						|
  return healthchecks
 | 
						|
endfunction
 |