mirror of
https://github.com/vim/vim
synced 2025-07-15 16:51:57 +00:00
runtime(man): improve :Man completion for man-db
On man-db systems, complete with actual man sections and pages, instead of shell commands. I tried to come up with a portable solution for multiple man implementations in https://github.com/vim/vim/discussions/16794 but I think the differences between implementations were too large to do that without overly complicated code. So instead, I implemented it for man-db (which I think is common on Linux) and hopefully left it easier for other people to implement it on other systems in the future if they want to. closes: #16843 Signed-off-by: David Mandelberg <david@mandelberg.org> Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
committed by
Christian Brabandt
parent
969e11a18b
commit
c2623824a7
83
runtime/autoload/dist/man.vim
vendored
83
runtime/autoload/dist/man.vim
vendored
@ -6,6 +6,7 @@
|
||||
" Last Change: 2024 Jan 17 (make it work on AIX, see #13847)
|
||||
" 2024 Jul 06 (honor command modifiers, #15117)
|
||||
" 2025 Mar 05 (add :keepjumps, #16791)
|
||||
" 2025 Mar 09 (improve :Man completion for man-db, #16843)
|
||||
|
||||
let s:cpo_save = &cpo
|
||||
set cpo-=C
|
||||
@ -35,6 +36,88 @@ endtry
|
||||
|
||||
unlet! uname_s
|
||||
|
||||
let s:man_db_pages_by_section = v:null
|
||||
func! s:ManDbPagesBySection() abort
|
||||
if s:man_db_pages_by_section isnot v:null
|
||||
return s:man_db_pages_by_section
|
||||
endif
|
||||
let s:man_db_pages_by_section = {}
|
||||
let list_command = 'apropos --long .'
|
||||
let unparsed_lines = []
|
||||
for line in systemlist(list_command)
|
||||
" Typical lines:
|
||||
" vim (1) - Vi IMproved, a programmer's text editor
|
||||
"
|
||||
" Unusual lines:
|
||||
" pgm_read_ T _ (3avr) - (unknown subject)
|
||||
"
|
||||
" Code that shows the line's format:
|
||||
" https://gitlab.com/man-db/man-db/-/blob/2607d203472efb036d888e9e7997724a41a53876/src/whatis.c#L409
|
||||
let match = matchlist(line, '^\(.\{-1,}\) (\(\S\+\)) ')
|
||||
if empty(match)
|
||||
call add(unparsed_lines, line)
|
||||
continue
|
||||
endif
|
||||
let [page, section] = match[1:2]
|
||||
if !has_key(s:man_db_pages_by_section, section)
|
||||
let s:man_db_pages_by_section[section] = []
|
||||
endif
|
||||
call add(s:man_db_pages_by_section[section], page)
|
||||
endfor
|
||||
if !empty(unparsed_lines)
|
||||
echomsg 'Unable to parse ' .. string(len(unparsed_lines)) .. ' lines ' ..
|
||||
\ 'from the output of `' .. list_command .. '`. Example lines:'
|
||||
for line in unparsed_lines[:9]
|
||||
echomsg line
|
||||
endfor
|
||||
endif
|
||||
return s:man_db_pages_by_section
|
||||
endfunc
|
||||
|
||||
func! dist#man#Reload() abort
|
||||
if g:ft_man_implementation ==# 'man-db'
|
||||
let s:man_db_pages_by_section = v:null
|
||||
call s:ManDbPagesBySection()
|
||||
endif
|
||||
endfunc
|
||||
|
||||
func! s:StartsWithCaseInsensitive(haystack, needle) abort
|
||||
if empty(a:needle)
|
||||
return v:true
|
||||
endif
|
||||
return a:haystack[:len(a:needle)-1] ==? a:needle
|
||||
endfunc
|
||||
|
||||
func! dist#man#ManDbComplete(arg_lead, cmd_line, cursor_pos) abort
|
||||
let args = split(trim(a:cmd_line[: a:cursor_pos - 1], '', 1), '', v:true)
|
||||
let pages_by_section = s:ManDbPagesBySection()
|
||||
if len(args) > 2
|
||||
" Page in the section args[1]. At least on Debian testing as of
|
||||
" 2025-03-06, man seems to match sections case-insensitively and match any
|
||||
" prefix of the section. E.g., `man 3 sigprocmask` and `man 3PoSi
|
||||
" sigprocmask` with both load sigprocmask(3posix) even though the 3 in the
|
||||
" first command is also the name of a different section.
|
||||
let results = []
|
||||
for [section, pages] in items(pages_by_section)
|
||||
if s:StartsWithCaseInsensitive(section, args[1])
|
||||
call extend(results, pages)
|
||||
endif
|
||||
endfor
|
||||
else
|
||||
" Could be a section, or a page in any section. Add space after sections
|
||||
" since there has to be a second argument in that case.
|
||||
let results = flattennew(values(pages_by_section), 1)
|
||||
for section in keys(pages_by_section)
|
||||
call add(results, section .. ' ')
|
||||
endfor
|
||||
endif
|
||||
call sort(results)
|
||||
call uniq(results)
|
||||
call filter(results,
|
||||
\ {_, val -> s:StartsWithCaseInsensitive(val, a:arg_lead)})
|
||||
return results
|
||||
endfunc
|
||||
|
||||
func s:ParseIntoPageAndSection()
|
||||
" Accommodate a reference that terminates in a hyphen.
|
||||
"
|
||||
|
@ -1,4 +1,4 @@
|
||||
*filetype.txt* For Vim version 9.1. Last change: 2025 Mar 09
|
||||
*filetype.txt* For Vim version 9.1. Last change: 2025 Mar 10
|
||||
|
||||
|
||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||
@ -776,7 +776,7 @@ Local mappings:
|
||||
to the end of the file in Normal mode. This means "> " is inserted in
|
||||
each line.
|
||||
|
||||
MAN *ft-man-plugin* *:Man* *man.vim*
|
||||
MAN *ft-man-plugin* *:Man* *:ManReload* *man.vim*
|
||||
|
||||
This plugin displays a manual page in a nice way. See |find-manpage| in the
|
||||
user manual for more information.
|
||||
@ -793,6 +793,8 @@ Commands:
|
||||
Man {name} Display the manual page for {name} in a window.
|
||||
Man {number} {name}
|
||||
Display the manual page for {name} in a section {number}.
|
||||
ManReload Reload the cache of available man pages used for |:Man| argument
|
||||
completion.
|
||||
|
||||
Global mapping:
|
||||
<Leader>K Displays the manual page for the word under the cursor.
|
||||
@ -823,6 +825,14 @@ desired folding style instead. For example: >
|
||||
If you would like :Man {number} {name} to behave like man {number} {name} by
|
||||
not running man {name} if no page is found, then use this: >
|
||||
let g:ft_man_no_sect_fallback = 1
|
||||
<
|
||||
*g:ft_man_implementation*
|
||||
The completion for the :Man command tries to guess which implementation of man
|
||||
the system has. If it guesses wrong, you can set g:ft_man_implementation to
|
||||
one of these values:
|
||||
'man-db' https://man-db.nongnu.org/
|
||||
'' Unknown, fall back to completing shell commands
|
||||
instead of man pages.
|
||||
|
||||
You may also want to set 'keywordprg' to make the |K| command open a manual
|
||||
page in a Vim window: >
|
||||
|
@ -2142,6 +2142,7 @@ $quote eval.txt /*$quote*
|
||||
:Lfilter quickfix.txt /*:Lfilter*
|
||||
:LogiPat pi_logipat.txt /*:LogiPat*
|
||||
:Man filetype.txt /*:Man*
|
||||
:ManReload filetype.txt /*:ManReload*
|
||||
:MkVimball pi_vimball.txt /*:MkVimball*
|
||||
:N editing.txt /*:N*
|
||||
:Nexplore pi_netrw.txt /*:Nexplore*
|
||||
@ -7581,6 +7582,7 @@ g:filetype_csh syntax.txt /*g:filetype_csh*
|
||||
g:filetype_haredoc ft_hare.txt /*g:filetype_haredoc*
|
||||
g:filetype_md syntax.txt /*g:filetype_md*
|
||||
g:filetype_r syntax.txt /*g:filetype_r*
|
||||
g:ft_man_implementation filetype.txt /*g:ft_man_implementation*
|
||||
g:ftplugin_rust_source_path ft_rust.txt /*g:ftplugin_rust_source_path*
|
||||
g:gnat ft_ada.txt /*g:gnat*
|
||||
g:gnat.Error_Format ft_ada.txt /*g:gnat.Error_Format*
|
||||
|
@ -6,6 +6,7 @@
|
||||
" Last Change: 2024 Jun 06 (disabled the q mapping, #8210)
|
||||
" 2024 Jul 06 (use nnoremap, #15130)
|
||||
" 2024 Aug 23 (improve the <Plug>ManBS mapping, #15547, #15556)
|
||||
" 2025 Mar 09 (improve :Man completion for man-db, #16843)
|
||||
|
||||
" To make the ":Man" command available before editing a manual page, source
|
||||
" this script from your startup vimrc file.
|
||||
@ -24,6 +25,14 @@ endif
|
||||
let s:cpo_save = &cpo
|
||||
set cpo-=C
|
||||
|
||||
if !exists('g:ft_man_implementation')
|
||||
if executable('mandb') > 0
|
||||
let g:ft_man_implementation = 'man-db'
|
||||
else
|
||||
let g:ft_man_implementation = ''
|
||||
endif
|
||||
endif
|
||||
|
||||
if &filetype == "man"
|
||||
" Allow hyphen, plus, colon, dot, and commercial at in manual page name.
|
||||
" Parentheses are not here but in dist#man#PreGetPage()
|
||||
@ -60,11 +69,19 @@ if &filetype == "man"
|
||||
endif
|
||||
|
||||
if exists(":Man") != 2
|
||||
com -nargs=+ -complete=shellcmd Man call dist#man#GetPage(<q-mods>, <f-args>)
|
||||
if g:ft_man_implementation ==# 'man-db'
|
||||
com -nargs=+ -complete=customlist,dist#man#ManDbComplete Man call dist#man#GetPage(<q-mods>, <f-args>)
|
||||
else
|
||||
com -nargs=+ -complete=shellcmd Man call dist#man#GetPage(<q-mods>, <f-args>)
|
||||
endif
|
||||
nnoremap <Leader>K :call dist#man#PreGetPage(0)<CR>
|
||||
nnoremap <Plug>ManPreGetPage :call dist#man#PreGetPage(0)<CR>
|
||||
endif
|
||||
|
||||
if exists(":ManReload") != 2
|
||||
com ManReload call dist#man#Reload()
|
||||
endif
|
||||
|
||||
let &cpo = s:cpo_save
|
||||
unlet s:cpo_save
|
||||
|
||||
|
Reference in New Issue
Block a user