fix(man): support MacOS 13

MacOS 13 has changed its version of `man` to an version that doesn't
properly support `man -w` (without arguments). In order to workaround
this we simply fallback to $MANPATH.

Fixes #20579
This commit is contained in:
Lewis Russell 2022-10-11 13:25:51 +01:00
parent cba05c541d
commit b126b11840

View File

@ -1,6 +1,6 @@
local api, fn = vim.api, vim.fn local api, fn = vim.api, vim.fn
local find_arg = '-w' local FIND_ARG = '-w'
local localfile_arg = true -- Always use -l if possible. #6683 local localfile_arg = true -- Always use -l if possible. #6683
local buf_hls = {} local buf_hls = {}
@ -12,7 +12,7 @@ local function man_error(msg)
end end
-- Run a system command and timeout after 30 seconds. -- Run a system command and timeout after 30 seconds.
local function man_system(args, silent, env) local function system(cmd, silent, env)
local stdout_data = {} local stdout_data = {}
local stderr_data = {} local stderr_data = {}
local stdout = vim.loop.new_pipe(false) local stdout = vim.loop.new_pipe(false)
@ -22,8 +22,8 @@ local function man_system(args, silent, env)
local exit_code local exit_code
local handle local handle
handle = vim.loop.spawn('man', { handle = vim.loop.spawn(cmd[1], {
args = args, args = vim.list_slice(cmd, 2),
stdio = { nil, stdout, stderr }, stdio = { nil, stdout, stderr },
env = env, env = env,
}, function(code) }, function(code)
@ -45,8 +45,8 @@ local function man_system(args, silent, env)
stdout:close() stdout:close()
stderr:close() stderr:close()
if not silent then if not silent then
local cmd = table.concat({ 'man', unpack(args) }, ' ') local cmd_str = table.concat(cmd, ' ')
man_error(string.format('command error: %s', cmd)) man_error(string.format('command error: %s', cmd_str))
end end
end end
@ -60,13 +60,13 @@ local function man_system(args, silent, env)
stdout:close() stdout:close()
stderr:close() stderr:close()
end end
local cmd = table.concat({ 'man', unpack(args) }, ' ') local cmd_str = table.concat(cmd, ' ')
man_error(string.format('command timed out: %s', cmd)) man_error(string.format('command timed out: %s', cmd_str))
end end
if exit_code ~= 0 and not silent then if exit_code ~= 0 and not silent then
local cmd = table.concat({ 'man', unpack(args) }, ' ') local cmd_str = table.concat(cmd, ' ')
man_error(string.format("command error '%s': %s", cmd, table.concat(stderr_data))) man_error(string.format("command error '%s': %s", cmd_str, table.concat(stderr_data)))
end end
return table.concat(stdout_data) return table.concat(stdout_data)
@ -269,14 +269,14 @@ local function get_path(sect, name, silent)
-- --
-- Finally, we can avoid relying on -S or -s here since they are very -- Finally, we can avoid relying on -S or -s here since they are very
-- inconsistently supported. Instead, call -w with a section and a name. -- inconsistently supported. Instead, call -w with a section and a name.
local args local cmd
if sect == '' then if sect == '' then
args = { find_arg, name } cmd = { 'man', FIND_ARG, name }
else else
args = { find_arg, sect, name } cmd = { 'man', FIND_ARG, sect, name }
end end
local lines = man_system(args, silent) local lines = system(cmd, silent)
local results = vim.split(lines or {}, '\n', { trimempty = true }) local results = vim.split(lines or {}, '\n', { trimempty = true })
if #results == 0 then if #results == 0 then
@ -342,7 +342,7 @@ end
-- 2. If it still could not be found, then we try again without a section. -- 2. If it still could not be found, then we try again without a section.
-- 3. If still not found but $MANSECT is set, then we try again with $MANSECT -- 3. If still not found but $MANSECT is set, then we try again with $MANSECT
-- unset. -- unset.
local function verify_exists(sect, name) local function verify_exists(sect, name, silent)
if sect and sect ~= '' then if sect and sect ~= '' then
local ret = get_path(sect, name, true) local ret = get_path(sect, name, true)
if ret then if ret then
@ -378,8 +378,10 @@ local function verify_exists(sect, name)
end end
end end
-- finally, if that didn't work, there is no hope if not silent then
man_error('no manual entry for ' .. name) -- finally, if that didn't work, there is no hope
man_error('no manual entry for ' .. name)
end
end end
local EXT_RE = vim.regex([[\.\%([glx]z\|bz2\|lzma\|Z\)$]]) local EXT_RE = vim.regex([[\.\%([glx]z\|bz2\|lzma\|Z\)$]])
@ -434,12 +436,12 @@ local function get_page(path, silent)
manwidth = api.nvim_win_get_width(0) manwidth = api.nvim_win_get_width(0)
end end
local args = localfile_arg and { '-l', path } or { path } local cmd = localfile_arg and { 'man', '-l', path } or { 'man', path }
-- Force MANPAGER=cat to ensure Vim is not recursively invoked (by man-db). -- Force MANPAGER=cat to ensure Vim is not recursively invoked (by man-db).
-- http://comments.gmane.org/gmane.editors.vim.devel/29085 -- http://comments.gmane.org/gmane.editors.vim.devel/29085
-- Set MAN_KEEP_FORMATTING so Debian man doesn't discard backspaces. -- Set MAN_KEEP_FORMATTING so Debian man doesn't discard backspaces.
return man_system(args, silent, { return system(cmd, silent, {
'MANPAGER=cat', 'MANPAGER=cat',
'MANWIDTH=' .. manwidth, 'MANWIDTH=' .. manwidth,
'MAN_KEEP_FORMATTING=1', 'MAN_KEEP_FORMATTING=1',
@ -480,38 +482,40 @@ local function format_candidate(path, psect)
return '' return ''
end end
local function get_paths(sect, name, do_fallback) local function move_elem_to_head(list, elem)
-- callers must try-catch this, as some `man` implementations don't support `s:find_arg` local list1 = vim.tbl_filter(function(v)
local ok, ret = pcall(function() return v ~= elem
local mandirs = end, list)
table.concat(vim.split(man_system({ find_arg }), '[:\n]', { trimempty = true }), ',') return { elem, unpack(list1) }
local paths = fn.globpath(mandirs, 'man?/' .. name .. '*.' .. sect .. '*', false, true) end
pcall(function()
-- Prioritize the result from verify_exists as it obeys b:man_default_sects.
local first = verify_exists(sect, name)
paths = vim.tbl_filter(function(v)
return v ~= first
end, paths)
paths = { first, unpack(paths) }
end)
return paths
end)
if not ok then local function get_paths(sect, name)
if not do_fallback then -- Try several sources for getting the list man directories:
error(ret) -- 1. `man -w` (works on most systems)
end -- 2. `manpath`
-- 3. $MANPATH
local mandirs_raw = vim.F.npcall(system, { 'man', FIND_ARG })
or vim.F.npcall(system, { 'manpath', '-q' })
or vim.env.MANPATH
-- Fallback to a single path, with the page we're trying to find. if not mandirs_raw then
ok, ret = pcall(verify_exists, sect, name) man_error("Could not determine man directories from: 'man -w', 'manpath' or $MANPATH")
return { ok and ret or nil }
end end
return ret or {}
local mandirs = table.concat(vim.split(mandirs_raw, '[:\n]', { trimempty = true }), ',')
local paths = fn.globpath(mandirs, 'man?/' .. name .. '*.' .. sect .. '*', false, true)
-- Prioritize the result from verify_exists as it obeys b:man_default_sects.
local first = verify_exists(sect, name, true)
if first then
paths = move_elem_to_head(paths, first)
end
return paths
end end
local function complete(sect, psect, name) local function complete(sect, psect, name)
local pages = get_paths(sect, name, false) local pages = get_paths(sect, name)
-- We remove duplicates in case the same manpage in different languages was found. -- We remove duplicates in case the same manpage in different languages was found.
return fn.uniq(fn.sort(vim.tbl_map(function(v) return fn.uniq(fn.sort(vim.tbl_map(function(v)
return format_candidate(v, psect) return format_candidate(v, psect)
@ -587,7 +591,7 @@ end
function M.goto_tag(pattern, _, _) function M.goto_tag(pattern, _, _)
local sect, name = extract_sect_and_name_ref(pattern) local sect, name = extract_sect_and_name_ref(pattern)
local paths = get_paths(sect, name, true) local paths = get_paths(sect, name)
local structured = {} local structured = {}
for _, path in ipairs(paths) do for _, path in ipairs(paths) do