mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	feat(man.lua): support spaces in manpage names
Problem: :Man command errors if given more than two arguments. Thus, it is impossible to open man pages that contain spaces in their names. Solution: Adjust :Man so that it tries variants with spaces and underscores, and uses the first found.
This commit is contained in:
		 Eriks Muhins
					Eriks Muhins
				
			
				
					committed by
					
						 Justin M. Keyes
						Justin M. Keyes
					
				
			
			
				
	
			
			
			 Justin M. Keyes
						Justin M. Keyes
					
				
			
						parent
						
							7a462c10d5
						
					
				
				
					commit
					160a019ffa
				
			| @@ -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 | -- search_for_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.search_for_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 | ||||||
|     man_error('no manual entry for ' .. name) |   return nil | ||||||
|   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 search_for_path as it obeys b:man_default_sects. | ||||||
|   local first = verify_exists(sect, name, true) |   local first = M.search_for_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.search_for_path(sect, name) | ||||||
|  |   if path == nil then | ||||||
|  |     path = M.search_for_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.search_for_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 | ||||||
|   | |||||||
| @@ -10,6 +10,26 @@ local write_file = helpers.write_file | |||||||
| local tmpname = helpers.tmpname | local tmpname = helpers.tmpname | ||||||
| local skip = helpers.skip | local skip = helpers.skip | ||||||
| local is_ci = helpers.is_ci | local is_ci = helpers.is_ci | ||||||
|  | local table_contains = vim.tbl_contains | ||||||
|  |  | ||||||
|  | -- Returns a table composed of all man page name arguments | ||||||
|  | -- that were passed to search_for_path after attempting to | ||||||
|  | -- open 'name'. | ||||||
|  | local function get_search_history(name) | ||||||
|  |   local as_table = string.gsub(name, ' ', '\', \'') | ||||||
|  |   as_table = '\'' .. as_table .. '\'' | ||||||
|  |   local code = ([[ | ||||||
|  |     local man = require('runtime.lua.man') | ||||||
|  |     local res = {} | ||||||
|  |     man.attempt_to_get_path = function(sect, name, silent) | ||||||
|  |       table.insert(res, name) | ||||||
|  |       return nil | ||||||
|  |     end | ||||||
|  |     pcall(man.open_page, 0, {tab = 0}, {%s}) | ||||||
|  |     return res | ||||||
|  |   ]]):format(as_table) | ||||||
|  |   return exec_lua(code) | ||||||
|  | end | ||||||
|  |  | ||||||
| clear() | clear() | ||||||
| if funcs.executable('man') == 0 then | if funcs.executable('man') == 0 then | ||||||
| @@ -173,4 +193,10 @@ describe(':Man', function() | |||||||
|       funcs.system(args, {''})) |       funcs.system(args, {''})) | ||||||
|     os.remove(actual_file) |     os.remove(actual_file) | ||||||
|   end) |   end) | ||||||
|  |  | ||||||
|  |   it('searches for manpage name with variants with spaces, underscores', function() | ||||||
|  |     local tried = get_search_history('NAME WITH SPACES') | ||||||
|  |     table_contains(tried, 'NAME WITH SPACES') | ||||||
|  |     table_contains(tried, 'NAME_WITH_SPACES') | ||||||
|  |   end) | ||||||
| end) | end) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user