Merge #22503 from eriks47/man-spaces

feat(man.lua): support spaces in manpage names
This commit is contained in:
Justin M. Keyes 2023-03-07 09:38:35 -05:00 committed by GitHub
commit 32ef36cb87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 69 additions and 22 deletions

View File

@ -162,6 +162,8 @@ The following new APIs or features were added.
• |:highlight| now supports an additional attribute "altfont". • |:highlight| now supports an additional attribute "altfont".
• |:Man| manpage viewer supports manpage names containing spaces.
• Treesitter captures can now be transformed by directives. This will allow • Treesitter captures can now be transformed by directives. This will allow
more complicated dynamic language injections. more complicated dynamic language injections.

View File

@ -328,7 +328,8 @@ local function get_path(sect, name, silent)
-- find any that match the specified name -- find any that match the specified name
---@param v string ---@param v string
local namematches = vim.tbl_filter(function(v) local namematches = vim.tbl_filter(function(v)
return fn.fnamemodify(v, ':t'):match(name) local tail = fn.fnamemodify(v, ':t')
return string.find(tail, name, 1, true)
end, results) or {} end, results) or {}
local sectmatches = {} local sectmatches = {}
@ -372,18 +373,18 @@ local function extract_sect_and_name_ref(ref)
if not name then if not name then
man_error('manpage reference cannot contain only parentheses: ' .. ref) man_error('manpage reference cannot contain only parentheses: ' .. ref)
end end
return '', spaces_to_underscores(name) return '', name
end end
local parts = vim.split(ref1, '(', { plain = true }) local parts = vim.split(ref1, '(', { plain = true })
-- see ':Man 3X curses' on why tolower. -- see ':Man 3X curses' on why tolower.
-- TODO(nhooyr) Not sure if this is portable across OSs -- TODO(nhooyr) Not sure if this is portable across OSs
-- but I have not seen a single uppercase section. -- but I have not seen a single uppercase section.
local sect = vim.split(parts[2] or '', ')', { plain = true })[1]:lower() local sect = vim.split(parts[2] or '', ')', { plain = true })[1]:lower()
local name = spaces_to_underscores(parts[1]) local name = parts[1]
return sect, name return sect, name
end end
-- verify_exists attempts to find the path to a manpage -- find_path attempts to find the path to a manpage
-- based on the passed section and name. -- based on the passed section and name.
-- --
-- 1. If manpage could not be found with the given sect and name, -- 1. If manpage could not be found with the given sect and name,
@ -391,10 +392,10 @@ 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.
-- 4. If a path still wasn't found, return nil.
---@param sect string? ---@param sect string?
---@param name string ---@param name string
---@param silent boolean? function M.find_path(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
@ -430,10 +431,8 @@ local function verify_exists(sect, name, silent)
end end
end end
if not silent then -- finally, if that didn't work, there is no hope
-- finally, if that didn't work, there is no hope return nil
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\)$]])
@ -585,8 +584,8 @@ local function get_paths(sect, name)
---@type string[] ---@type string[]
local paths = fn.globpath(mandirs, 'man?/' .. name .. '*.' .. sect .. '*', false, true) local paths = fn.globpath(mandirs, 'man?/' .. name .. '*.' .. sect .. '*', false, true)
-- Prioritize the result from verify_exists as it obeys b:man_default_sects. -- Prioritize the result from find_path as it obeys b:man_default_sects.
local first = verify_exists(sect, name, true) local first = M.find_path(sect, name)
if first then if first then
paths = move_elem_to_head(paths, first) paths = move_elem_to_head(paths, first)
end end
@ -728,10 +727,6 @@ end
---@param count integer ---@param count integer
---@param args string[] ---@param args string[]
function M.open_page(count, smods, args) function M.open_page(count, smods, args)
if #args > 2 then
man_error('too many arguments')
end
local ref ---@type string local ref ---@type string
if #args == 0 then if #args == 0 then
ref = vim.bo.filetype == 'man' and fn.expand('<cWORD>') or fn.expand('<cword>') ref = vim.bo.filetype == 'man' and fn.expand('<cWORD>') or fn.expand('<cword>')
@ -743,9 +738,14 @@ function M.open_page(count, smods, args)
else else
-- Combine the name and sect into a manpage reference so that all -- Combine the name and sect into a manpage reference so that all
-- verification/extraction can be kept in a single function. -- verification/extraction can be kept in a single function.
-- If args[2] is a reference as well, that is fine because it is the only if tonumber(args[1]) then
-- reference that will match. local sect = args[1]
ref = ('%s(%s)'):format(args[2], args[1]) table.remove(args, 1)
local name = table.concat(args, ' ')
ref = ('%s(%s)'):format(name, sect)
else
ref = table.concat(args, ' ')
end
end end
local sect, name = extract_sect_and_name_ref(ref) local sect, name = extract_sect_and_name_ref(ref)
@ -753,9 +753,16 @@ function M.open_page(count, smods, args)
sect = tostring(count) sect = tostring(count)
end end
local path = verify_exists(sect, name) -- Try both spaces and underscores, use the first that exists.
sect, name = extract_sect_and_name_path(path) local path = M.find_path(sect, name)
if path == nil then
path = M.find_path(sect, spaces_to_underscores(name))
if path == nil then
man_error('no manual entry for ' .. name)
end
end
sect, name = extract_sect_and_name_path(path)
local buf = fn.bufnr() local buf = fn.bufnr()
local save_tfu = vim.bo[buf].tagfunc local save_tfu = vim.bo[buf].tagfunc
vim.bo[buf].tagfunc = "v:lua.require'man'.goto_tag" vim.bo[buf].tagfunc = "v:lua.require'man'.goto_tag"
@ -786,7 +793,10 @@ end
-- Called when a man:// buffer is opened. -- Called when a man:// buffer is opened.
function M.read_page(ref) function M.read_page(ref)
local sect, name = extract_sect_and_name_ref(ref) local sect, name = extract_sect_and_name_ref(ref)
local path = verify_exists(sect, name) local path = M.find_path(sect, name)
if path == nil then
man_error('no manual entry for ' .. name)
end
sect = extract_sect_and_name_path(path) sect = extract_sect_and_name_path(path)
local page = get_page(path) local page = get_page(path)
vim.b.man_sect = sect vim.b.man_sect = sect

