diff options
Diffstat (limited to 'runtime/autoload/health/provider.vim')
-rw-r--r-- | runtime/autoload/health/provider.vim | 244 |
1 files changed, 198 insertions, 46 deletions
diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index 87d82150b6..0482cb7f3c 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -38,9 +38,10 @@ endfunction " Handler for s:system() function. function! s:system_handler(jobid, data, event) dict abort if a:event ==# 'stderr' - let self.stderr .= join(a:data, '') - if !self.ignore_stderr + if self.add_stderr_to_output let self.output .= join(a:data, '') + else + let self.stderr .= join(a:data, '') endif elseif a:event ==# 'stdout' let self.output .= join(a:data, '') @@ -64,7 +65,7 @@ function! s:system(cmd, ...) abort let stdin = a:0 ? a:1 : '' let ignore_error = a:0 > 2 ? a:3 : 0 let opts = { - \ 'ignore_stderr': a:0 > 1 ? a:2 : 0, + \ 'add_stderr_to_output': a:0 > 1 ? a:2 : 0, \ 'output': '', \ 'stderr': '', \ 'on_stdout': function('s:system_handler'), @@ -89,8 +90,15 @@ function! s:system(cmd, ...) abort call health#report_error(printf('Command timed out: %s', s:shellify(a:cmd))) call jobstop(jobid) elseif s:shell_error != 0 && !ignore_error - call health#report_error(printf("Command error (job=%d, exit code %d): `%s` (in %s)\nOutput: %s\nStderr: %s", - \ jobid, s:shell_error, s:shellify(a:cmd), string(getcwd()), opts.output, opts.stderr)) + let emsg = printf("Command error (job=%d, exit code %d): `%s` (in %s)", + \ jobid, s:shell_error, s:shellify(a:cmd), string(getcwd())) + if !empty(opts.output) + let emsg .= "\noutput: " . opts.output + end + if !empty(opts.stderr) + let emsg .= "\nstderr: " . opts.stderr + end + call health#report_error(emsg) endif return opts.output @@ -155,7 +163,7 @@ function! s:check_clipboard() abort endif endfunction -" Get the latest Neovim Python client (pynvim) version from PyPI. +" Get the latest Nvim Python client (pynvim) version from PyPI. function! s:latest_pypi_version() abort let pypi_version = 'unable to get pypi response' let pypi_response = s:download('https://pypi.python.org/pypi/pynvim/json') @@ -172,7 +180,7 @@ endfunction " Get version information using the specified interpreter. The interpreter is " used directly in case breaking changes were introduced since the last time -" Neovim's Python client was updated. +" Nvim's Python client was updated. " " Returns: [ " {python executable version}, @@ -194,7 +202,8 @@ function! s:version_info(python) abort let nvim_path = s:trim(s:system([ \ a:python, '-c', - \ 'import sys; sys.path.remove(""); ' . + \ 'import sys; ' . + \ 'sys.path = list(filter(lambda x: x != "", sys.path)); ' . \ 'import neovim; print(neovim.__file__)'])) if s:shell_error || empty(nvim_path) return [python_version, 'unable to load neovim Python module', pypi_version, @@ -215,7 +224,7 @@ function! s:version_info(python) abort \ 'print("{}.{}.{}{}".format(v.major, v.minor, v.patch, v.prerelease))'], \ '', 1, 1) if empty(nvim_version) - let nvim_version = 'unable to find neovim Python module version' + let nvim_version = 'unable to find pynvim module version' let base = fnamemodify(nvim_path, ':h') let metas = glob(base.'-*/METADATA', 1, 1) \ + glob(base.'-*/PKG-INFO', 1, 1) @@ -257,6 +266,22 @@ function! s:check_bin(bin) abort return 1 endfunction +" Check "loaded" var for given a:provider. +" Returns 1 if the caller should return (skip checks). +function! s:disabled_via_loaded_var(provider) abort + let loaded_var = 'g:loaded_'.a:provider.'_provider' + if exists(loaded_var) && !exists('*provider#'.a:provider.'#Call') + let v = eval(loaded_var) + if 0 is v + call health#report_info('Disabled ('.loaded_var.'='.v.').') + return 1 + else + call health#report_info('Disabled ('.loaded_var.'='.v.'). This might be due to some previous error.') + endif + endif + return 0 +endfunction + function! s:check_python(version) abort call health#report_start('Python ' . a:version . ' provider (optional)') @@ -264,11 +289,10 @@ function! s:check_python(version) abort let python_exe = '' let venv = exists('$VIRTUAL_ENV') ? resolve($VIRTUAL_ENV) : '' let host_prog_var = pyname.'_host_prog' - let loaded_var = 'g:loaded_'.pyname.'_provider' let python_multiple = [] - if exists(loaded_var) && !exists('*provider#'.pyname.'#Call') - call health#report_info('Disabled ('.loaded_var.'='.eval(loaded_var).'). This might be due to some previous error.') + if s:disabled_via_loaded_var(pyname) + return endif let [pyenv, pyenv_root] = s:check_for_pyenv() @@ -286,7 +310,7 @@ function! s:check_python(version) abort let python_exe = pyname endif - " No Python executable could `import neovim`. + " No Python executable could `import neovim`, or host_prog_var was used. if !empty(pythonx_errors) call health#report_error('Python provider error:', pythonx_errors) @@ -339,7 +363,7 @@ function! s:check_python(version) abort \ && !empty(pyenv_root) && resolve(python_exe) !~# '^'.pyenv_root.'/' call health#report_warn('pyenv is not set up optimally.', [ \ printf('Create a virtualenv specifically ' - \ . 'for Neovim using pyenv, and set `g:%s`. This will avoid ' + \ . 'for Nvim using pyenv, and set `g:%s`. This will avoid ' \ . 'the need to install the pynvim module in each ' \ . 'version/virtualenv.', host_prog_var) \ ]) @@ -353,7 +377,7 @@ function! s:check_python(version) abort if resolve(python_exe) !~# '^'.venv_root.'/' call health#report_warn('Your virtualenv is not set up optimally.', [ \ printf('Create a virtualenv specifically ' - \ . 'for Neovim and use `g:%s`. This will avoid ' + \ . 'for Nvim and use `g:%s`. This will avoid ' \ . 'the need to install the pynvim module in each ' \ . 'virtualenv.', host_prog_var) \ ]) @@ -368,18 +392,6 @@ function! s:check_python(version) abort let python_exe = '' endif - " Check if $VIRTUAL_ENV is valid. - if exists('$VIRTUAL_ENV') && !empty(python_exe) - if $VIRTUAL_ENV ==# matchstr(python_exe, '^\V'.$VIRTUAL_ENV) - call health#report_info('$VIRTUAL_ENV matches executable') - else - call health#report_warn( - \ '$VIRTUAL_ENV exists but appears to be inactive. ' - \ . 'This could lead to unexpected results.', - \ [ 'If you are using Zsh, see: http://vi.stackexchange.com/a/7654' ]) - endif - endif - " Diagnostic output call health#report_info('Executable: ' . (empty(python_exe) ? 'Not found' : python_exe)) if len(python_multiple) @@ -473,12 +485,83 @@ function! s:check_for_pyenv() abort return [pyenv_path, pyenv_root] endfunction +" Resolves Python executable path by invoking and checking `sys.executable`. +function! s:python_exepath(invocation) abort + return s:normalize_path(system(a:invocation + \ . ' -c "import sys; sys.stdout.write(sys.executable)"')) +endfunction + +" Checks that $VIRTUAL_ENV Python executables are found at front of $PATH in +" Nvim and subshells. +function! s:check_virtualenv() abort + call health#report_start('Python virtualenv') + if !exists('$VIRTUAL_ENV') + call health#report_ok('no $VIRTUAL_ENV') + return + endif + let errors = [] + " Keep hints as dict keys in order to discard duplicates. + let hints = {} + " The virtualenv should contain some Python executables, and those + " executables should be first both on Nvim's $PATH and the $PATH of + " subshells launched from Nvim. + let bin_dir = has('win32') ? '/Scripts' : '/bin' + let venv_bins = glob($VIRTUAL_ENV . bin_dir . '/python*', v:true, v:true) + " XXX: Remove irrelevant executables found in bin/. + let venv_bins = filter(venv_bins, 'v:val !~# "python-config"') + if len(venv_bins) + for venv_bin in venv_bins + let venv_bin = s:normalize_path(venv_bin) + let py_bin_basename = fnamemodify(venv_bin, ':t') + let nvim_py_bin = s:python_exepath(exepath(py_bin_basename)) + let subshell_py_bin = s:python_exepath(py_bin_basename) + if venv_bin !=# nvim_py_bin + call add(errors, '$PATH yields this '.py_bin_basename.' executable: '.nvim_py_bin) + let hint = '$PATH ambiguities arise if the virtualenv is not ' + \.'properly activated prior to launching Nvim. Close Nvim, activate the virtualenv, ' + \.'check that invoking Python from the command line launches the correct one, ' + \.'then relaunch Nvim.' + let hints[hint] = v:true + endif + if venv_bin !=# subshell_py_bin + call add(errors, '$PATH in subshells yields this ' + \.py_bin_basename . ' executable: '.subshell_py_bin) + let hint = '$PATH ambiguities in subshells typically are ' + \.'caused by your shell config overriding the $PATH previously set by the ' + \.'virtualenv. Either prevent them from doing so, or use this workaround: ' + \.'https://vi.stackexchange.com/a/7654' + let hints[hint] = v:true + endif + endfor + else + call add(errors, 'no Python executables found in the virtualenv '.bin_dir.' directory.') + endif + + let msg = '$VIRTUAL_ENV is set to: '.$VIRTUAL_ENV + if len(errors) + if len(venv_bins) + let msg .= "\nAnd its ".bin_dir.' directory contains: ' + \.join(map(venv_bins, "fnamemodify(v:val, ':t')"), ', ') + endif + let conj = "\nBut " + for error in errors + let msg .= conj.error + let conj = "\nAnd " + endfor + let msg .= "\nSo invoking Python may lead to unexpected results." + call health#report_warn(msg, keys(hints)) + else + call health#report_info(msg) + call health#report_info('Python version: ' + \.system('python -c "import platform, sys; sys.stdout.write(platform.python_version())"')) + call health#report_ok('$VIRTUAL_ENV provides :!python.') + endif +endfunction + function! s:check_ruby() abort call health#report_start('Ruby provider (optional)') - let loaded_var = 'g:loaded_ruby_provider' - if exists(loaded_var) && !exists('*provider#ruby#Call') - call health#report_info('Disabled. '.loaded_var.'='.eval(loaded_var)) + if s:disabled_via_loaded_var('ruby') return endif @@ -501,7 +584,7 @@ function! s:check_ruby() abort endif call health#report_info('Host: '. host) - let latest_gem_cmd = has('win32') ? 'cmd /c gem list -ra ^^neovim$' : 'gem list -ra ^neovim$' + let latest_gem_cmd = has('win32') ? 'cmd /c gem list -ra "^^neovim$"' : 'gem list -ra ^neovim$' let latest_gem = s:system(split(latest_gem_cmd)) if s:shell_error || empty(latest_gem) call health#report_error('Failed to run: '. latest_gem_cmd, @@ -509,7 +592,7 @@ function! s:check_ruby() abort \ 'Are you behind a firewall or proxy?']) return endif - let latest_gem = get(split(latest_gem, 'neovim (\|, \|)$' ), 1, 'not found') + let latest_gem = get(split(latest_gem, 'neovim (\|, \|)$' ), 0, 'not found') let current_gem_cmd = host .' --version' let current_gem = s:system(current_gem_cmd) @@ -532,9 +615,7 @@ endfunction function! s:check_node() abort call health#report_start('Node.js provider (optional)') - let loaded_var = 'g:loaded_node_provider' - if exists(loaded_var) && !exists('*provider#node#Call') - call health#report_info('Disabled. '.loaded_var.'='.eval(loaded_var)) + if s:disabled_via_loaded_var('node') return endif @@ -546,8 +627,8 @@ function! s:check_node() abort endif let node_v = get(split(s:system('node -v'), "\n"), 0, '') call health#report_info('Node.js: '. node_v) - if !s:shell_error && s:version_cmp(node_v[1:], '6.0.0') < 0 - call health#report_warn('Neovim node.js host does not support '.node_v) + if s:shell_error || s:version_cmp(node_v[1:], '6.0.0') < 0 + call health#report_warn('Nvim node.js host does not support '.node_v) " Skip further checks, they are nonsense if nodejs is too old. return endif @@ -562,7 +643,7 @@ function! s:check_node() abort \ 'Run in shell (if you use yarn): yarn global add neovim']) return endif - call health#report_info('Neovim node.js host: '. host) + call health#report_info('Nvim node.js host: '. host) let manager = executable('npm') ? 'npm' : 'yarn' let latest_npm_cmd = has('win32') ? @@ -575,14 +656,12 @@ function! s:check_node() abort \ 'Are you behind a firewall or proxy?']) return endif - if !empty(latest_npm) - try - let pkg_data = json_decode(latest_npm) - catch /E474/ - return 'error: '.latest_npm - endtry - let latest_npm = get(get(pkg_data, 'dist-tags', {}), 'latest', 'unable to parse') - endif + try + let pkg_data = json_decode(latest_npm) + catch /E474/ + return 'error: '.latest_npm + endtry + let latest_npm = get(get(pkg_data, 'dist-tags', {}), 'latest', 'unable to parse') let current_npm_cmd = ['node', host, '--version'] let current_npm = s:system(current_npm_cmd) @@ -603,10 +682,83 @@ function! s:check_node() abort endif endfunction +function! s:check_perl() abort + call health#report_start('Perl provider (optional)') + + if s:disabled_via_loaded_var('perl') + return + endif + + if !executable('perl') || !executable('cpanm') + call health#report_warn( + \ '`perl` and `cpanm` must be in $PATH.', + \ ['Install Perl and cpanminus and verify that `perl` and `cpanm` commands work.']) + return + endif + let perl_v = get(split(s:system(['perl', '-W', '-e', 'print $^V']), "\n"), 0, '') + call health#report_info('Perl: '. perl_v) + if s:shell_error + call health#report_warn('Nvim perl host does not support '.perl_v) + " Skip further checks, they are nonsense if perl is too old. + return + endif + + let host = provider#perl#Detect() + if empty(host) + call health#report_warn('Missing "Neovim::Ext" cpan module.', + \ ['Run in shell: cpanm Neovim::Ext']) + return + endif + call health#report_info('Nvim perl host: '. host) + + let latest_cpan_cmd = 'cpanm --info -q Neovim::Ext' + let latest_cpan = s:system(latest_cpan_cmd) + if s:shell_error || empty(latest_cpan) + call health#report_error('Failed to run: '. latest_cpan_cmd, + \ ["Make sure you're connected to the internet.", + \ 'Are you behind a firewall or proxy?']) + return + elseif latest_cpan[0] ==# '!' + let cpanm_errs = split(latest_cpan, '!') + if cpanm_errs[0] =~# "Can't write to " + call health#report_warn(cpanm_errs[0], cpanm_errs[1:-2]) + " Last line is the package info + let latest_cpan = cpanm_errs[-1] + else + call health#report_error('Unknown warning from command: ' . latest_cpan_cmd, cpanm_errs) + return + endif + endif + let latest_cpan = matchstr(latest_cpan, '\(\.\?\d\)\+') + if empty(latest_cpan) + call health#report_error('Cannot parse version number from cpanm output: ' . latest_cpan) + return + endif + + let current_cpan_cmd = [host, '-W', '-MNeovim::Ext', '-e', 'print $Neovim::Ext::VERSION'] + let current_cpan = s:system(current_cpan_cmd) + if s:shell_error + call health#report_error('Failed to run: '. string(current_cpan_cmd), + \ ['Report this issue with the output of: ', string(current_cpan_cmd)]) + return + endif + + if s:version_cmp(current_cpan, latest_cpan) == -1 + call health#report_warn( + \ printf('Module "Neovim::Ext" is out-of-date. Installed: %s, latest: %s', + \ current_cpan, latest_cpan), + \ ['Run in shell: cpanm Neovim::Ext']) + else + call health#report_ok('Latest "Neovim::Ext" cpan module is installed: '. current_cpan) + endif +endfunction + function! health#provider#check() abort call s:check_clipboard() call s:check_python(2) call s:check_python(3) + call s:check_virtualenv() call s:check_ruby() call s:check_node() + call s:check_perl() endfunction |