diff options
Diffstat (limited to 'runtime/autoload')
25 files changed, 1584 insertions, 1806 deletions
diff --git a/runtime/autoload/cargo.vim b/runtime/autoload/cargo.vim new file mode 100644 index 0000000000..6696b3105f --- /dev/null +++ b/runtime/autoload/cargo.vim @@ -0,0 +1,149 @@ +" Last Modified: 2023-09-11 + +function! cargo#Load() + " Utility call to get this script loaded, for debugging +endfunction + +function! cargo#cmd(args) abort + " Trim trailing spaces. This is necessary since :terminal command parses + " trailing spaces as an empty argument. + let args = substitute(a:args, '\s\+$', '', '') + if exists('g:cargo_shell_command_runner') + let cmd = g:cargo_shell_command_runner + elseif has('terminal') + let cmd = 'terminal' + elseif has('nvim') + let cmd = 'noautocmd new | terminal' + else + let cmd = '!' + endif + execute cmd 'cargo' args +endfunction + +function! s:nearest_cargo(...) abort + " If the second argument is not specified, the first argument determines + " whether we will start from the current directory or the directory of the + " current buffer, otherwise, we start with the provided path on the + " second argument. + + let l:is_getcwd = get(a:, 1, 0) + if l:is_getcwd + let l:starting_path = get(a:, 2, getcwd()) + else + let l:starting_path = get(a:, 2, expand('%:p:h')) + endif + + return findfile('Cargo.toml', l:starting_path . ';') +endfunction + +function! cargo#nearestCargo(is_getcwd) abort + return s:nearest_cargo(a:is_getcwd) +endfunction + +function! cargo#nearestWorkspaceCargo(is_getcwd) abort + let l:nearest = s:nearest_cargo(a:is_getcwd) + while l:nearest !=# '' + for l:line in readfile(l:nearest, '', 0x100) + if l:line =~# '\V[workspace]' + return l:nearest + endif + endfor + let l:next = fnamemodify(l:nearest, ':p:h:h') + let l:nearest = s:nearest_cargo(0, l:next) + endwhile + return '' +endfunction + +function! cargo#nearestRootCargo(is_getcwd) abort + " Try to find a workspace Cargo.toml, and if not found, take the nearest + " regular Cargo.toml + let l:workspace_cargo = cargo#nearestWorkspaceCargo(a:is_getcwd) + if l:workspace_cargo !=# '' + return l:workspace_cargo + endif + return s:nearest_cargo(a:is_getcwd) +endfunction + + +function! cargo#build(args) + call cargo#cmd("build " . a:args) +endfunction + +function! cargo#check(args) + call cargo#cmd("check " . a:args) +endfunction + +function! cargo#clean(args) + call cargo#cmd("clean " . a:args) +endfunction + +function! cargo#doc(args) + call cargo#cmd("doc " . a:args) +endfunction + +function! cargo#new(args) + call cargo#cmd("new " . a:args) + cd `=a:args` +endfunction + +function! cargo#init(args) + call cargo#cmd("init " . a:args) +endfunction + +function! cargo#run(args) + call cargo#cmd("run " . a:args) +endfunction + +function! cargo#test(args) + call cargo#cmd("test " . a:args) +endfunction + +function! cargo#bench(args) + call cargo#cmd("bench " . a:args) +endfunction + +function! cargo#update(args) + call cargo#cmd("update " . a:args) +endfunction + +function! cargo#search(args) + call cargo#cmd("search " . a:args) +endfunction + +function! cargo#publish(args) + call cargo#cmd("publish " . a:args) +endfunction + +function! cargo#install(args) + call cargo#cmd("install " . a:args) +endfunction + +function! cargo#runtarget(args) + let l:filename = expand('%:p') + let l:read_manifest = system('cargo read-manifest') + let l:metadata = json_decode(l:read_manifest) + let l:targets = get(l:metadata, 'targets', []) + let l:did_run = 0 + for l:target in l:targets + let l:src_path = get(l:target, 'src_path', '') + let l:kinds = get(l:target, 'kind', []) + let l:name = get(l:target, 'name', '') + if l:src_path == l:filename + if index(l:kinds, 'example') != -1 + let l:did_run = 1 + call cargo#run("--example " . shellescape(l:name) . " " . a:args) + return + elseif index(l:kinds, 'bin') != -1 + let l:did_run = 1 + call cargo#run("--bin " . shellescape(l:name) . " " . a:args) + return + endif + endif + endfor + if l:did_run != 1 + call cargo#run(a:args) + return + endif +endfunction + +" vim: set et sw=4 sts=4 ts=8: diff --git a/runtime/autoload/cargo/quickfix.vim b/runtime/autoload/cargo/quickfix.vim new file mode 100644 index 0000000000..f2a006f6c5 --- /dev/null +++ b/runtime/autoload/cargo/quickfix.vim @@ -0,0 +1,29 @@ +" Last Modified: 2023-09-11 + +function! cargo#quickfix#CmdPre() abort + if &filetype ==# 'rust' && get(b:, 'current_compiler', '') ==# 'cargo' && + \ &makeprg =~ '\V\^cargo\ \.\*' + " Preserve the current directory, and 'lcd' to the nearest Cargo file. + let b:rust_compiler_cargo_qf_has_lcd = haslocaldir() + let b:rust_compiler_cargo_qf_prev_cd = getcwd() + let b:rust_compiler_cargo_qf_prev_cd_saved = 1 + let l:nearest = fnamemodify(cargo#nearestRootCargo(0), ':h') + execute 'lchdir! '.l:nearest + else + let b:rust_compiler_cargo_qf_prev_cd_saved = 0 + endif +endfunction + +function! cargo#quickfix#CmdPost() abort + if exists("b:rust_compiler_cargo_qf_prev_cd_saved") && b:rust_compiler_cargo_qf_prev_cd_saved + " Restore the current directory. + if b:rust_compiler_cargo_qf_has_lcd + execute 'lchdir! '.b:rust_compiler_cargo_qf_prev_cd + else + execute 'chdir! '.b:rust_compiler_cargo_qf_prev_cd + endif + let b:rust_compiler_cargo_qf_prev_cd_saved = 0 + endif +endfunction + +" vim: set et sw=4 sts=4 ts=8: diff --git a/runtime/autoload/ccomplete.lua b/runtime/autoload/ccomplete.lua index f4a3eabd9a..ce85e84f7a 100644 --- a/runtime/autoload/ccomplete.lua +++ b/runtime/autoload/ccomplete.lua @@ -24,10 +24,11 @@ local SearchMembers = nil -- vim9script -- # Vim completion script --- # Language: C --- # Maintainer: Bram Moolenaar <Bram@vim.org> +-- # Language: C +-- # Maintainer: The Vim Project <https://github.com/vim/vim> +-- # Last Change: 2023 Aug 10 -- # Rewritten in Vim9 script by github user lacygoill --- # Last Change: 2022 Jan 31 +-- # Former Maintainer: Bram Moolenaar <Bram@vim.org> prepended = '' grepCache = vim.empty_dict() diff --git a/runtime/autoload/dist/vim.vim b/runtime/autoload/dist/vim.vim new file mode 100644 index 0000000000..021244c93b --- /dev/null +++ b/runtime/autoload/dist/vim.vim @@ -0,0 +1,32 @@ +" Vim runtime support library, +" runs the vim9 script version or legacy script version +" on demand (mostly for Neovim compatability) +" +" Maintainer: The Vim Project <https://github.com/vim/vim> +" Last Change: 2023 Nov 04 + + +" enable the zip and gzip plugin by default, if not set +if !exists('g:zip_exec') + let g:zip_exec = 1 +endif + +if !exists('g:gzip_exec') + let g:gzip_exec = 1 +endif + +if !has('vim9script') + function dist#vim#IsSafeExecutable(filetype, executable) + let cwd = getcwd() + return get(g:, a:filetype .. '_exec', get(g:, 'plugin_exec', 0)) && + \ (fnamemodify(exepath(a:executable), ':p:h') !=# cwd + \ || (split($PATH, has('win32') ? ';' : ':')->index(cwd) != -1 && + \ cwd != '.')) + endfunction + + finish +endif + +def dist#vim#IsSafeExecutable(filetype: string, executable: string): bool + return dist#vim9#IsSafeExecutable(filetype, executable) +enddef diff --git a/runtime/autoload/gzip.vim b/runtime/autoload/gzip.vim index e4adec0947..26b1cda034 100644 --- a/runtime/autoload/gzip.vim +++ b/runtime/autoload/gzip.vim @@ -1,6 +1,7 @@ " Vim autoload file for editing compressed files. -" Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2016 Sep 28 +" Maintainer: The Vim Project <https://github.com/vim/vim> +" Last Change: 2023 Aug 10 +" Former Maintainer: Bram Moolenaar <Bram@vim.org> " These functions are used by the gzip plugin. @@ -9,12 +10,17 @@ fun s:check(cmd) let name = substitute(a:cmd, '\(\S*\).*', '\1', '') if !exists("s:have_" . name) + " safety check, don't execute anything from the current directory + let f = dist#vim#IsSafeExecutable('gzip', name) + if !f + echoerr "Warning: NOT executing " .. name .. " from current directory!" + endif let e = executable(name) if e < 0 let r = system(name . " --version") let e = (r !~ "not found" && r != "") endif - exe "let s:have_" . name . "=" . e + exe "let s:have_" . name . "=" . (e && f) endif exe "return s:have_" . name endfun diff --git a/runtime/autoload/health.vim b/runtime/autoload/health.vim index 5fd4627b11..a7dbedab08 100644 --- a/runtime/autoload/health.vim +++ b/runtime/autoload/health.vim @@ -1,207 +1,40 @@ -" 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) - - " Create buffer and open in a tab, unless this is the default buffer when Nvim starts. - let emptybuf = (bufnr('$') == 1 && empty(getline(1)) && 1 == line('$')) - execute (emptybuf ? 'buffer' : 'tab sbuffer') nvim_create_buf(v:true, v:true) - if bufexists('health://') - bwipe health:// - endif - file health:// - setfiletype checkhealth - - 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) - " in the event the healthcheck doesn't return anything - " (the plugin author should avoid this possibility) - if len(s:output) == 0 - throw 'healthcheck_no_return_value' - endif - catch - let s:output = [] " Clear the output - if v:exception =~# 'healthcheck_not_found' - call health#report_error('No healthcheck found for "'.name.'" plugin.') - elseif v:exception =~# 'healthcheck_no_return_value' - call health#report_error('The healthcheck report for "'.name.'" plugin is empty.') - 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 = [repeat('=', 78), name .. ': ' .. func, ''] - " 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 +function! s:deprecate(type) abort + let deprecate = v:lua.vim.deprecate('health#report_' . a:type, 'vim.health.' . a:type, '0.11') + redraw | echo 'Running healthchecks...' + if deprecate isnot v:null + call v:lua.vim.health.warn(deprecate) endif - - " Clear the 'Running healthchecks...' message. - 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(printf("\n%s ~", a:name)) + call v:lua.vim.health.start(a:name) + call s:deprecate('start') 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") +function! health#report_info(msg) abort + call v:lua.vim.health.info(a:msg) + call s:deprecate('info') 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') +function! health#report_ok(msg) abort + call v:lua.vim.health.ok(a:msg) + call s:deprecate('ok') 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 .. (empty(a:status) ? '' : ' ') .. s:indent_after_line1(a:msg, 2) - - " Optional parameters +function! health#report_warn(msg, ...) abort 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, 6) - endfor - endif - endif - - return s:help_to_link(output) -endfunction " }}} - -" Reports a message as a listitem in the current section. -function! health#report_info(msg) abort " {{{ - call s:collect_output(s:format_report_message('', 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)) + call v:lua.vim.health.warn(a:msg, a:1) else - call s:collect_output(s:format_report_message('WARNING', a:msg)) + call v:lua.vim.health.warn(a:msg) endif -endfunction " }}} + call s:deprecate('warn') +endfunction -" Reports a failed healthcheck. -" a:1: Optional advice (string or list) -function! health#report_error(msg, ...) abort " {{{ +function! health#report_error(msg, ...) abort if a:0 > 0 - call s:collect_output(s:format_report_message('ERROR', a:msg, a:1)) + call v:lua.vim.health.error(a:msg, a:1) else - call s:collect_output(s:format_report_message('ERROR', a:msg)) + call v:lua.vim.health.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 - try - " vim.health is not a healthcheck, skip it - call remove(output, 'vim') - catch - endtry - 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, '', '']] " healthcheck not found - else - let healthchecks += map(uniq(sort(paths)), - \'<SID>filepath_to_healthcheck(v:val)') - end - endfor - return healthchecks + call s:deprecate('error') endfunction diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim deleted file mode 100644 index d104bcfd67..0000000000 --- a/runtime/autoload/health/provider.vim +++ /dev/null @@ -1,771 +0,0 @@ -let s:shell_error = 0 - -function! s:is_bad_response(s) abort - return a:s =~? '\v(^unable)|(^error)|(^outdated)' -endfunction - -function! s:trim(s) abort - return substitute(a:s, '^\_s*\|\_s*$', '', 'g') -endfunction - -" Convert '\' to '/'. Collapse '//' and '/./'. -function! s:normalize_path(s) abort - return substitute(substitute(a:s, '\', '/', 'g'), '/\./\|/\+', '/', 'g') -endfunction - -" Returns TRUE if `cmd` exits with success, else FALSE. -function! s:cmd_ok(cmd) abort - call system(a:cmd) - return v:shell_error == 0 -endfunction - -" Simple version comparison. -function! s:version_cmp(a, b) abort - let a = split(a:a, '\.', 0) - let b = split(a:b, '\.', 0) - - for i in range(len(a)) - if str2nr(a[i]) > str2nr(b[i]) - return 1 - elseif str2nr(a[i]) < str2nr(b[i]) - return -1 - endif - endfor - - return 0 -endfunction - -" Handler for s:system() function. -function! s:system_handler(jobid, data, event) dict abort - if a:event ==# '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, '') - elseif a:event ==# 'exit' - let s:shell_error = a:data - endif -endfunction - -" Attempts to construct a shell command from an args list. -" Only for display, to help users debug a failed command. -function! s:shellify(cmd) abort - if type(a:cmd) != type([]) - return a:cmd - endif - return join(map(copy(a:cmd), - \'v:val =~# ''\m[^\-.a-zA-Z_/]'' ? shellescape(v:val) : v:val'), ' ') -endfunction - -" Run a system command and timeout after 30 seconds. -function! s:system(cmd, ...) abort - let stdin = a:0 ? a:1 : '' - let ignore_error = a:0 > 2 ? a:3 : 0 - let opts = { - \ 'add_stderr_to_output': a:0 > 1 ? a:2 : 0, - \ 'output': '', - \ 'stderr': '', - \ 'on_stdout': function('s:system_handler'), - \ 'on_stderr': function('s:system_handler'), - \ 'on_exit': function('s:system_handler'), - \ } - let jobid = jobstart(a:cmd, opts) - - if jobid < 1 - call health#report_error(printf('Command error (job=%d): `%s` (in %s)', - \ jobid, s:shellify(a:cmd), string(getcwd()))) - let s:shell_error = 1 - return opts.output - endif - - if !empty(stdin) - call jobsend(jobid, stdin) - endif - - let res = jobwait([jobid], 30000) - if res[0] == -1 - call health#report_error(printf('Command timed out: %s', s:shellify(a:cmd))) - call jobstop(jobid) - elseif s:shell_error != 0 && !ignore_error - 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 -endfunction - -function! s:systemlist(cmd, ...) abort - let stdout = split(s:system(a:cmd, a:0 ? a:1 : ''), "\n") - if a:0 > 1 && !empty(a:2) - return filter(stdout, '!empty(v:val)') - endif - return stdout -endfunction - -" Fetch the contents of a URL. -function! s:download(url) abort - let has_curl = executable('curl') - if has_curl && system(['curl', '-V']) =~# 'Protocols:.*https' - let rv = s:system(['curl', '-sL', a:url], '', 1, 1) - return s:shell_error ? 'curl error with '.a:url.': '.s:shell_error : rv - elseif executable('python') - let script = " - \try:\n - \ from urllib.request import urlopen\n - \except ImportError:\n - \ from urllib2 import urlopen\n - \\n - \response = urlopen('".a:url."')\n - \print(response.read().decode('utf8'))\n - \" - let rv = s:system(['python', '-c', script]) - return empty(rv) && s:shell_error - \ ? 'python urllib.request error: '.s:shell_error - \ : rv - endif - return 'missing `curl` ' - \ .(has_curl ? '(with HTTPS support) ' : '') - \ .'and `python`, cannot make web request' -endfunction - -" Check for clipboard tools. -function! s:check_clipboard() abort - call health#report_start('Clipboard (optional)') - - if !empty($TMUX) && executable('tmux') && executable('pbpaste') && !s:cmd_ok('pbpaste') - let tmux_version = matchstr(system('tmux -V'), '\d\+\.\d\+') - call health#report_error('pbcopy does not work with tmux version: '.tmux_version, - \ ['Install tmux 2.6+. https://superuser.com/q/231130', - \ 'or use tmux with reattach-to-user-namespace. https://superuser.com/a/413233']) - endif - - let clipboard_tool = provider#clipboard#Executable() - if exists('g:clipboard') && empty(clipboard_tool) - call health#report_error( - \ provider#clipboard#Error(), - \ ["Use the example in :help g:clipboard as a template, or don't set g:clipboard at all."]) - elseif empty(clipboard_tool) - call health#report_warn( - \ 'No clipboard tool found. Clipboard registers (`"+` and `"*`) will not work.', - \ [':help clipboard']) - else - call health#report_ok('Clipboard tool found: '. clipboard_tool) - endif -endfunction - -" 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') - if !empty(pypi_response) - try - let pypi_data = json_decode(pypi_response) - catch /E474/ - return 'error: '.pypi_response - endtry - let pypi_version = get(get(pypi_data, 'info', {}), 'version', 'unable to parse') - endif - return pypi_version -endfunction - -" Get version information using the specified interpreter. The interpreter is -" used directly in case breaking changes were introduced since the last time -" Nvim's Python client was updated. -" -" Returns: [ -" {python executable version}, -" {current nvim version}, -" {current pypi nvim status}, -" {installed version status} -" ] -function! s:version_info(python) abort - let pypi_version = s:latest_pypi_version() - let python_version = s:trim(s:system([ - \ a:python, - \ '-c', - \ 'import sys; print(".".join(str(x) for x in sys.version_info[:3]))', - \ ])) - - if empty(python_version) - let python_version = 'unable to parse '.a:python.' response' - endif - - let nvim_path = s:trim(s:system([ - \ a:python, '-c', - \ 'import sys; ' . - \ 'sys.path = [p for p in sys.path if p != ""]; ' . - \ 'import neovim; print(neovim.__file__)'])) - if s:shell_error || empty(nvim_path) - return [python_version, 'unable to load neovim Python module', pypi_version, - \ nvim_path] - endif - - " Assuming that multiple versions of a package are installed, sort them - " numerically in descending order. - function! s:compare(metapath1, metapath2) abort - let a = matchstr(fnamemodify(a:metapath1, ':p:h:t'), '[0-9.]\+') - let b = matchstr(fnamemodify(a:metapath2, ':p:h:t'), '[0-9.]\+') - return a == b ? 0 : a > b ? 1 : -1 - endfunction - - " Try to get neovim.VERSION (added in 0.1.11dev). - let nvim_version = s:system([a:python, '-c', - \ 'from neovim import VERSION as v; '. - \ 'print("{}.{}.{}{}".format(v.major, v.minor, v.patch, v.prerelease))'], - \ '', 1, 1) - if empty(nvim_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) - \ + glob(base.'.egg-info/PKG-INFO', 1, 1) - let metas = sort(metas, 's:compare') - - if !empty(metas) - for meta_line in readfile(metas[0]) - if meta_line =~# '^Version:' - let nvim_version = matchstr(meta_line, '^Version: \zs\S\+') - break - endif - endfor - endif - endif - - let nvim_path_base = fnamemodify(nvim_path, ':~:h') - let version_status = 'unknown; '.nvim_path_base - if !s:is_bad_response(nvim_version) && !s:is_bad_response(pypi_version) - if s:version_cmp(nvim_version, pypi_version) == -1 - let version_status = 'outdated; from '.nvim_path_base - else - let version_status = 'up to date' - endif - endif - - return [python_version, nvim_version, pypi_version, version_status] -endfunction - -" Check the Python interpreter's usability. -function! s:check_bin(bin) abort - if !filereadable(a:bin) && (!has('win32') || !filereadable(a:bin.'.exe')) - call health#report_error(printf('"%s" was not found.', a:bin)) - return 0 - elseif executable(a:bin) != 1 - call health#report_error(printf('"%s" is not executable.', a:bin)) - return 0 - endif - 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() abort - call health#report_start('Python 3 provider (optional)') - - let pyname = 'python3' - let python_exe = '' - let venv = exists('$VIRTUAL_ENV') ? resolve($VIRTUAL_ENV) : '' - let host_prog_var = pyname.'_host_prog' - let python_multiple = [] - - if s:disabled_via_loaded_var(pyname) - return - endif - - let [pyenv, pyenv_root] = s:check_for_pyenv() - - if exists('g:'.host_prog_var) - call health#report_info(printf('Using: g:%s = "%s"', host_prog_var, get(g:, host_prog_var))) - endif - - let [pyname, pythonx_warnings] = provider#pythonx#Detect(3) - - if empty(pyname) - call health#report_warn('No Python executable found that can `import neovim`. ' - \ . 'Using the first available executable for diagnostics.') - elseif exists('g:'.host_prog_var) - let python_exe = pyname - endif - - " No Python executable could `import neovim`, or host_prog_var was used. - if !empty(pythonx_warnings) - call health#report_warn(pythonx_warnings, ['See :help provider-python for more information.', - \ 'You may disable this provider (and warning) by adding `let g:loaded_python3_provider = 0` to your init.vim']) - - elseif !empty(pyname) && empty(python_exe) - if !exists('g:'.host_prog_var) - call health#report_info(printf('`g:%s` is not set. Searching for ' - \ . '%s in the environment.', host_prog_var, pyname)) - endif - - if !empty(pyenv) - let python_exe = s:trim(s:system([pyenv, 'which', pyname], '', 1)) - - if empty(python_exe) - call health#report_warn(printf('pyenv could not find %s.', pyname)) - endif - endif - - if empty(python_exe) - let python_exe = exepath(pyname) - - if exists('$PATH') - for path in split($PATH, has('win32') ? ';' : ':') - let path_bin = s:normalize_path(path.'/'.pyname) - if path_bin != s:normalize_path(python_exe) - \ && index(python_multiple, path_bin) == -1 - \ && executable(path_bin) - call add(python_multiple, path_bin) - endif - endfor - - if len(python_multiple) - " This is worth noting since the user may install something - " that changes $PATH, like homebrew. - call health#report_info(printf('Multiple %s executables found. ' - \ . 'Set `g:%s` to avoid surprises.', pyname, host_prog_var)) - endif - - if python_exe =~# '\<shims\>' - call health#report_warn(printf('`%s` appears to be a pyenv shim.', python_exe), [ - \ '`pyenv` is not in $PATH, your pyenv installation is broken. ' - \ .'Set `g:'.host_prog_var.'` to avoid surprises.', - \ ]) - endif - endif - endif - endif - - if !empty(python_exe) && !exists('g:'.host_prog_var) - if empty(venv) && !empty(pyenv) - \ && !empty(pyenv_root) && resolve(python_exe) !~# '^'.pyenv_root.'/' - call health#report_warn('pyenv is not set up optimally.', [ - \ printf('Create a virtualenv specifically ' - \ . '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) - \ ]) - elseif !empty(venv) - if !empty(pyenv_root) |