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