diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua index 5c4a80f9f0..9efc7e96f9 100644 --- a/runtime/lua/man.lua +++ b/runtime/lua/man.lua @@ -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)' diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua index 2e2c6d4050..6e435c283f 100644 --- a/test/functional/plugin/man_spec.lua +++ b/test/functional/plugin/man_spec.lua @@ -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' },