1let s:shell_error = 0
2
3function! s:is_bad_response(s) abort
4  return a:s =~? '\v(^unable)|(^error)|(^outdated)'
5endfunction
6
7function! s:trim(s) abort
8  return substitute(a:s, '^\_s*\|\_s*$', '', 'g')
9endfunction
10
11" Convert '\' to '/'. Collapse '//' and '/./'.
12function! s:normalize_path(s) abort
13  return substitute(substitute(a:s, '\', '/', 'g'), '/\./\|/\+', '/', 'g')
14endfunction
15
16" Returns TRUE if `cmd` exits with success, else FALSE.
17function! s:cmd_ok(cmd) abort
18  call system(a:cmd)
19  return v:shell_error == 0
20endfunction
21
22" Simple version comparison.
23function! s:version_cmp(a, b) abort
24  let a = split(a:a, '\.', 0)
25  let b = split(a:b, '\.', 0)
26
27  for i in range(len(a))
28    if str2nr(a[i]) > str2nr(b[i])
29      return 1
30    elseif str2nr(a[i]) < str2nr(b[i])
31      return -1
32    endif
33  endfor
34
35  return 0
36endfunction
37
38" Handler for s:system() function.
39function! s:system_handler(jobid, data, event) dict abort
40  if a:event ==# 'stderr'
41    if self.add_stderr_to_output
42      let self.output .= join(a:data, '')
43    else
44      let self.stderr .= join(a:data, '')
45    endif
46  elseif a:event ==# 'stdout'
47    let self.output .= join(a:data, '')
48  elseif a:event ==# 'exit'
49    let s:shell_error = a:data
50  endif
51endfunction
52
53" Attempts to construct a shell command from an args list.
54" Only for display, to help users debug a failed command.
55function! s:shellify(cmd) abort
56  if type(a:cmd) != type([])
57    return a:cmd
58  endif
59  return join(map(copy(a:cmd),
60    \'v:val =~# ''\m[^\-.a-zA-Z_/]'' ? shellescape(v:val) : v:val'), ' ')
61endfunction
62
63" Run a system command and timeout after 30 seconds.
64function! s:system(cmd, ...) abort
65  let stdin = a:0 ? a:1 : ''
66  let ignore_error = a:0 > 2 ? a:3 : 0
67  let opts = {
68        \ 'add_stderr_to_output': a:0 > 1 ? a:2 : 0,
69        \ 'output': '',
70        \ 'stderr': '',
71        \ 'on_stdout': function('s:system_handler'),
72        \ 'on_stderr': function('s:system_handler'),
73        \ 'on_exit': function('s:system_handler'),
74        \ }
75  let jobid = jobstart(a:cmd, opts)
76
77  if jobid < 1
78    call health#report_error(printf('Command error (job=%d): `%s` (in %s)',
79          \ jobid, s:shellify(a:cmd), string(getcwd())))
80    let s:shell_error = 1
81    return opts.output
82  endif
83
84  if !empty(stdin)
85    call jobsend(jobid, stdin)
86  endif
87
88  let res = jobwait([jobid], 30000)
89  if res[0] == -1
90    call health#report_error(printf('Command timed out: %s', s:shellify(a:cmd)))
91    call jobstop(jobid)
92  elseif s:shell_error != 0 && !ignore_error
93    let emsg = printf("Command error (job=%d, exit code %d): `%s` (in %s)",
94          \ jobid, s:shell_error, s:shellify(a:cmd), string(getcwd()))
95    if !empty(opts.output)
96      let emsg .= "\noutput: " . opts.output
97    end
98    if !empty(opts.stderr)
99      let emsg .= "\nstderr: " . opts.stderr
100    end
101    call health#report_error(emsg)
102  endif
103
104  return opts.output
105endfunction
106
107function! s:systemlist(cmd, ...) abort
108  let stdout = split(s:system(a:cmd, a:0 ? a:1 : ''), "\n")
109  if a:0 > 1 && !empty(a:2)
110    return filter(stdout, '!empty(v:val)')
111  endif
112  return stdout
113endfunction
114
115" Fetch the contents of a URL.
116function! s:download(url) abort
117  let has_curl = executable('curl')
118  if has_curl && system(['curl', '-V']) =~# 'Protocols:.*https'
119    let rv = s:system(['curl', '-sL', a:url], '', 1, 1)
120    return s:shell_error ? 'curl error with '.a:url.': '.s:shell_error : rv
121  elseif executable('python')
122    let script = "
123          \try:\n
124          \    from urllib.request import urlopen\n
125          \except ImportError:\n
126          \    from urllib2 import urlopen\n
127          \\n
128          \response = urlopen('".a:url."')\n
129          \print(response.read().decode('utf8'))\n
130          \"
131    let rv = s:system(['python', '-c', script])
132    return empty(rv) && s:shell_error
133          \ ? 'python urllib.request error: '.s:shell_error
134          \ : rv
135  endif
136  return 'missing `curl` '
137          \ .(has_curl ? '(with HTTPS support) ' : '')
138          \ .'and `python`, cannot make web request'
139endfunction
140
141" Check for clipboard tools.
142function! s:check_clipboard() abort
143  call health#report_start('Clipboard (optional)')
144
145  if !empty($TMUX) && executable('tmux') && executable('pbpaste') && !s:cmd_ok('pbpaste')
146    let tmux_version = matchstr(system('tmux -V'), '\d\+\.\d\+')
147    call health#report_error('pbcopy does not work with tmux version: '.tmux_version,
148          \ ['Install tmux 2.6+.  https://superuser.com/q/231130',
149          \  'or use tmux with reattach-to-user-namespace.  https://superuser.com/a/413233'])
150  endif
151
152  let clipboard_tool = provider#clipboard#Executable()
153  if exists('g:clipboard') && empty(clipboard_tool)
154    call health#report_error(
155          \ provider#clipboard#Error(),
156          \ ["Use the example in :help g:clipboard as a template, or don't set g:clipboard at all."])
157  elseif empty(clipboard_tool)
158    call health#report_warn(
159          \ 'No clipboard tool found. Clipboard registers (`"+` and `"*`) will not work.',
160          \ [':help clipboard'])
161  else
162    call health#report_ok('Clipboard tool found: '. clipboard_tool)
163  endif
164endfunction
165
166" Get the latest Nvim Python client (pynvim) version from PyPI.
167function! s:latest_pypi_version() abort
168  let pypi_version = 'unable to get pypi response'
169  let pypi_response = s:download('https://pypi.python.org/pypi/pynvim/json')
170  if !empty(pypi_response)
171    try
172      let pypi_data = json_decode(pypi_response)
173    catch /E474/
174      return 'error: '.pypi_response
175    endtry
176    let pypi_version = get(get(pypi_data, 'info', {}), 'version', 'unable to parse')
177  endif
178  return pypi_version
179endfunction
180
181" Get version information using the specified interpreter.  The interpreter is
182" used directly in case breaking changes were introduced since the last time
183" Nvim's Python client was updated.
184"
185" Returns: [
186"     {python executable version},
187"     {current nvim version},
188"     {current pypi nvim status},
189"     {installed version status}
190" ]
191function! s:version_info(python) abort
192  let pypi_version = s:latest_pypi_version()
193  let python_version = s:trim(s:system([
194        \ a:python,
195        \ '-c',
196        \ 'import sys; print(".".join(str(x) for x in sys.version_info[:3]))',
197        \ ]))
198
199  if empty(python_version)
200    let python_version = 'unable to parse '.a:python.' response'
201  endif
202
203  let nvim_path = s:trim(s:system([
204        \ a:python, '-c',
205        \ 'import sys; ' .
206        \ 'sys.path = list(filter(lambda x: x != "", sys.path)); ' .
207        \ 'import neovim; print(neovim.__file__)']))
208  if s:shell_error || empty(nvim_path)
209    return [python_version, 'unable to load neovim Python module', pypi_version,
210          \ nvim_path]
211  endif
212
213  " Assuming that multiple versions of a package are installed, sort them
214  " numerically in descending order.
215  function! s:compare(metapath1, metapath2) abort
216    let a = matchstr(fnamemodify(a:metapath1, ':p:h:t'), '[0-9.]\+')
217    let b = matchstr(fnamemodify(a:metapath2, ':p:h:t'), '[0-9.]\+')
218    return a == b ? 0 : a > b ? 1 : -1
219  endfunction
220
221  " Try to get neovim.VERSION (added in 0.1.11dev).
222  let nvim_version = s:system([a:python, '-c',
223        \ 'from neovim import VERSION as v; '.
224        \ 'print("{}.{}.{}{}".format(v.major, v.minor, v.patch, v.prerelease))'],
225        \ '', 1, 1)
226  if empty(nvim_version)
227    let nvim_version = 'unable to find pynvim module version'
228    let base = fnamemodify(nvim_path, ':h')
229    let metas = glob(base.'-*/METADATA', 1, 1)
230          \ + glob(base.'-*/PKG-INFO', 1, 1)
231          \ + glob(base.'.egg-info/PKG-INFO', 1, 1)
232    let metas = sort(metas, 's:compare')
233
234    if !empty(metas)
235      for meta_line in readfile(metas[0])
236        if meta_line =~# '^Version:'
237          let nvim_version = matchstr(meta_line, '^Version: \zs\S\+')
238          break
239        endif
240      endfor
241    endif
242  endif
243
244  let nvim_path_base = fnamemodify(nvim_path, ':~:h')
245  let version_status = 'unknown; '.nvim_path_base
246  if !s:is_bad_response(nvim_version) && !s:is_bad_response(pypi_version)
247    if s:version_cmp(nvim_version, pypi_version) == -1
248      let version_status = 'outdated; from '.nvim_path_base
249    else
250      let version_status = 'up to date'
251    endif
252  endif
253
254  return [python_version, nvim_version, pypi_version, version_status]
255endfunction
256
257" Check the Python interpreter's usability.
258function! s:check_bin(bin) abort
259  if !filereadable(a:bin) && (!has('win32') || !filereadable(a:bin.'.exe'))
260    call health#report_error(printf('"%s" was not found.', a:bin))
261    return 0
262  elseif executable(a:bin) != 1
263    call health#report_error(printf('"%s" is not executable.', a:bin))
264    return 0
265  endif
266  return 1
267endfunction
268
269" Check "loaded" var for given a:provider.
270" Returns 1 if the caller should return (skip checks).
271function! s:disabled_via_loaded_var(provider) abort
272  let loaded_var = 'g:loaded_'.a:provider.'_provider'
273  if exists(loaded_var) && !exists('*provider#'.a:provider.'#Call')
274    let v = eval(loaded_var)
275    if 0 is v
276      call health#report_info('Disabled ('.loaded_var.'='.v.').')
277      return 1
278    else
279      call health#report_info('Disabled ('.loaded_var.'='.v.').  This might be due to some previous error.')
280    endif
281  endif
282  return 0
283endfunction
284
285function! s:check_python(version) abort
286  call health#report_start('Python ' . a:version . ' provider (optional)')
287
288  let pyname = 'python'.(a:version == 2 ? '' : '3')
289  let python_exe = ''
290  let venv = exists('$VIRTUAL_ENV') ? resolve($VIRTUAL_ENV) : ''
291  let host_prog_var = pyname.'_host_prog'
292  let python_multiple = []
293
294  if s:disabled_via_loaded_var(pyname)
295    return
296  endif
297
298  let [pyenv, pyenv_root] = s:check_for_pyenv()
299
300  if exists('g:'.host_prog_var)
301    call health#report_info(printf('Using: g:%s = "%s"', host_prog_var, get(g:, host_prog_var)))
302  endif
303
304  let [pyname, pythonx_errors] = provider#pythonx#Detect(a:version)
305
306  if empty(pyname)
307    call health#report_warn('No Python executable found that can `import neovim`. '
308            \ . 'Using the first available executable for diagnostics.')
309  elseif exists('g:'.host_prog_var)
310    let python_exe = pyname
311  endif
312
313  " No Python executable could `import neovim`, or host_prog_var was used.
314  if !empty(pythonx_errors)
315    call health#report_error('Python provider error:', pythonx_errors)
316
317  elseif !empty(pyname) && empty(python_exe)
318    if !exists('g:'.host_prog_var)
319      call health#report_info(printf('`g:%s` is not set.  Searching for '
320            \ . '%s in the environment.', host_prog_var, pyname))
321    endif
322
323    if !empty(pyenv)
324      let python_exe = s:trim(s:system([pyenv, 'which', pyname], '', 1))
325
326      if empty(python_exe)
327        call health#report_warn(printf('pyenv could not find %s.', pyname))
328      endif
329    endif
330
331    if empty(python_exe)
332      let python_exe = exepath(pyname)
333
334      if exists('$PATH')
335        for path in split($PATH, has('win32') ? ';' : ':')
336          let path_bin = s:normalize_path(path.'/'.pyname)
337          if path_bin != s:normalize_path(python_exe)
338                \ && index(python_multiple, path_bin) == -1
339                \ && executable(path_bin)
340            call add(python_multiple, path_bin)
341          endif
342        endfor
343
344        if len(python_multiple)
345          " This is worth noting since the user may install something
346          " that changes $PATH, like homebrew.
347          call health#report_info(printf('Multiple %s executables found.  '
348                \ . 'Set `g:%s` to avoid surprises.', pyname, host_prog_var))
349        endif
350
351        if python_exe =~# '\<shims\>'
352          call health#report_warn(printf('`%s` appears to be a pyenv shim.', python_exe), [
353                      \ '`pyenv` is not in $PATH, your pyenv installation is broken. '
354                      \ .'Set `g:'.host_prog_var.'` to avoid surprises.',
355                      \ ])
356        endif
357      endif
358    endif
359  endif
360
361  if !empty(python_exe) && !exists('g:'.host_prog_var)
362    if empty(venv) && !empty(pyenv)
363          \ && !empty(pyenv_root) && resolve(python_exe) !~# '^'.pyenv_root.'/'
364      call health#report_warn('pyenv is not set up optimally.', [
365            \ printf('Create a virtualenv specifically '
366            \ . 'for Nvim using pyenv, and set `g:%s`.  This will avoid '
367            \ . 'the need to install the pynvim module in each '
368            \ . 'version/virtualenv.', host_prog_var)
369            \ ])
370    elseif !empty(venv)
371      if !empty(pyenv_root)
372        let venv_root = pyenv_root
373      else
374        let venv_root = fnamemodify(venv, ':h')
375      endif
376
377      if resolve(python_exe) !~# '^'.venv_root.'/'
378        call health#report_warn('Your virtualenv is not set up optimally.', [
379              \ printf('Create a virtualenv specifically '
380              \ . 'for Nvim and use `g:%s`.  This will avoid '
381              \ . 'the need to install the pynvim module in each '
382              \ . 'virtualenv.', host_prog_var)
383              \ ])
384      endif
385    endif
386  endif
387
388  if empty(python_exe) && !empty(pyname)
389    " An error message should have already printed.
390    call health#report_error(printf('`%s` was not found.', pyname))
391  elseif !empty(python_exe) && !s:check_bin(python_exe)
392    let python_exe = ''
393  endif
394
395  " Diagnostic output
396  call health#report_info('Executable: ' . (empty(python_exe) ? 'Not found' : python_exe))
397  if len(python_multiple)
398    for path_bin in python_multiple
399      call health#report_info('Other python executable: ' . path_bin)
400    endfor
401  endif
402
403  if empty(python_exe)
404    " No Python executable can import 'neovim'. Check if any Python executable
405    " can import 'pynvim'. If so, that Python failed to import 'neovim' as
406    " well, which is most probably due to a failed pip upgrade:
407    " https://github.com/neovim/neovim/wiki/Following-HEAD#20181118
408    let [pynvim_exe, errors] = provider#pythonx#DetectByModule('pynvim', a:version)
409    if !empty(pynvim_exe)
410      call health#report_error(
411            \ 'Detected pip upgrade failure: Python executable can import "pynvim" but '
412            \ . 'not "neovim": '. pynvim_exe,
413            \ "Use that Python version to reinstall \"pynvim\" and optionally \"neovim\".\n"
414            \ . pynvim_exe ." -m pip uninstall pynvim neovim\n"
415            \ . pynvim_exe ." -m pip install pynvim\n"
416            \ . pynvim_exe ." -m pip install neovim  # only if needed by third-party software")
417    endif
418  else
419    let [pyversion, current, latest, status] = s:version_info(python_exe)
420
421    if a:version != str2nr(pyversion)
422      call health#report_warn('Unexpected Python version.' .
423                  \ ' This could lead to confusing error messages.')
424    endif
425
426    call health#report_info('Python version: ' . pyversion)
427
428    if s:is_bad_response(status)
429      call health#report_info(printf('pynvim version: %s (%s)', current, status))
430    else
431      call health#report_info(printf('pynvim version: %s', current))
432    endif
433
434    if s:is_bad_response(current)
435      call health#report_error(
436        \ "pynvim is not installed.\nError: ".current,
437        \ ['Run in shell: '. python_exe .' -m pip install pynvim'])
438    endif
439
440    if s:is_bad_response(latest)
441      call health#report_warn('Could not contact PyPI to get latest version.')
442      call health#report_error('HTTP request failed: '.latest)
443    elseif s:is_bad_response(status)
444      call health#report_warn(printf('Latest pynvim is NOT installed: %s', latest))
445    elseif !s:is_bad_response(current)
446      call health#report_ok(printf('Latest pynvim is installed.'))
447    endif
448  endif
449endfunction
450
451" Check if pyenv is available and a valid pyenv root can be found, then return
452" their respective paths. If either of those is invalid, return two empty
453" strings, effectivly ignoring pyenv.
454function! s:check_for_pyenv() abort
455  let pyenv_path = resolve(exepath('pyenv'))
456
457  if empty(pyenv_path)
458    return ['', '']
459  endif
460
461  call health#report_info('pyenv: Path: '. pyenv_path)
462
463  let pyenv_root = exists('$PYENV_ROOT') ? resolve($PYENV_ROOT) : ''
464
465  if empty(pyenv_root)
466    let pyenv_root = s:trim(s:system([pyenv_path, 'root']))
467    call health#report_info('pyenv: $PYENV_ROOT is not set. Infer from `pyenv root`.')
468  endif
469
470  if !isdirectory(pyenv_root)
471    call health#report_warn(
472          \ printf('pyenv: Root does not exist: %s. '
473          \ . 'Ignoring pyenv for all following checks.', pyenv_root))
474    return ['', '']
475  endif
476
477  call health#report_info('pyenv: Root: '.pyenv_root)
478
479  return [pyenv_path, pyenv_root]
480endfunction
481
482" Resolves Python executable path by invoking and checking `sys.executable`.
483function! s:python_exepath(invocation) abort
484  return s:normalize_path(system(fnameescape(a:invocation)
485    \ . ' -c "import sys; sys.stdout.write(sys.executable)"'))
486endfunction
487
488" Checks that $VIRTUAL_ENV Python executables are found at front of $PATH in
489" Nvim and subshells.
490function! s:check_virtualenv() abort
491  call health#report_start('Python virtualenv')
492  if !exists('$VIRTUAL_ENV')
493    call health#report_ok('no $VIRTUAL_ENV')
494    return
495  endif
496  let errors = []
497  " Keep hints as dict keys in order to discard duplicates.
498  let hints = {}
499  " The virtualenv should contain some Python executables, and those
500  " executables should be first both on Nvim's $PATH and the $PATH of
501  " subshells launched from Nvim.
502  let bin_dir = has('win32') ? '/Scripts' : '/bin'
503  let venv_bins = glob($VIRTUAL_ENV . bin_dir . '/python*', v:true, v:true)
504  " XXX: Remove irrelevant executables found in bin/.
505  let venv_bins = filter(venv_bins, 'v:val !~# "python-config"')
506  if len(venv_bins)
507    for venv_bin in venv_bins
508      let venv_bin = s:normalize_path(venv_bin)
509      let py_bin_basename = fnamemodify(venv_bin, ':t')
510      let nvim_py_bin = s:python_exepath(exepath(py_bin_basename))
511      let subshell_py_bin = s:python_exepath(py_bin_basename)
512      if venv_bin !=# nvim_py_bin
513        call add(errors, '$PATH yields this '.py_bin_basename.' executable: '.nvim_py_bin)
514        let hint = '$PATH ambiguities arise if the virtualenv is not '
515          \.'properly activated prior to launching Nvim. Close Nvim, activate the virtualenv, '
516          \.'check that invoking Python from the command line launches the correct one, '
517          \.'then relaunch Nvim.'
518        let hints[hint] = v:true
519      endif
520      if venv_bin !=# subshell_py_bin
521        call add(errors, '$PATH in subshells yields this '
522          \.py_bin_basename . ' executable: '.subshell_py_bin)
523        let hint = '$PATH ambiguities in subshells typically are '
524          \.'caused by your shell config overriding the $PATH previously set by the '
525          \.'virtualenv. Either prevent them from doing so, or use this workaround: '
526          \.'https://vi.stackexchange.com/a/34996'
527        let hints[hint] = v:true
528      endif
529    endfor
530  else
531    call add(errors, 'no Python executables found in the virtualenv '.bin_dir.' directory.')
532  endif
533
534  let msg = '$VIRTUAL_ENV is set to: '.$VIRTUAL_ENV
535  if len(errors)
536    if len(venv_bins)
537      let msg .= "\nAnd its ".bin_dir.' directory contains: '
538        \.join(map(venv_bins, "fnamemodify(v:val, ':t')"), ', ')
539    endif
540    let conj = "\nBut "
541    for error in errors
542      let msg .= conj.error
543      let conj = "\nAnd "
544    endfor
545    let msg .= "\nSo invoking Python may lead to unexpected results."
546    call health#report_warn(msg, keys(hints))
547  else
548    call health#report_info(msg)
549    call health#report_info('Python version: '
550      \.system('python -c "import platform, sys; sys.stdout.write(platform.python_version())"'))
551    call health#report_ok('$VIRTUAL_ENV provides :!python.')
552  endif
553endfunction
554
555function! s:check_ruby() abort
556  call health#report_start('Ruby provider (optional)')
557
558  if s:disabled_via_loaded_var('ruby')
559    return
560  endif
561
562  if !executable('ruby') || !executable('gem')
563    call health#report_warn(
564          \ '`ruby` and `gem` must be in $PATH.',
565          \ ['Install Ruby and verify that `ruby` and `gem` commands work.'])
566    return
567  endif
568  call health#report_info('Ruby: '. s:system('ruby -v'))
569
570  let [host, err] = provider#ruby#Detect()
571  if empty(host)
572    call health#report_warn('`neovim-ruby-host` not found.',
573          \ ['Run `gem install neovim` to ensure the neovim RubyGem is installed.',
574          \  'Run `gem environment` to ensure the gem bin directory is in $PATH.',
575          \  'If you are using rvm/rbenv/chruby, try "rehashing".',
576          \  'See :help g:ruby_host_prog for non-standard gem installations.'])
577    return
578  endif
579  call health#report_info('Host: '. host)
580
581  let latest_gem_cmd = has('win32') ? 'cmd /c gem list -ra "^^neovim$"' : 'gem list -ra ^neovim$'
582  let latest_gem = s:system(split(latest_gem_cmd))
583  if s:shell_error || empty(latest_gem)
584    call health#report_error('Failed to run: '. latest_gem_cmd,
585          \ ["Make sure you're connected to the internet.",
586          \  'Are you behind a firewall or proxy?'])
587    return
588  endif
589  let latest_gem = get(split(latest_gem, 'neovim (\|, \|)$' ), 0, 'not found')
590
591  let current_gem_cmd = host .' --version'
592  let current_gem = s:system(current_gem_cmd)
593  if s:shell_error
594    call health#report_error('Failed to run: '. current_gem_cmd,
595          \ ['Report this issue with the output of: ', current_gem_cmd])
596    return
597  endif
598
599  if s:version_cmp(current_gem, latest_gem) == -1
600    call health#report_warn(
601          \ printf('Gem "neovim" is out-of-date. Installed: %s, latest: %s',
602          \ current_gem, latest_gem),
603          \ ['Run in shell: gem update neovim'])
604  else
605    call health#report_ok('Latest "neovim" gem is installed: '. current_gem)
606  endif
607endfunction
608
609function! s:check_node() abort
610  call health#report_start('Node.js provider (optional)')
611
612  if s:disabled_via_loaded_var('node')
613    return
614  endif
615
616  if !executable('node') || (!executable('npm') && !executable('yarn'))
617    call health#report_warn(
618          \ '`node` and `npm` (or `yarn`) must be in $PATH.',
619          \ ['Install Node.js and verify that `node` and `npm` (or `yarn`) commands work.'])
620    return
621  endif
622  let node_v = get(split(s:system('node -v'), "\n"), 0, '')
623  call health#report_info('Node.js: '. node_v)
624  if s:shell_error || s:version_cmp(node_v[1:], '6.0.0') < 0
625    call health#report_warn('Nvim node.js host does not support '.node_v)
626    " Skip further checks, they are nonsense if nodejs is too old.
627    return
628  endif
629  if !provider#node#can_inspect()
630    call health#report_warn('node.js on this system does not support --inspect-brk so $NVIM_NODE_HOST_DEBUG is ignored.')
631  endif
632
633  let [host, err] = provider#node#Detect()
634  if empty(host)
635    call health#report_warn('Missing "neovim" npm (or yarn) package.',
636          \ ['Run in shell: npm install -g neovim',
637          \  'Run in shell (if you use yarn): yarn global add neovim'])
638    return
639  endif
640  call health#report_info('Nvim node.js host: '. host)
641
642  let manager = executable('npm') ? 'npm' : 'yarn'
643  let latest_npm_cmd = has('win32') ?
644        \ 'cmd /c '. manager .' info neovim --json' :
645        \ manager .' info neovim --json'
646  let latest_npm = s:system(split(latest_npm_cmd))
647  if s:shell_error || empty(latest_npm)
648    call health#report_error('Failed to run: '. latest_npm_cmd,
649          \ ["Make sure you're connected to the internet.",
650          \  'Are you behind a firewall or proxy?'])
651    return
652  endif
653  try
654    let pkg_data = json_decode(latest_npm)
655  catch /E474/
656    return 'error: '.latest_npm
657  endtry
658  let latest_npm = get(get(pkg_data, 'dist-tags', {}), 'latest', 'unable to parse')
659
660  let current_npm_cmd = ['node', host, '--version']
661  let current_npm = s:system(current_npm_cmd)
662  if s:shell_error
663    call health#report_error('Failed to run: '. string(current_npm_cmd),
664          \ ['Report this issue with the output of: ', string(current_npm_cmd)])
665    return
666  endif
667
668  if s:version_cmp(current_npm, latest_npm) == -1
669    call health#report_warn(
670          \ printf('Package "neovim" is out-of-date. Installed: %s, latest: %s',
671          \ current_npm, latest_npm),
672          \ ['Run in shell: npm install -g neovim',
673          \  'Run in shell (if you use yarn): yarn global add neovim'])
674  else
675    call health#report_ok('Latest "neovim" npm/yarn package is installed: '. current_npm)
676  endif
677endfunction
678
679function! s:check_perl() abort
680  call health#report_start('Perl provider (optional)')
681
682  if s:disabled_via_loaded_var('perl')
683    return
684  endif
685
686  let [perl_exec, perl_errors] = provider#perl#Detect()
687  if empty(perl_exec)
688    if !empty(perl_errors)
689      call health#report_error('perl provider error:', perl_errors)
690	else
691      call health#report_warn('No usable perl executable found')
692    endif
693	return
694  endif
695
696  call health#report_info('perl executable: '. perl_exec)
697
698  " we cannot use cpanm that is on the path, as it may not be for the perl
699  " set with g:perl_host_prog
700  call s:system([perl_exec, '-W', '-MApp::cpanminus', '-e', ''])
701  if s:shell_error
702    return [perl_exec, '"App::cpanminus" module is not installed']
703  endif
704
705  let latest_cpan_cmd = [perl_exec,
706			  \ '-MApp::cpanminus::fatscript', '-e',
707			  \ 'my $app = App::cpanminus::script->new;
708			  \ $app->parse_options ("--info", "-q", "Neovim::Ext");
709			  \ exit $app->doit']
710
711  let latest_cpan = s:system(latest_cpan_cmd)
712  if s:shell_error || empty(latest_cpan)
713    call health#report_error('Failed to run: '. join(latest_cpan_cmd, " "),
714          \ ["Make sure you're connected to the internet.",
715          \  'Are you behind a firewall or proxy?'])
716    return
717  elseif latest_cpan[0] ==# '!'
718    let cpanm_errs = split(latest_cpan, '!')
719    if cpanm_errs[0] =~# "Can't write to "
720      call health#report_warn(cpanm_errs[0], cpanm_errs[1:-2])
721      " Last line is the package info
722      let latest_cpan = cpanm_errs[-1]
723    else
724      call health#report_error('Unknown warning from command: ' . latest_cpan_cmd, cpanm_errs)
725      return
726    endif
727  endif
728  let latest_cpan = matchstr(latest_cpan, '\(\.\?\d\)\+')
729  if empty(latest_cpan)
730    call health#report_error('Cannot parse version number from cpanm output: ' . latest_cpan)
731    return
732  endif
733
734  let current_cpan_cmd = [perl_exec, '-W', '-MNeovim::Ext', '-e', 'print $Neovim::Ext::VERSION']
735  let current_cpan = s:system(current_cpan_cmd)
736  if s:shell_error
737    call health#report_error('Failed to run: '. string(current_cpan_cmd),
738          \ ['Report this issue with the output of: ', string(current_cpan_cmd)])
739    return
740  endif
741
742  if s:version_cmp(current_cpan, latest_cpan) == -1
743    call health#report_warn(
744          \ printf('Module "Neovim::Ext" is out-of-date. Installed: %s, latest: %s',
745          \ current_cpan, latest_cpan),
746          \ ['Run in shell: cpanm -n Neovim::Ext'])
747  else
748    call health#report_ok('Latest "Neovim::Ext" cpan module is installed: '. current_cpan)
749  endif
750endfunction
751
752function! health#provider#check() abort
753  call s:check_clipboard()
754  call s:check_python(2)
755  call s:check_python(3)
756  call s:check_virtualenv()
757  call s:check_ruby()
758  call s:check_node()
759  call s:check_perl()
760endfunction
761