mirror of
https://github.com/neovim/neovim.git
synced 2025-01-01 17:23:36 -07:00
6c39edaa7e
In a few places ipairs was used to iterate over elements of the array. However, the first return value of ipairs was erronously used, which is not the value, but rather the index. This would result in errors, for instance when trying to retrieve a field from the value.
410 lines
13 KiB
Lua
410 lines
13 KiB
Lua
local M = {}
|
|
local health = require('vim.health')
|
|
|
|
local fn_bool = function(key)
|
|
return function(...)
|
|
return vim.fn[key](...) == 1
|
|
end
|
|
end
|
|
|
|
local has = fn_bool('has')
|
|
local executable = fn_bool('executable')
|
|
local empty = fn_bool('empty')
|
|
local filereadable = fn_bool('filereadable')
|
|
local filewritable = fn_bool('filewritable')
|
|
|
|
local shell_error = function()
|
|
return vim.v.shell_error ~= 0
|
|
end
|
|
|
|
local suggest_faq = 'https://github.com/neovim/neovim/wiki/Building-Neovim#optimized-builds'
|
|
|
|
local function check_runtime()
|
|
health.report_start('Runtime')
|
|
-- Files from an old installation.
|
|
local bad_files = {
|
|
['plugin/man.vim'] = false,
|
|
['scripts.vim'] = false,
|
|
['autoload/man.vim'] = false,
|
|
}
|
|
local bad_files_msg = ''
|
|
for k, _ in pairs(bad_files) do
|
|
local path = ('%s/%s'):format(vim.env.VIMRUNTIME, k)
|
|
if vim.loop.fs_stat(path) then
|
|
bad_files[k] = true
|
|
bad_files_msg = ('%s%s\n'):format(bad_files_msg, path)
|
|
end
|
|
end
|
|
|
|
local ok = (bad_files_msg == '')
|
|
local info = ok and health.report_ok or health.report_info
|
|
info(string.format('$VIMRUNTIME: %s', vim.env.VIMRUNTIME))
|
|
if not ok then
|
|
health.report_error(
|
|
string.format(
|
|
'$VIMRUNTIME has files from an old installation (this can cause weird behavior):\n%s',
|
|
bad_files_msg
|
|
),
|
|
{ 'Delete $VIMRUNTIME (or uninstall Nvim), then reinstall Nvim.' }
|
|
)
|
|
end
|
|
end
|
|
|
|
local function check_config()
|
|
health.report_start('Configuration')
|
|
local ok = true
|
|
|
|
local vimrc = (
|
|
empty(vim.env.MYVIMRC) and vim.fn.stdpath('config') .. '/init.vim' or vim.env.MYVIMRC
|
|
)
|
|
if not filereadable(vimrc) then
|
|
ok = false
|
|
local has_vim = filereadable(vim.fn.expand('~/.vimrc'))
|
|
health.report_warn(
|
|
(-1 == vim.fn.getfsize(vimrc) and 'Missing' or 'Unreadable') .. ' user config file: ' .. vimrc,
|
|
{ has_vim and ':help nvim-from-vim' or ':help init.vim' }
|
|
)
|
|
end
|
|
|
|
-- If $VIM is empty we don't care. Else make sure it is valid.
|
|
if not empty(vim.env.VIM) and not filereadable(vim.env.VIM .. '/runtime/doc/nvim.txt') then
|
|
ok = false
|
|
health.report_error('$VIM is invalid: ' .. vim.env.VIM)
|
|
end
|
|
|
|
if vim.env.NVIM_TUI_ENABLE_CURSOR_SHAPE then
|
|
ok = false
|
|
health.report_warn('$NVIM_TUI_ENABLE_CURSOR_SHAPE is ignored in Nvim 0.2+', {
|
|
"Use the 'guicursor' option to configure cursor shape. :help 'guicursor'",
|
|
'https://github.com/neovim/neovim/wiki/Following-HEAD#20170402',
|
|
})
|
|
end
|
|
|
|
if vim.v.ctype == 'C' then
|
|
ok = false
|
|
health.report_error(
|
|
'Locale does not support UTF-8. Unicode characters may not display correctly.'
|
|
.. ('\n$LANG=%s $LC_ALL=%s $LC_CTYPE=%s'):format(
|
|
vim.env.LANG,
|
|
vim.env.LC_ALL,
|
|
vim.env.LC_CTYPE
|
|
),
|
|
{
|
|
'If using tmux, try the -u option.',
|
|
'Ensure that your terminal/shell/tmux/etc inherits the environment, or set $LANG explicitly.',
|
|
'Configure your system locale.',
|
|
}
|
|
)
|
|
end
|
|
|
|
if vim.o.paste == 1 then
|
|
ok = false
|
|
health.report_error(
|
|
"'paste' is enabled. This option is only for pasting text.\nIt should not be set in your config.",
|
|
{
|
|
'Remove `set paste` from your init.vim, if applicable.',
|
|
'Check `:verbose set paste?` to see if a plugin or script set the option.',
|
|
}
|
|
)
|
|
end
|
|
|
|
local writeable = true
|
|
local shadaopt = vim.fn.split(vim.o.shada, ',')
|
|
local shadafile = (
|
|
empty(vim.o.shada) and vim.o.shada
|
|
or vim.fn.substitute(vim.fn.matchstr(shadaopt[#shadaopt], '^n.\\+'), '^n', '', '')
|
|
)
|
|
shadafile = (
|
|
empty(vim.o.shadafile)
|
|
and (empty(shadafile) and vim.fn.stdpath('state') .. '/shada/main.shada' or vim.fn.expand(
|
|
shadafile
|
|
))
|
|
or (vim.o.shadafile == 'NONE' and '' or vim.o.shadafile)
|
|
)
|
|
if not empty(shadafile) and empty(vim.fn.glob(shadafile)) then
|
|
-- Since this may be the first time Nvim has been run, try to create a shada file.
|
|
if not pcall(vim.cmd.wshada) then
|
|
writeable = false
|
|
end
|
|
end
|
|
if
|
|
not writeable
|
|
or (not empty(shadafile) and (not filereadable(shadafile) or not filewritable(shadafile)))
|
|
then
|
|
ok = false
|
|
health.report_error(
|
|
'shada file is not '
|
|
.. ((not writeable or filereadable(shadafile)) and 'writeable' or 'readable')
|
|
.. ':\n'
|
|
.. shadafile
|
|
)
|
|
end
|
|
|
|
if ok then
|
|
health.report_ok('no issues found')
|
|
end
|
|
end
|
|
|
|
local function check_performance()
|
|
health.report_start('Performance')
|
|
|
|
-- Check buildtype
|
|
local buildtype = vim.fn.matchstr(vim.fn.execute('version'), [[\v\cbuild type:?\s*[^\n\r\t ]+]])
|
|
if empty(buildtype) then
|
|
health.report_error('failed to get build type from :version')
|
|
elseif vim.regex([[\v(MinSizeRel|Release|RelWithDebInfo)]]):match_str(buildtype) then
|
|
health.report_ok(buildtype)
|
|
else
|
|
health.report_info(buildtype)
|
|
health.report_warn('Non-optimized debug build. Nvim will be slower.', {
|
|
'Install a different Nvim package, or rebuild with `CMAKE_BUILD_TYPE=RelWithDebInfo`.',
|
|
suggest_faq,
|
|
})
|
|
end
|
|
|
|
-- check for slow shell invocation
|
|
local slow_cmd_time = 1.5
|
|
local start_time = vim.fn.reltime()
|
|
vim.fn.system('echo')
|
|
local elapsed_time = vim.fn.reltimefloat(vim.fn.reltime(start_time))
|
|
if elapsed_time > slow_cmd_time then
|
|
health.report_warn(
|
|
'Slow shell invocation (took ' .. vim.fn.printf('%.2f', elapsed_time) .. ' seconds).'
|
|
)
|
|
end
|
|
end
|
|
|
|
-- Load the remote plugin manifest file and check for unregistered plugins
|
|
local function check_rplugin_manifest()
|
|
health.report_start('Remote Plugins')
|
|
|
|
local existing_rplugins = {}
|
|
for _, item in ipairs(vim.fn['remote#host#PluginsForHost']('python')) do
|
|
existing_rplugins[item.path] = 'python'
|
|
end
|
|
|
|
for _, item in ipairs(vim.fn['remote#host#PluginsForHost']('python3')) do
|
|
existing_rplugins[item.path] = 'python3'
|
|
end
|
|
|
|
local require_update = false
|
|
local handle_path = function(path)
|
|
local python_glob = vim.fn.glob(path .. '/rplugin/python*', true, true)
|
|
if empty(python_glob) then
|
|
return
|
|
end
|
|
|
|
local python_dir = python_glob[1]
|
|
local python_version = vim.fn.fnamemodify(python_dir, ':t')
|
|
|
|
local scripts = vim.fn.glob(python_dir .. '/*.py', true, true)
|
|
vim.list_extend(scripts, vim.fn.glob(python_dir .. '/*/__init__.py', true, true))
|
|
|
|
for _, script in ipairs(scripts) do
|
|
local contents = vim.fn.join(vim.fn.readfile(script))
|
|
if vim.regex([[\<\%(from\|import\)\s\+neovim\>]]):match_str(contents) then
|
|
if vim.regex([[[\/]__init__\.py$]]):match_str(script) then
|
|
script = vim.fn.tr(vim.fn.fnamemodify(script, ':h'), '\\', '/')
|
|
end
|
|
if not existing_rplugins[script] then
|
|
local msg = vim.fn.printf('"%s" is not registered.', vim.fn.fnamemodify(path, ':t'))
|
|
if python_version == 'pythonx' then
|
|
if not has('python3') then
|
|
msg = msg .. ' (python3 not available)'
|
|
end
|
|
elseif not has(python_version) then
|
|
msg = msg .. vim.fn.printf(' (%s not available)', python_version)
|
|
else
|
|
require_update = true
|
|
end
|
|
|
|
health.report_warn(msg)
|
|
end
|
|
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, path in ipairs(vim.fn.map(vim.fn.split(vim.o.runtimepath, ','), 'resolve(v:val)')) do
|
|
handle_path(path)
|
|
end
|
|
|
|
if require_update then
|
|
health.report_warn('Out of date', { 'Run `:UpdateRemotePlugins`' })
|
|
else
|
|
health.report_ok('Up to date')
|
|
end
|
|
end
|
|
|
|
local function check_tmux()
|
|
if empty(vim.env.TMUX) or not executable('tmux') then
|
|
return
|
|
end
|
|
|
|
local get_tmux_option = function(option)
|
|
local cmd = 'tmux show-option -qvg ' .. option -- try global scope
|
|
local out = vim.fn.system(vim.fn.split(cmd))
|
|
local val = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g')
|
|
if shell_error() then
|
|
health.report_error('command failed: ' .. cmd .. '\n' .. out)
|
|
return 'error'
|
|
elseif empty(val) then
|
|
cmd = 'tmux show-option -qvgs ' .. option -- try session scope
|
|
out = vim.fn.system(vim.fn.split(cmd))
|
|
val = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g')
|
|
if shell_error() then
|
|
health.report_error('command failed: ' .. cmd .. '\n' .. out)
|
|
return 'error'
|
|
end
|
|
end
|
|
return val
|
|
end
|
|
|
|
health.report_start('tmux')
|
|
|
|
-- check escape-time
|
|
local suggestions =
|
|
{ 'set escape-time in ~/.tmux.conf:\nset-option -sg escape-time 10', suggest_faq }
|
|
local tmux_esc_time = get_tmux_option('escape-time')
|
|
if tmux_esc_time ~= 'error' then
|
|
if empty(tmux_esc_time) then
|
|
health.report_error('`escape-time` is not set', suggestions)
|
|
elseif tonumber(tmux_esc_time) > 300 then
|
|
health.report_error(
|
|
'`escape-time` (' .. tmux_esc_time .. ') is higher than 300ms',
|
|
suggestions
|
|
)
|
|
else
|
|
health.report_ok('escape-time: ' .. tmux_esc_time)
|
|
end
|
|
end
|
|
|
|
-- check focus-events
|
|
local tmux_focus_events = get_tmux_option('focus-events')
|
|
if tmux_focus_events ~= 'error' then
|
|
if empty(tmux_focus_events) or tmux_focus_events ~= 'on' then
|
|
health.report_warn(
|
|
"`focus-events` is not enabled. |'autoread'| may not work.",
|
|
{ '(tmux 1.9+ only) Set `focus-events` in ~/.tmux.conf:\nset-option -g focus-events on' }
|
|
)
|
|
else
|
|
health.report_ok('focus-events: ' .. tmux_focus_events)
|
|
end
|
|
end
|
|
|
|
-- check default-terminal and $TERM
|
|
health.report_info('$TERM: ' .. vim.env.TERM)
|
|
local cmd = 'tmux show-option -qvg default-terminal'
|
|
local out = vim.fn.system(vim.fn.split(cmd))
|
|
local tmux_default_term = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g')
|
|
if empty(tmux_default_term) then
|
|
cmd = 'tmux show-option -qvgs default-terminal'
|
|
out = vim.fn.system(vim.fn.split(cmd))
|
|
tmux_default_term = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g')
|
|
end
|
|
|
|
if shell_error() then
|
|
health.report_error('command failed: ' .. cmd .. '\n' .. out)
|
|
elseif tmux_default_term ~= vim.env.TERM then
|
|
health.report_info('default-terminal: ' .. tmux_default_term)
|
|
health.report_error(
|
|
'$TERM differs from the tmux `default-terminal` setting. Colors might look wrong.',
|
|
{ '$TERM may have been set by some rc (.bashrc, .zshrc, ...).' }
|
|
)
|
|
elseif not vim.regex([[\v(tmux-256color|screen-256color)]]):match_str(vim.env.TERM) then
|
|
health.report_error(
|
|
'$TERM should be "screen-256color" or "tmux-256color" in tmux. Colors might look wrong.',
|
|
{
|
|
'Set default-terminal in ~/.tmux.conf:\nset-option -g default-terminal "screen-256color"',
|
|
suggest_faq,
|
|
}
|
|
)
|
|
end
|
|
|
|
-- check for RGB capabilities
|
|
local info = vim.fn.system({ 'tmux', 'display-message', '-p', '#{client_termfeatures}' })
|
|
info = vim.split(vim.trim(info), ',', { trimempty = true })
|
|
if not vim.tbl_contains(info, 'RGB') then
|
|
local has_rgb = false
|
|
if #info == 0 then
|
|
-- client_termfeatures may not be supported; fallback to checking show-messages
|
|
info = vim.fn.system({ 'tmux', 'show-messages', '-JT' })
|
|
has_rgb = info:find(' Tc: (flag) true', 1, true) or info:find(' RGB: (flag) true', 1, true)
|
|
end
|
|
if not has_rgb then
|
|
health.report_warn(
|
|
"Neither Tc nor RGB capability set. True colors are disabled. |'termguicolors'| won't work properly.",
|
|
{
|
|
"Put this in your ~/.tmux.conf and replace XXX by your $TERM outside of tmux:\nset-option -sa terminal-features ',XXX:RGB'",
|
|
"For older tmux versions use this instead:\nset-option -ga terminal-overrides ',XXX:Tc'",
|
|
}
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function check_terminal()
|
|
if not executable('infocmp') then
|
|
return
|
|
end
|
|
|
|
health.report_start('terminal')
|
|
local cmd = 'infocmp -L'
|
|
local out = vim.fn.system(vim.fn.split(cmd))
|
|
local kbs_entry = vim.fn.matchstr(out, 'key_backspace=[^,[:space:]]*')
|
|
local kdch1_entry = vim.fn.matchstr(out, 'key_dc=[^,[:space:]]*')
|
|
|
|
if
|
|
shell_error()
|
|
and (
|
|
not has('win32')
|
|
or empty(
|
|
vim.fn.matchstr(
|
|
out,
|
|
[[infocmp: couldn't open terminfo file .\+\%(conemu\|vtpcon\|win32con\)]]
|
|
)
|
|
)
|
|
)
|
|
then
|
|
health.report_error('command failed: ' .. cmd .. '\n' .. out)
|
|
else
|
|
health.report_info(
|
|
vim.fn.printf(
|
|
'key_backspace (kbs) terminfo entry: `%s`',
|
|
(empty(kbs_entry) and '? (not found)' or kbs_entry)
|
|
)
|
|
)
|
|
|
|
health.report_info(
|
|
vim.fn.printf(
|
|
'key_dc (kdch1) terminfo entry: `%s`',
|
|
(empty(kbs_entry) and '? (not found)' or kdch1_entry)
|
|
)
|
|
)
|
|
end
|
|
|
|
for _, env_var in ipairs({
|
|
'XTERM_VERSION',
|
|
'VTE_VERSION',
|
|
'TERM_PROGRAM',
|
|
'COLORTERM',
|
|
'SSH_TTY',
|
|
}) do
|
|
if vim.env[env_var] then
|
|
health.report_info(vim.fn.printf('$%s="%s"', env_var, vim.env[env_var]))
|
|
end
|
|
end
|
|
end
|
|
|
|
function M.check()
|
|
check_config()
|
|
check_runtime()
|
|
check_performance()
|
|
check_rplugin_manifest()
|
|
check_terminal()
|
|
check_tmux()
|
|
end
|
|
|
|
return M
|