View File

@ -8,9 +8,29 @@ local nvim_prog = helpers.nvim_prog
local matches = helpers.matches local matches = helpers.matches
local write_file = helpers.write_file local write_file = helpers.write_file
local tmpname = helpers.tmpname local tmpname = helpers.tmpname
local eq = helpers.eq
local skip = helpers.skip local skip = helpers.skip
local is_ci = helpers.is_ci local is_ci = helpers.is_ci
-- Collects all names passed to find_path() after attempting ":Man foo".
local function get_search_history(name)
local args = vim.split(name, ' ')
local code = [[
local args = ...
local man = require('runtime.lua.man')
local res = {}
man.find_path = function(sect, name)
table.insert(res, name)
return nil
end
local ok, rv = pcall(man.open_page, 0, {tab = 0}, args)
assert(not ok)
assert(rv and rv:match('no manual entry'))
return res
]]
return exec_lua(code, args)
end
clear() clear()
if funcs.executable('man') == 0 then if funcs.executable('man') == 0 then
pending('missing "man" command', function() end) pending('missing "man" command', function() end)
@ -173,4 +193,19 @@ describe(':Man', function()
funcs.system(args, {''})) funcs.system(args, {''}))
os.remove(actual_file) os.remove(actual_file)
end) end)
it('tries variants with spaces, underscores #22503', function()
eq({
'NAME WITH SPACES',
'NAME_WITH_SPACES',
}, get_search_history('NAME WITH SPACES'))
eq({
'some other man',
'some_other_man',
}, get_search_history('3 some other man'))
eq({
'other_man',
'other_man',
}, get_search_history('other_man(1)'))
end)
end) end)