mirror of
https://github.com/neovim/neovim.git
synced 2026-03-28 03:12:00 +00:00
fix(man.lua): :Man ignores section of gzipped manpage #38235
Problem: Under certain circumstances (e.g. gzipped manpages with mandoc), :Man will not find the correct page because it does not process multiple extensions correctly. For example, with a file named strcpy.3p.gz, it will only check the .gz part to try to check the section. This leads to some pages being inaccessible because it will return the page from the wrong section. Solution: Loop and try multiple extensions to try to find one which matches the name of the section. Also refactor the man.get_path function so that it can be tested.
This commit is contained in:
@@ -217,6 +217,30 @@ end
|
||||
--- @param name? string
|
||||
--- @param sect? string
|
||||
local function get_path(name, sect)
|
||||
name = name or ''
|
||||
sect = sect or ''
|
||||
-- We can avoid relying on -S or -s here since they are very
|
||||
-- inconsistently supported. Instead, call -w with a section and a name.
|
||||
local cmd --- @type string[]
|
||||
if sect == '' then
|
||||
cmd = { 'man', '-w', name }
|
||||
else
|
||||
cmd = { 'man', '-w', sect, name }
|
||||
end
|
||||
|
||||
local lines = system(cmd, true)
|
||||
local results = vim.split(lines, '\n', { trimempty = true })
|
||||
|
||||
return M._match_manpage_path(results, name, sect)
|
||||
end
|
||||
|
||||
--- Given an array of paths returned by man -w,
|
||||
--- find the correct path for the given name and section.
|
||||
--- @param paths? string[]
|
||||
--- @param name? string
|
||||
--- @param sect? string
|
||||
function M._match_manpage_path(paths, name, sect)
|
||||
paths = paths or {}
|
||||
name = name or ''
|
||||
sect = sect or ''
|
||||
-- Some man implementations (OpenBSD) return all available paths from the
|
||||
@@ -234,20 +258,7 @@ local function get_path(name, sect)
|
||||
-- clock_getres.2, which is the right page. Searching the results for
|
||||
-- clock_gettime will no longer work. In this case, we should just use the
|
||||
-- first one that was found in the correct section.
|
||||
--
|
||||
-- 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.
|
||||
local cmd --- @type string[]
|
||||
if sect == '' then
|
||||
cmd = { 'man', '-w', name }
|
||||
else
|
||||
cmd = { 'man', '-w', sect, name }
|
||||
end
|
||||
|
||||
local lines = system(cmd, true)
|
||||
local results = vim.split(lines, '\n', { trimempty = true })
|
||||
|
||||
if #results == 0 then
|
||||
if #paths == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
@@ -255,7 +266,7 @@ local function get_path(name, sect)
|
||||
-- stops us from actually determining if a path has a corresponding man file.
|
||||
-- Since `:Man /some/path/to/man/file` isn't supported anyway, we should just
|
||||
-- error out here if we detect this is the case.
|
||||
if sect == '' and #results == 1 and results[1] == name then
|
||||
if sect == '' and #paths == 1 and paths[1] == name then
|
||||
return
|
||||
end
|
||||
|
||||
@@ -264,17 +275,30 @@ local function get_path(name, sect)
|
||||
local namematches = vim.tbl_filter(function(v)
|
||||
local tail = vim.fs.basename(v)
|
||||
return tail:find(name, 1, true) ~= nil
|
||||
end, results) or {}
|
||||
end, paths) or {}
|
||||
local sectmatches = {}
|
||||
|
||||
if #namematches > 0 and sect ~= '' then
|
||||
--- @param v string
|
||||
sectmatches = vim.tbl_filter(function(v)
|
||||
return fn.fnamemodify(v, ':e') == sect
|
||||
-- On some systems, there are multiple extensions, e.g. strlen.3.gz
|
||||
-- We must test all of the extensions to make sure we get the correct match.
|
||||
-- Limit to 3 tests to avoid the risk of getting stuck.
|
||||
local root = v
|
||||
for _ = 1, 3 do
|
||||
local extension = fn.fnamemodify(root, ':e')
|
||||
if extension == sect then
|
||||
return true
|
||||
elseif extension == '' then
|
||||
return false
|
||||
end
|
||||
root = fn.fnamemodify(root, ':r')
|
||||
end
|
||||
return false
|
||||
end, namematches)
|
||||
end
|
||||
|
||||
return (sectmatches[1] or namematches[1] or results[1]):gsub('\n+$', '')
|
||||
return (sectmatches[1] or namematches[1] or paths[1]):gsub('\n+$', '')
|
||||
end
|
||||
|
||||
--- Attempt to extract the name and sect out of 'name(sect)'
|
||||
|
||||
@@ -259,6 +259,53 @@ describe(':Man', function()
|
||||
os.remove(actual_file)
|
||||
end)
|
||||
|
||||
-- if man -w returns multiple results, :Man should select the correct one.
|
||||
it('matches correct manpage section and name', function()
|
||||
-- mock the system function to return the results we want to test.
|
||||
local function _test(results, name, sect)
|
||||
return exec_lua(function()
|
||||
local man = require 'man'
|
||||
return man._match_manpage_path(results, name, sect)
|
||||
end)
|
||||
end
|
||||
|
||||
eq(
|
||||
'/usr/share/man/man3/strcpy.3',
|
||||
_test({
|
||||
'/usr/share/man/man3/strcpy.3',
|
||||
'/usr/share/man/man3/string.3',
|
||||
}, 'strcpy')
|
||||
)
|
||||
|
||||
eq(
|
||||
'/usr/share/man/man3/strcpy.3',
|
||||
_test({
|
||||
'/usr/share/man/man3/string.3',
|
||||
'/usr/share/man/man3/strcpy.3',
|
||||
}, 'strcpy')
|
||||
)
|
||||
|
||||
eq(
|
||||
'/usr/share/man/man3/strcpy.3',
|
||||
_test({
|
||||
'/usr/share/man/man3p/strcpy.3p',
|
||||
'/usr/share/man/man3/strcpy.3',
|
||||
'/usr/share/man/man3/string.3',
|
||||
'/usr/share/man/man7/string_copying.7',
|
||||
}, 'strcpy', '3')
|
||||
)
|
||||
|
||||
eq(
|
||||
'/usr/share/man/man3/strcpy.3.gz',
|
||||
_test({
|
||||
'/usr/share/man/man3p/strcpy.3p.gz',
|
||||
'/usr/share/man/man3/strcpy.3.gz',
|
||||
'/usr/share/man/man3/string.3.gz',
|
||||
'/usr/share/man/man7/string_copying.7.gz',
|
||||
}, 'strcpy', '3')
|
||||
)
|
||||
end)
|
||||
|
||||
it('tries variants with spaces, underscores #22503', function()
|
||||
eq({
|
||||
{ vim.NIL, 'NAME WITH SPACES' },
|
||||
|
||||
Reference in New Issue
Block a user