├── .gitignore ├── improvements.md ├── autoload ├── man │ ├── section.vim │ ├── grep │ │ ├── vanilla.vim │ │ ├── dispatch.vim │ │ └── nvim.vim │ ├── completion.vim │ ├── grep.vim │ └── helpers.vim └── man.vim ├── plugin └── man.vim ├── ftplugin └── man.vim ├── syntax └── man.vim ├── CHANGELOG.md ├── doc └── man.txt ├── README.md └── test └── manpage_extension_stripping_test.vim /.gitignore: -------------------------------------------------------------------------------- 1 | tags 2 | -------------------------------------------------------------------------------- /improvements.md: -------------------------------------------------------------------------------- 1 | ### Improvements 2 | 3 | This file contains a list of improvements over `man.vim` that comes with 4 | vim. For a full list of changes you might want to check 5 | [CHANGELOG.md](CHANGELOG.md). 6 | 7 | - `:Vman` command that opens a manpage in a vertical split 8 | - command line completion for `:Man` and `:Vman` commands 9 | - `:Mangrep` command for grepping through manpages 10 | - `:Man` (without arguments) opens the current file as a manpage (works only for 11 | `nroff` files) 12 | - `[[` and `]]` for navigating manpage sections 13 | - improvements to manpage syntax highlighting 14 | - docs 15 | - faster plugin loading (uses vim's autoloading) 16 | - a lot of bugfixes 17 | -------------------------------------------------------------------------------- /autoload/man/section.vim: -------------------------------------------------------------------------------- 1 | " load guard {{{1 2 | 3 | if exists('g:autoloaded_man_section') 4 | finish 5 | endif 6 | let g:autoloaded_man_section = 1 7 | 8 | " }}} 9 | " man#section#move {{{1 10 | 11 | function! man#section#move(direction, mode, count) 12 | norm! m' 13 | if a:mode ==# 'v' 14 | norm! gv 15 | endif 16 | let i = 0 17 | while i < a:count 18 | let i += 1 19 | " saving current position 20 | let line = line('.') 21 | let col = col('.') 22 | let pos = search('^\a\+', 'W'.a:direction) 23 | " if there are no more matches, return to last position 24 | if pos == 0 25 | call cursor(line, col) 26 | return 27 | endif 28 | endwhile 29 | endfunction 30 | 31 | " }}} 32 | " vim:set ft=vim et sw=2: 33 | -------------------------------------------------------------------------------- /plugin/man.vim: -------------------------------------------------------------------------------- 1 | if exists('g:loaded_vim_utils_man') && g:loaded_vim_utils_man 2 | finish 3 | endif 4 | let g:loaded_vim_utils_man = 1 5 | 6 | let s:save_cpo = &cpo 7 | set cpo&vim 8 | 9 | if !exists('g:vim_man_cmd') 10 | let g:vim_man_cmd='/usr/bin/man' 11 | endif 12 | 13 | command! -nargs=* -bar -complete=customlist,man#completion#run Man call man#get_page('horizontal', ) 14 | command! -nargs=* -bar -complete=customlist,man#completion#run Sman call man#get_page('horizontal', ) 15 | command! -nargs=* -bar -complete=customlist,man#completion#run Vman call man#get_page('vertical', ) 16 | command! -nargs=* -bar -complete=customlist,man#completion#run Tman call man#get_page('tab', ) 17 | 18 | command! -nargs=+ -bang Mangrep call man#grep#run(0, ) 19 | 20 | " map a key to open a manpage for word under cursor, example: map ,k (Man) 21 | nnoremap (Man) :call man#get_page_from_cword('horizontal', v:count) 22 | nnoremap (Sman) :call man#get_page_from_cword('horizontal', v:count) 23 | nnoremap (Vman) :call man#get_page_from_cword('vertical', v:count) 24 | nnoremap (Tman) :call man#get_page_from_cword('tab', v:count) 25 | 26 | let &cpo = s:save_cpo 27 | unlet s:save_cpo 28 | 29 | " vim:set ft=vim et sw=2: 30 | -------------------------------------------------------------------------------- /autoload/man/grep/vanilla.vim: -------------------------------------------------------------------------------- 1 | " Stategy for running :Mangrep command in "vanilla" vim. It blocks vim until 2 | " the command is done. 3 | " 4 | " load guard {{{1 5 | 6 | if exists('g:autoloaded_man_grep_vanilla') 7 | finish 8 | endif 9 | let g:autoloaded_man_grep_vanilla = 1 10 | 11 | " }}} 12 | " man#grep#vanilla#run {{{1 13 | 14 | function! man#grep#vanilla#run(bang, insensitive, pattern, files) 15 | let $MANWIDTH = man#helpers#manwidth() 16 | let insensitive_flag = a:insensitive ? '-i' : '' 17 | for file in a:files 18 | let output_manfile = g:vim_man_cmd.' '.file.' | col -b |' 19 | let trim_whitespace = "sed '1 {; /^\s*$/d; }' |" 20 | let grep = 'grep '.insensitive_flag.' -n -E '.a:pattern 21 | let matches = systemlist(output_manfile . trim_whitespace . grep) 22 | if v:shell_error ==# 0 23 | " found matches 24 | call s:add_matches_to_quickfixlist(file, matches) 25 | endif 26 | endfor 27 | " by convention jumps to the first result unless mangrep is invoked with bang (!) 28 | if a:bang ==# 0 29 | cc 1 30 | endif 31 | endfunction 32 | 33 | " }}} 34 | " s:add_matches_to_quickfixlist {{{1 35 | 36 | " adds grep matches for a single manpage 37 | function! s:add_matches_to_quickfixlist(file_path, matches) 38 | let man_name = man#helpers#strip_dirname_and_extension(a:file_path) 39 | let section = matchstr(fnamemodify(a:file_path, ':h:t'), '^\(man\|cat\)\zs.*') 40 | let buf_num = man#grep#create_empty_buffer(man_name, section) 41 | for result in a:matches 42 | let line_num = matchstr(result, '^\d\+') 43 | " trimmed line content 44 | let line_text = matchstr(result, '^[^:]\+:\s*\zs.\{-}\ze\s*$') 45 | call setqflist([{'bufnr': buf_num, 'lnum': line_num, 'text': line_text}], 'a') 46 | endfor 47 | endfunction 48 | 49 | " }}} 50 | -------------------------------------------------------------------------------- /autoload/man/completion.vim: -------------------------------------------------------------------------------- 1 | " load guard {{{1 2 | 3 | if exists('g:autoloaded_man_completion') 4 | finish 5 | endif 6 | let g:autoloaded_man_completion = 1 7 | 8 | " }}} 9 | " man#completion#run {{{1 10 | 11 | function! man#completion#run(A, L, P) 12 | let manpath = man#helpers#manpath() 13 | if manpath =~# '^\s*$' 14 | return [] 15 | endif 16 | let section = s:get_manpage_section(a:L, a:P) 17 | let path_glob = man#helpers#get_path_glob(manpath, section, '', ',') 18 | let matching_files = man#helpers#expand_path_glob(path_glob, a:A) 19 | return s:strip_file_names(matching_files) 20 | endfunction 21 | 22 | " extracts the manpage section number (if there is one) from the command 23 | function! s:get_manpage_section(line, cursor_position) 24 | " extracting section argument from the line 25 | let leading_line = strpart(a:line, 0, a:cursor_position) 26 | let section_arg = matchstr(leading_line, '^\s*\S\+\s\+\zs\S\+\ze\s\+') 27 | if !empty(section_arg) 28 | return man#helpers#extract_permitted_section_value(section_arg) 29 | endif 30 | " no section arg or extracted section cannot be used for man dir name globbing 31 | return '' 32 | endfunction 33 | 34 | " strips file names so they correspond manpage names 35 | function! s:strip_file_names(matching_files) 36 | if empty(a:matching_files) 37 | return [] 38 | else 39 | let matches = [] 40 | let len = 0 41 | for manpage_path in a:matching_files 42 | " since already looping also count list length 43 | let len += 1 44 | call add(matches, man#helpers#strip_dirname_and_extension(manpage_path)) 45 | endfor 46 | " remove duplicates from small lists (it takes noticeably longer 47 | " and is less relevant for large lists) 48 | return len > 500 ? matches : filter(matches, 'index(matches, v:val, v:key+1)==-1') 49 | endif 50 | endfunction 51 | 52 | " }}} 53 | " vim:set ft=vim et sw=2: 54 | -------------------------------------------------------------------------------- /ftplugin/man.vim: -------------------------------------------------------------------------------- 1 | if exists('b:did_ftplugin') 2 | finish 3 | endif 4 | let b:did_ftplugin = 1 5 | 6 | " Ensure Vim is not recursively invoked (man-db does this) 7 | " when doing ctrl-] on a man page reference. 8 | if exists('$MANPAGER') 9 | let $MANPAGER = '' 10 | endif 11 | 12 | " buffer local options {{{1 13 | 14 | " allow dot and dash in manual page name. 15 | setlocal iskeyword+=\.,- 16 | 17 | setlocal nonumber 18 | setlocal norelativenumber 19 | setlocal foldcolumn=0 20 | setlocal nofoldenable 21 | setlocal nolist 22 | 23 | " tabs in man pages are 8 spaces 24 | setlocal tabstop=8 25 | 26 | " scratch buffer options 27 | setlocal buftype=nofile 28 | setlocal bufhidden=hide 29 | setlocal nobuflisted 30 | setlocal noswapfile 31 | 32 | " }}} 33 | " mappings {{{1 34 | 35 | nnoremap K :call man#get_page_from_cword('horizontal', v:count) 36 | " all tag mappings are defined for completeness and they all perform the same action 37 | nnoremap :call man#get_page_from_cword('horizontal', v:count) 38 | nnoremap g :call man#get_page_from_cword('horizontal', v:count) 39 | nnoremap g] :call man#get_page_from_cword('horizontal', v:count) 40 | nnoremap ] :call man#get_page_from_cword('horizontal', v:count) 41 | nnoremap :call man#get_page_from_cword('horizontal', v:count) 42 | nnoremap g :call man#get_page_from_cword('horizontal', v:count) 43 | nnoremap g] :call man#get_page_from_cword('horizontal', v:count) 44 | nnoremap } :call man#get_page_from_cword('horizontal', v:count) 45 | nnoremap g} :call man#get_page_from_cword('horizontal', v:count) 46 | 47 | nnoremap :call man#pop_page() 48 | 49 | nnoremap [[ :call man#section#move('b', 'n', v:count1) 50 | nnoremap ]] :call man#section#move('' , 'n', v:count1) 51 | xnoremap [[ :call man#section#move('b', 'v', v:count1) 52 | xnoremap ]] :call man#section#move('' , 'v', v:count1) 53 | 54 | nnoremap q :q 55 | nnoremap g/ /^\s*\zs 56 | 57 | " }}} 58 | 59 | let b:undo_ftplugin = 'setlocal iskeyword<' 60 | 61 | " vim:set ft=vim et sw=2: 62 | -------------------------------------------------------------------------------- /autoload/man/grep/dispatch.vim: -------------------------------------------------------------------------------- 1 | " strategy to utilize vim-dispatch's plugin async jobs for :Mangrep command 2 | " 3 | " load guard {{{1 4 | 5 | if exists('g:autoloaded_man_grep_dispatch') 6 | finish 7 | endif 8 | let g:autoloaded_man_grep_dispatch = 1 9 | 10 | " }}} 11 | " man#grep#dispatch {{{1 12 | 13 | function! man#grep#dispatch#run(bang, insensitive, pattern, path_glob) 14 | let insensitive_flag = a:insensitive ? '-i' : '' 15 | let command = man#grep#command(a:path_glob, insensitive_flag, a:pattern) 16 | " run a Make command, but do not overrwrite user-set compiler 17 | call s:set_compiler(command) 18 | Make! 19 | call s:restore_compiler() 20 | let s:enable_mangrep_buffer_creation = 1 21 | endfunction 22 | 23 | " }}} 24 | " s:set_compiler {{{1 25 | 26 | " does everything a regular call to :compiler would do 27 | function! s:set_compiler(command) 28 | let cpo_save = &cpo 29 | set cpo-=C 30 | " save variables for later restore 31 | let s:makeprg = &makeprg 32 | let s:efm = &errorformat 33 | let &makeprg = a:command 34 | let &errorformat = '%*[^!]/man%t/%f!%l:%m,%*[^!]/cat%t/%f!%l:%m' 35 | let &cpo = cpo_save 36 | endfunction 37 | 38 | " }}} 39 | " s:restore_compiler {{{1 40 | 41 | function! s:restore_compiler() 42 | let cpo_save = &cpo 43 | set cpo-=C 44 | let &makeprg = s:makeprg 45 | let &errorformat = s:efm 46 | let &cpo = cpo_save 47 | endfunction 48 | 49 | " }}} 50 | " manGrepDispatch autocommand {{{1 51 | 52 | augroup manGrepDispatch 53 | au! 54 | " sets up buffers to open manpages when quickfixlist is opened with :Copen 55 | au QuickFixCmdPost cgetfile call s:create_buffers_for_quicklist_entries() 56 | 57 | " disable quickfix buffer manipulation when another vim-dispatch command is run 58 | au QuickFixCmdPre dispatch-make let s:enable_mangrep_buffer_creation = 0 59 | augroup END 60 | 61 | function! s:create_buffers_for_quicklist_entries() 62 | if !s:enable_mangrep_buffer_creation 63 | return 64 | endif 65 | for entry in getqflist() 66 | let buffer_num = entry['bufnr'] 67 | let section = entry['type'] 68 | if buffer_num ==# 0 || !empty(getbufvar(buffer_num, 'man_name')) 69 | " early exit, buffer_num is wrong or buffer already set 70 | continue 71 | else 72 | let name = man#helpers#strip_extension(bufname(buffer_num)) 73 | call man#grep#setup_manpage_buffer(buffer_num, name, section) 74 | endif 75 | endfor 76 | endfunction 77 | 78 | " }}} 79 | 80 | " vim:set ft=vim et sw=2: 81 | -------------------------------------------------------------------------------- /syntax/man.vim: -------------------------------------------------------------------------------- 1 | if exists('b:current_syntax') 2 | finish 3 | endif 4 | 5 | " Get the CTRL-H syntax to handle backspaced text 6 | runtime! syntax/ctrlh.vim 7 | 8 | syntax case ignore 9 | syntax match manReference '\<\zs\(\f\|:\)\+(\([nlpo]\|\d[a-z]*\)\?)\ze\(\W\|$\)' 10 | syntax match manTitle '^\(\f\|:\)\+([0-9nlpo][a-z]*).*' 11 | syntax match manSectionHeading '^[a-z][a-z0-9& ,.-]*[a-z]$' 12 | syntax match manHeaderFile '\s\zs<\f\+\.h>\ze\(\W\|$\)' 13 | syntax match manURL `\v<(((https?|ftp|gopher)://|(mailto|file|news):)[^' <>"]+|(www|web|w3)[a-z0-9_-]*\.[a-z0-9._-]+\.[^' <>"]+)[a-zA-Z0-9/]` 14 | syntax match manEmail '<\?[a-zA-Z0-9_.+-]\+@[a-zA-Z0-9-]\+\.[a-zA-Z0-9-.]\+>\?' 15 | syntax match manHighlight +`.\{-}''\?+ 16 | 17 | " below syntax elements valid for manpages 2 & 3 only 18 | if getline(1) =~ '^\(\f\|:\)\+([23][px]\?)' 19 | syntax include @cCode syntax/c.vim 20 | syntax match manCFuncDefinition display '\<\h\w*\>\s*('me=e-1 contained 21 | syntax match manCError display '^\s\+\[E\(\u\|\d\)\+\]' contained 22 | syntax match manSignal display '\C\<\zs\(SIG\|SIG_\|SA_\)\(\d\|\u\)\+\ze\(\W\|$\)' 23 | syntax region manSynopsis start='^\(LEGACY \)\?SYNOPSIS'hs=s+8 end='^\u[A-Z ]*$'me=e-30 keepend contains=manSectionHeading,@cCode,manCFuncDefinition,manHeaderFile 24 | syntax region manErrors start='^ERRORS'hs=s+6 end='^\u[A-Z ]*$'me=e-30 keepend contains=manSignal,manReference,manSectionHeading,manHeaderFile,manCError 25 | endif 26 | 27 | syntax match manFile display '\s\zs\~\?\/[0-9A-Za-z_*/$.{}<>-]*' contained 28 | syntax match manEnvVarFile display '\s\zs\$[0-9A-Za-z_{}]\+\/[0-9A-Za-z_*/$.{}<>-]*' contained 29 | syntax region manFiles start='^FILES'hs=s+5 end='^\u[A-Z ]*$'me=e-30 keepend contains=manReference,manSectionHeading,manHeaderFile,manURL,manEmail,manFile,manEnvVarFile 30 | 31 | syntax match manEnvVar display '\s\zs\(\u\|_\)\{3,}' contained 32 | syntax region manFiles start='^ENVIRONMENT'hs=s+11 end='^\u[A-Z ]*$'me=e-30 keepend contains=manReference,manSectionHeading,manHeaderFile,manURL,manEmail,manEnvVar 33 | 34 | hi def link manTitle Title 35 | hi def link manSectionHeading Statement 36 | hi def link manOptionDesc Constant 37 | hi def link manLongOptionDesc Constant 38 | hi def link manReference PreProc 39 | hi def link manCFuncDefinition Function 40 | hi def link manHeaderFile String 41 | hi def link manURL Underlined 42 | hi def link manEmail Underlined 43 | hi def link manCError Identifier 44 | hi def link manSignal Identifier 45 | hi def link manFile Identifier 46 | hi def link manEnvVarFile Identifier 47 | hi def link manEnvVar Identifier 48 | hi def link manHighlight Statement 49 | 50 | let b:current_syntax = 'man' 51 | 52 | " vim:set ft=vim et sw=2: 53 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### master 4 | 5 | - add "guards" for the autoload files 6 | - add syntax highlighting for files in FILES manpage section 7 | - syntax highlighting for environment variables in ENVIRONMENT manpage section 8 | - syntax bugfix: url's can't contain tab characters 9 | - add instructions for creating `viman` command line script (@alx741) 10 | - `q` mapping quits `vim-man` buffer 11 | - `g/` mapping for easier option search 12 | 13 | ### v0.1.0, Jan 24, 2015 14 | 15 | - split the main script into ftplugin, plugin and autoload directories 16 | - remove tab characters from the source and whitespace cleanup 17 | - remove script headers, give credits to the authors in the README 18 | - drop support for vim versions 5 and 6 19 | - use `keepjumps` so that jumplist is not messed up 20 | - consistently use longer VimL keywords (function, command, syntax etc) 21 | - improve error message shown when man page is not found 22 | - move scratch buffer local options to ft file 23 | - update manpage buffer name 24 | - `:Vman` - opens a manpage in vertical split 25 | - add `:Sman` (the same as `:Man`) for "interface" consistency 26 | - `tabstop=8` for manpages 27 | - add all tag mappings and map them to the same action 28 | - [[ and ]] mappings for section movement 29 | - remove the mapping "guards". If the user installed the plugin, they are most 30 | likely wanted. 31 | - enable (in vim's command line) to follow man commands 32 | - command line completion for `:Man`, `:Sman` and `:Vman` commands 33 | - switch to different implementation for `strip_file_names` completion helper 34 | function. vim's `map()` function errors out for larger lists 35 | - implement tests for completion "strip extension" function. Improve manpage 36 | filename stripping so it supports more cases. 37 | - improve manpage section argument handling for completion 38 | - $MANPATH environment variable has precedence when searching for manpath 39 | - remove duplicates from `:Man` command completion list 40 | - improve `[[` and `]]` mappings: manpage section can start only with letters 41 | - bugfix: manTitle syntax highlighting for TCL man pages 42 | - syntax highlighting for more functions in manpages 43 | - improved syntax highlighting for 'LEGACY SYNOPSIS' manpage section 44 | - `g:man_width` enables user to specify fixed manpage width 45 | - `K` within man buffer also jumps to a manpage under cursor 46 | - remove the default `K` mapping and enable users do define their own 47 | mapping to open a manpage for word under cursor 48 | - enable converting `nroff` files to manpages with `:Man`, `:Sman` and `:Vman` 49 | commands 50 | - add `(Sman)` and `(Vman)` mappings. `(Sman)` is the same as 51 | `(Man)` and ` 0 ? 0 : 1 44 | 45 | let $MANWIDTH = man#helpers#manwidth() 46 | let insensitive_flag = a:insensitive ? '-i' : '' 47 | 48 | let command = man#grep#command(a:path_glob, insensitive_flag, a:pattern) 49 | let s:job_number = jobstart('mangrep', 'sh', ['-c', command]) 50 | endfunction 51 | 52 | " }}} 53 | " man#grep#nvim#handle_async_output {{{1 54 | 55 | function! man#grep#nvim#handle_async_output() 56 | " we're not interested in the output of some lagging Mangrep process that 57 | " should've already been dead 58 | if s:not_currently_registered_job(v:job_data[0]) 59 | return 60 | end 61 | 62 | if v:job_data[1] ==# 'stdout' 63 | for one_line in v:job_data[2] 64 | " line format: 'manpage_file_name!line_number:line_text' 65 | " example: '/usr/share/man/man1/echo.1!123: line match example' 66 | " ! (exclamation mark) is used as a delimiter between a filename and " line num 67 | let manpage_file_name = matchstr(one_line, '^[^!]\+') 68 | let line_number = matchstr(one_line, '^[^!]\+!\zs\d\+') 69 | let line_text = matchstr(one_line, '^[^!]\+![^:]\+:\s*\zs.\{-}\ze\s*$') 70 | 71 | " example input: '/usr/share/man/man1/echo.1' 72 | " get manpage name: 'echo' and man section '1' 73 | let man_name = man#helpers#strip_dirname_and_extension(manpage_file_name) 74 | let section = matchstr(fnamemodify(manpage_file_name, ':h:t'), '^\(man\|cat\)\zs.*') 75 | 76 | let buf_num = man#grep#create_empty_buffer(man_name, section) 77 | call setqflist([{'bufnr': buf_num, 'lnum': line_number, 'text': line_text}], 'a') 78 | 79 | " jump to first result if command not invoked with bang 80 | if s:grep_not_bang > 0 && s:grep_opened_first_result ==# 0 81 | let s:grep_opened_first_result = 1 82 | cc 1 83 | " TODO: for some reason cc 1 does not trigger autocmd for loading man 84 | " page into current buffer, so we're doing it manually 85 | call man#grep#quickfix_get_page() 86 | exec 'norm! '.line_number.'G' 87 | endif 88 | endfor 89 | elseif v:job_data[1] ==# 'exit' 90 | echom 'Mangrep command done' 91 | endif 92 | endfunction 93 | 94 | function! s:not_currently_registered_job(job_num) 95 | return a:job_num !=# s:job_number 96 | endfunction 97 | 98 | " }}} 99 | -------------------------------------------------------------------------------- /doc/man.txt: -------------------------------------------------------------------------------- 1 | *man.txt* View man pages in vim. Grep for the man pages. 2 | 3 | Maintainer: Bruno Sutic 4 | 5 | INTRODUCTION *man* *vim-man* 6 | 7 | This plugin enables viewing and navigating manual pages in vim. 8 | |:Mangrep| command adds the possibility of grepping all the manpages. 9 | 10 | COMMANDS *man-commands* 11 | 12 | *man-:Man_command* 13 | :Man [args] Open man page in a horizontal split. The command has 14 | support for man page completion. 15 | Examples: 16 | :Man printf 17 | :Man 3 sprintf 18 | 19 | When editing 'nroff' files, this command can be invoked 20 | without arguments to open current file as a man page. 21 | 22 | *man-:Sman* 23 | :Sman [args] The same as |:Man_command|. 24 | 25 | *man-:Vman* 26 | :Vman [args] Like |:Man_command|, but open man page in a vertical 27 | split. 28 | 29 | *man-:Mangrep* 30 | :Mangrep[!] [args] Grep the content of manual pages. This feature is 31 | still in "beta". Results should be accurate, but you 32 | might experience hiccups when opening results from the 33 | quickfix list. Also please see |:Mangrep_strategy|. 34 | Example: 35 | :Mangrep 1 foobar 36 | 37 | If man page section number is not provided it will 38 | default to 1. So this command: 39 | :Mangrep foobar 40 | 41 | is equivalent to this one: 42 | :Mangrep 1 foobar 43 | 44 | The section defaults to 1 because searching all 45 | sections takes a long time. If you want to force 46 | searching all man sections, use *: 47 | :Mangrep * foobar 48 | 49 | Start a case insensitive search with the '-i' flag: 50 | :Mangrep -i 6 foobar 51 | 52 | Regex searches are possible too (the same as 53 | with 'grep -E') but remember to quote the search 54 | pattern: 55 | :Mangrep 6 'foo(bar|baz)' 56 | 57 | *man-:Mangrep_strategy* 58 | If you use neovim, |:Mangrep| command runs 59 | asynchronously in the background. 60 | 61 | If you have vim-dispatch plugin installed |:Mangrep| 62 | command also runs in the background. Access the 63 | results with :Copen command (may be called before 64 | the process is finished). 65 | 66 | With vanilla vim |:Mangrep| will block and make vim 67 | unusable until the command is done (and it can take a 68 | while). Installing vim-dispatch plugin is recommended, 69 | but if that is not possible consider running 70 | |:Mangrep| in another vim. That way your working vim instance 71 | stays usable. 72 | 73 | MAN *ft-man* 74 | 75 | The following mappings are defined when displaying a man page: 76 | 77 | *man-]]* 78 | ]] Jump to the next section heading. 79 | 80 | *man-[[* 81 | [[ Jump to the previous section heading. 82 | 83 | *man-CTRL-]* *man-g_CTRL-]* 84 | *man-g]* *man-CTRL-W_CTRL-]* 85 | *man-CTRL-W_]* *man-CTRL-W_g}* 86 | *man-CTRL-W_g_CTRL-]* 87 | *man-CTRL-W_g]* *man-CTRL-W_}* 88 | CTRL-] Jump to man page for a word under cursor (all the 89 | mappings behave the same). 90 | 91 | *man-K* 92 | K Same as |man-CTRL-]|. 93 | 94 | *man-CTRL-T* 95 | CTRL-T Jump back to the previous man page. 96 | 97 | *man-g/* *g/* 98 | g/ Start option search. 99 | 100 | MAPS *man-maps* 101 | 102 | No global mappings are defined by default, but you can define the following 103 | in your |vimrc|: 104 | 105 | - open man page for a word under cursor in a horizontal split 106 | > 107 | map k (Man) 108 | < 109 | - open man page for a word under cursor in a vertical split 110 | > 111 | map v (Vman) 112 | < 113 | OPTIONS *man-options* 114 | 115 | Default man command is '/usr/bin/man', but it can be changed: 116 | > 117 | let g:vim_man_cmd = 'LANG=ja_JP.UTF-8 /usr/bin/man' 118 | < 119 | CONTRIBUTING *man-contributing* *man-bugs* 120 | 121 | Contributing and bug fixes are welcome. If you have an idea for a new feature 122 | please get in touch by opening an issue so we can discuss it first. 123 | 124 | Github repo: 125 | 126 | https://github.com/vim-utils/vim-man 127 | 128 | List of open issues: 129 | 130 | https://github.com/vim-utils/vim-man/issues 131 | 132 | CREDITS *man-credits* 133 | 134 | This plugin is an improvement of vim's original man page plugin. These people 135 | created and maintain (or maintained) the original man.vim plugin: 136 | 137 | * SungHyun Nam 138 | * Gautam H. Mudunuri 139 | * Johannes Tanzler 140 | 141 | LICENSE *man-license* 142 | 143 | Same as Vim itself (see |license|) 144 | 145 | vim:tw=78:ts=8:ft=help:norl: 146 | -------------------------------------------------------------------------------- /autoload/man.vim: -------------------------------------------------------------------------------- 1 | " load guard {{{1 2 | 3 | if exists('g:autoloaded_man') 4 | finish 5 | endif 6 | let g:autoloaded_man = 1 7 | 8 | " }}} 9 | " variable initialization {{{1 10 | 11 | let s:man_tag_depth = 0 12 | 13 | " }}} 14 | " man#get_page {{{1 15 | 16 | function! man#get_page(split_type, ...) 17 | if a:0 == 0 18 | call s:handle_nroff_file_or_error(a:split_type) 19 | return 20 | elseif a:0 == 1 21 | let sect = '' 22 | let page = a:1 23 | elseif a:0 >= 2 24 | let sect = a:1 25 | let page = a:2 26 | endif 27 | 28 | if sect !=# '' && !s:manpage_exists(sect, page) 29 | let sect = '' 30 | endif 31 | if !s:manpage_exists(sect, page) 32 | call man#helpers#error("No manual entry for '".page."'.") 33 | return 34 | endif 35 | 36 | call s:update_man_tag_variables() 37 | call s:get_new_or_existing_man_window(a:split_type) 38 | call man#helpers#set_manpage_buffer_name(page, sect) 39 | call man#helpers#load_manpage_text(page, sect) 40 | endfunction 41 | 42 | function! s:manpage_exists(sect, page) 43 | if a:page ==# '' 44 | return 0 45 | endif 46 | let find_arg = man#helpers#find_arg() 47 | let where = system(g:vim_man_cmd.' '.find_arg.' '.man#helpers#get_cmd_arg(a:sect, a:page)) 48 | if where !~# '^\s*/' 49 | " result does not look like a file path 50 | return 0 51 | else 52 | " found a manpage 53 | return 1 54 | endif 55 | endfunction 56 | 57 | " }}} 58 | " :Man command in nroff files {{{1 59 | 60 | " handles :Man command invocation with zero arguments 61 | function! s:handle_nroff_file_or_error(split_type) 62 | " :Man command can be invoked in 'nroff' files to convert it to a manpage 63 | if &filetype ==# 'nroff' 64 | if filereadable(expand('%')) 65 | return s:get_nroff_page(a:split_type, expand('%:p')) 66 | else 67 | return man#helpers#error("Can't open file ".expand('%')) 68 | endif 69 | else 70 | " simulating vim's error when not enough args provided 71 | return man#helpers#error('E471: Argument required') 72 | endif 73 | endfunction 74 | 75 | " open a nroff file as a manpage 76 | function! s:get_nroff_page(split_type, nroff_file) 77 | call s:update_man_tag_variables() 78 | call s:get_new_or_existing_man_window(a:split_type) 79 | silent exec 'edit '.fnamemodify(a:nroff_file, ':t').'\ manpage\ (from\ nroff)' 80 | call man#helpers#load_manpage_text(a:nroff_file, '') 81 | endfunction 82 | 83 | " }}} 84 | " man#get_page_from_cword {{{1 85 | 86 | function! man#get_page_from_cword(split_type, cnt) 87 | if a:cnt == 0 88 | let old_isk = &iskeyword 89 | if &filetype ==# 'man' 90 | " when in a manpage try determining section from a word like this 'printf(3)' 91 | setlocal iskeyword+=(,),: 92 | endif 93 | let str = expand('') 94 | let &l:iskeyword = old_isk 95 | let page = matchstr(str, '\(\k\|:\)\+') 96 | let sect = matchstr(str, '(\zs[^)]*\ze)') 97 | if sect !~# '^[0-9nlpo][a-z]*$' || sect ==# page 98 | let sect = '' 99 | endif 100 | else 101 | let sect = a:cnt 102 | let old_isk = &iskeyword 103 | setlocal iskeyword+=: 104 | let page = expand('') 105 | let &l:iskeyword = old_isk 106 | endif 107 | call man#get_page(a:split_type, sect, page) 108 | endfunction 109 | 110 | " }}} 111 | " man#pop_page {{{1 112 | 113 | function! man#pop_page() 114 | if s:man_tag_depth <= 0 115 | return 116 | endif 117 | let s:man_tag_depth -= 1 118 | let buffer = s:man_tag_buf_{s:man_tag_depth} 119 | let line = s:man_tag_lin_{s:man_tag_depth} 120 | let column = s:man_tag_col_{s:man_tag_depth} 121 | " jumps to exact buffer, line and column 122 | exec buffer.'b' 123 | exec line 124 | exec 'norm! '.column.'|' 125 | unlet s:man_tag_buf_{s:man_tag_depth} 126 | unlet s:man_tag_lin_{s:man_tag_depth} 127 | unlet s:man_tag_col_{s:man_tag_depth} 128 | endfunction 129 | 130 | " }}} 131 | " script local helpers {{{1 132 | 133 | function! s:update_man_tag_variables() 134 | let s:man_tag_buf_{s:man_tag_depth} = bufnr('%') 135 | let s:man_tag_lin_{s:man_tag_depth} = line('.') 136 | let s:man_tag_col_{s:man_tag_depth} = col('.') 137 | let s:man_tag_depth += 1 138 | endfunction 139 | 140 | function! s:get_new_or_existing_man_window(split_type) 141 | if &filetype != 'man' 142 | let thiswin = winnr() 143 | exec "norm! \b" 144 | if winnr() > 1 145 | exec 'norm! '.thiswin."\w" 146 | while 1 147 | if &filetype == 'man' 148 | break 149 | endif 150 | exec "norm! \w" 151 | if thiswin == winnr() 152 | break 153 | endif 154 | endwhile 155 | endif 156 | if &filetype != 'man' 157 | if a:split_type == 'vertical' 158 | vnew 159 | elseif a:split_type == 'tab' 160 | tabnew 161 | else 162 | new 163 | endif 164 | endif 165 | endif 166 | endfunction 167 | 168 | " }}} 169 | " vim:set ft=vim et sw=2: 170 | -------------------------------------------------------------------------------- /autoload/man/grep.vim: -------------------------------------------------------------------------------- 1 | " load guard {{{1 2 | 3 | if exists('g:autoloaded_man_grep') 4 | finish 5 | endif 6 | let g:autoloaded_man_grep = 1 7 | 8 | " }}} 9 | " man#grep#run {{{1 10 | 11 | function! man#grep#run(bang, ...) 12 | " argument handling and sanitization 13 | if a:0 ==# 1 14 | " just the pattern is provided 15 | let grep_case_insensitive = 0 16 | " defaulting section to 1 17 | let section = '1' 18 | let pattern = a:1 19 | 20 | elseif a:0 ==# 2 21 | " section + pattern OR grep `-i` flag + pattern 22 | if a:1 ==# '-i' 23 | " grep `-i` flag + pattern 24 | let grep_case_insensitive = 1 25 | " defaulting section to 1 26 | let section = '1' 27 | let pattern = a:1 28 | else 29 | " section + pattern 30 | let grep_case_insensitive = 0 31 | let section = man#helpers#extract_permitted_section_value(a:1) 32 | if section ==# '' 33 | " don't run an expensive grep on *all* sections if a user made a typo 34 | return man#helpers#error('Unknown man section '.a:1) 35 | endif 36 | let pattern = a:2 37 | endif 38 | 39 | elseif a:0 ==# 3 40 | " grep `-i` flag + section + pattern 41 | if a:1 ==# '-i' 42 | let grep_case_insensitive = 1 43 | else 44 | return man#helpers#error('Unknown Mangrep argument '.a:1) 45 | endif 46 | let section = man#helpers#extract_permitted_section_value(a:2) 47 | if section ==# '' 48 | " don't run an expensive grep on *all* sections if a user made a typo 49 | return man#helpers#error('Unknown man section '.a:2) 50 | endif 51 | let pattern = a:3 52 | 53 | elseif a:0 >=# 4 54 | return man#helpers#error('Too many arguments') 55 | endif 56 | " argument handling end 57 | 58 | let manpath = man#helpers#manpath() 59 | if manpath =~# '^\s*$' 60 | return 61 | endif 62 | " create new quickfix list 63 | call setqflist([], ' ') 64 | 65 | if has('nvim') 66 | " strategy for running :Mangrep with neovim's async job 67 | let path_glob = man#helpers#get_path_glob(manpath, section, '*', ' ') 68 | call man#grep#nvim#run(a:bang, grep_case_insensitive, pattern, path_glob) 69 | elseif exists('g:loaded_dispatch') 70 | 71 | " strategy for running :Mangrep with vim-dispatch async job 72 | let path_glob = man#helpers#get_path_glob(manpath, section, '*', ' ') 73 | call man#grep#dispatch#run(a:bang, grep_case_insensitive, pattern, path_glob) 74 | else 75 | 76 | " Run :Mangrep command in plain old vim. It blocks until the job is done. 77 | let path_glob = man#helpers#get_path_glob(manpath, section, '', ',') 78 | let matching_files = man#helpers#expand_path_glob(path_glob, '*') 79 | call man#grep#vanilla#run(a:bang, grep_case_insensitive, pattern, matching_files) 80 | endif 81 | endfunction 82 | 83 | " }}} 84 | " man#quickfix_get_page {{{1 85 | 86 | function! man#grep#quickfix_get_page() 87 | let manpage_name = get(b:, 'man_name') 88 | let manpage_section = get(b:, 'man_section') 89 | set nobuflisted 90 | " TODO: switch to existing 'man' window or create a split 91 | call man#helpers#set_manpage_buffer_name(manpage_name, manpage_section) 92 | call man#helpers#load_manpage_text(manpage_name, manpage_section) 93 | endfunction 94 | 95 | " }}} 96 | " man#grep#create_empty_buffer {{{1 97 | 98 | function! man#grep#create_empty_buffer(name, section) 99 | let buffer_name = a:name.'('.a:section.')' 100 | if bufnr(buffer_name) >=# 0 101 | " buffer already exists 102 | return bufnr(buffer_name) 103 | endif 104 | let buffer_num = bufnr(buffer_name, 1) 105 | call man#grep#setup_manpage_buffer(buffer_num, a:name, a:section) 106 | return buffer_num 107 | endfunction 108 | 109 | " }}} 110 | " man#grep#setup_manpage_buffer {{{1 111 | 112 | " saving manpage name and section as buffer variable, so they're 113 | " easy to get later when in the buffer 114 | function! man#grep#setup_manpage_buffer(buffer_num, name, section) 115 | call setbufvar(a:buffer_num, 'man_name', a:name) 116 | call setbufvar(a:buffer_num, 'man_section', a:section) 117 | exec 'au BufEnter call man#grep#quickfix_get_page()' 118 | endfunction 119 | 120 | " }}} 121 | " man#grep#command {{{1 122 | 123 | " TODO: can this whole command be simplified? 124 | function! man#grep#command(path_glob, insensitive_flag, pattern) 125 | let command = 'ls '.a:path_glob.' 2>/dev/null |' 126 | " xargs is used to feed manpages one-by-one 127 | let command .= 'xargs -I{} -n1 sh -c "manpage={};' 128 | " inner variables execute within a shell started by xargs 129 | let command .= g:vim_man_cmd.' \$manpage 2>/dev/null|col -b|' 130 | " if the first manpage line is blank, remove it (stupid semicolons are required) 131 | let command .= "sed '1 {;/^\s*$/d;}'|" 132 | let command .= 'grep '.a:insensitive_flag.' -nE '.a:pattern.'|' 133 | " prepending filename to each line of grep output, followed by a ! 134 | let command .= 'sed "s,^,\$manpage!,"' 135 | let command .= '"' 136 | return command 137 | endfunction 138 | 139 | " }}} 140 | " vim:set ft=vim et sw=2: 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # man.vim 2 | 3 | View man pages in vim. Grep for the man pages. 4 | 5 | ### Features and Usage 6 | 7 | ##### Viewing man pages 8 | 9 | - `:Man printf` - open `printf(1)` man page in a split 10 | - `:Vman 3 putc` - open `putc(3)` man page in a vertical split (read more 11 | [here](http://unix.stackexchange.com/a/3587/80379) on what the 12 | manual page numbers mean, they are really useful) 13 | - `:Man pri` - command completion for man page names 14 | - `:Man 3 pri` - completion "respects" the man page section argument 15 | - `:Man 6 ` - list all man pages from section 6 16 | 17 | ##### When editing `nroff` files 18 | 19 | - `:Man` - (without arguments) open a current `nroff` file as a man page 20 | 21 | ##### When inside a man page buffer 22 | 23 | - `[[` and `]]` - jump to prev/next section heading 24 | - `Ctrl-]` - jump to man page for a word under cursor (works nicely with 25 | specially highlighted references to other man pages i.e. `printf(3)`), also 26 | defined for other tag mappings like `g_Ctrl-]`, `Ctrl-W_Ctrl-]` etc. 27 | - `K` - same as `Ctrl-]` 28 | - `Ctrl-T` - jump \*back* to the previous man page 29 | - `g/` - start option search (useful for quickly jumping to man page option 30 | description, example `--quiet` or `-q`) 31 | - `gx` - open a link under cursor in a browser 32 | ([vim feature](http://vimdoc.sourceforge.net/htmldoc/pi_netrw.html#netrw-gx)) 33 | - `gf` - jump to a file under cursor 34 | ([vim feature](http://vimdoc.sourceforge.net/htmldoc/editing.html#gf), 35 | works nicely with C header files often found in section 2 and 3 man pages i.e. 36 | ``) 37 | - `q` - quit `vim-man` buffer 38 | 39 | ##### Using from the shell 40 | 41 | You can use vim-man from the shell (instead of standard `man` program) using 42 | the following script: 43 | 44 | #! /bin/sh 45 | vim -c "Man $1 $2" -c 'silent only' 46 | 47 | Save it in `/usr/bin/` as a file named `viman`, give it execution 48 | permission with: 49 | 50 | $ chmod +x /usr/bin/viman 51 | 52 | Then from your shell you can read a DOC with: 53 | 54 | $ viman doc 55 | 56 | Or you can use the alias `alias man=viman` so you can do (as usual): 57 | 58 | $ man doc 59 | 60 | ##### Searching/grepping man pages 61 | 62 | Also see [About Mangrep](#about-mangrep) 63 | 64 | - `:Mangrep 1 foobar` - search for "foobar" in all section 1 man pages 65 | - `:Mangrep foobar` - same as `:Mangrep 1 foobar` (grepping all man sections 66 | by default would take too long) 67 | - `:Mangrep * foobar` - force search \*all* man sections 68 | - `:Mangrep -i 6 foobar` - case insensitive search 69 | - `:Mangrep 6 '(foo|bar|baz)'` - regex search (`Mangrep` uses `grep -E`), just 70 | remember to quote the search pattern 71 | 72 | ##### Defining mappings in `.vimrc` 73 | 74 | No mappings are defined by default. 75 | 76 | - `map k (Man)` - open man page for word under cursor in a horizontal 77 | split 78 | - `map v (Vman)` - open man page for word under cursor in a vertical 79 | split 80 | 81 | ### About Mangrep 82 | 83 | This feature is still in beta. 84 | Please help fix the [issues](https://github.com/vim-utils/vim-man/issues/). 85 | 86 | `Mangrep` populates quickfix list with the results. While they should be 87 | accurate, you might experience hiccups when opening those results. 88 | 89 | Running `Mangrep`: 90 | 91 | - the command runs in the background if you use neovim 92 | - The command runs in the background if you have 93 | [vim-dispatch](https://github.com/tpope/vim-dispatch) installed. Access the 94 | results with 95 | [`:Copen` command](https://github.com/tpope/vim-dispatch#background-builds) 96 | (may be called before the process is finished). 97 | - If you have vanilla vim the command will \*block* and make vim unusable 98 | until done (and it can take a while).
99 | Installing [vim-dispatch](https://github.com/tpope/vim-dispatch) 100 | is recommended. Or at least run `Mangrep` in another vim so your working vim 101 | instance stays usable. 102 | 103 | ### Installation 104 | 105 | Just use your favorite plugin manager. 106 | 107 | If you were previously using `man.vim` that comes with vim by default, please 108 | remove this line `runtime! ftplugin/man.vim` from your `.vimrc`. It's known to 109 | be causing [issues](https://github.com/vim-utils/vim-man/issues/23) with this 110 | plugin. 111 | 112 | ### Contributing 113 | 114 | Contributing and bug fixes are welcome. If you have an idea for a new feature 115 | please get in touch by opening an issue so we can discuss it first. 116 | 117 | ### Credits 118 | 119 | Vim by default comes with man page viewer, as decribed in 120 | [find-manpage](http://vimdoc.sourceforge.net/htmldoc/usr_12.html#find-manpage). 121 | This work is the improvement of vim's original man page plugin. The list of 122 | improvements is [here](improvements.md). 123 | 124 | These people created and maintain (or maintained) `man.vim` that comes with vim 125 | itself: 126 | * SungHyun Nam 127 | * Gautam H. Mudunuri 128 | * Johannes Tanzler 129 | 130 | ### License 131 | 132 | Vim license, see `:help license`. 133 | -------------------------------------------------------------------------------- /autoload/man/helpers.vim: -------------------------------------------------------------------------------- 1 | " load guard {{{1 2 | 3 | if exists('g:autoloaded_man_helpers') 4 | finish 5 | endif 6 | let g:autoloaded_man_helpers = 1 7 | 8 | " }}} 9 | " man#helpers#section_arg and man#helpers#find_arg {{{1 10 | 11 | let s:section_arg = '' 12 | let s:find_arg = '-w' 13 | try 14 | if !has('win32') && $OSTYPE !~ 'cygwin\|linux' && system('uname -s') =~ 'SunOS' && system('uname -r') =~ '^5' 15 | let s:section_arg = '-s' 16 | let s:find_arg = '-l' 17 | endif 18 | catch /E145:/ 19 | " Ignore the error in restricted mode 20 | endtry 21 | 22 | function! man#helpers#section_arg() 23 | return s:section_arg 24 | endfunction 25 | 26 | function! man#helpers#find_arg() 27 | return s:find_arg 28 | endfunction 29 | 30 | " }}} 31 | " man#helpers#error {{{1 32 | 33 | function! man#helpers#error(str) 34 | echohl ErrorMsg 35 | echomsg a:str 36 | echohl None 37 | endfunction 38 | 39 | " }}} 40 | " man#helpers#get_cmd_arg {{{1 41 | 42 | function! man#helpers#get_cmd_arg(sect, page) 43 | if a:sect == '' 44 | return a:page 45 | else 46 | return man#helpers#section_arg().' '.a:sect.' '.a:page 47 | endif 48 | endfunction 49 | 50 | " }}} 51 | " man#helpers#set_manpage_buffer_name {{{1 52 | 53 | function! man#helpers#set_manpage_buffer_name(page, section) 54 | silent exec 'edit '.s:manpage_buffer_name(a:page, a:section) 55 | endfunction 56 | 57 | function! s:manpage_buffer_name(page, section) 58 | if !empty(a:section) 59 | return a:page.'('.a:section.')\ manpage' 60 | else 61 | return a:page.'\ manpage' 62 | endif 63 | endfunction 64 | 65 | " }}} 66 | " man#helpers#load_manpage_text {{{1 67 | 68 | function! man#helpers#load_manpage_text(page, section) 69 | setlocal modifiable 70 | silent keepj norm! 1GdG 71 | let $MANWIDTH = man#helpers#manwidth() 72 | silent exec 'r!'.g:vim_man_cmd.' '.man#helpers#get_cmd_arg(a:section, a:page). ' 2>/dev/null | col -b' 73 | call s:remove_blank_lines_from_top_and_bottom() 74 | setlocal filetype=man 75 | setlocal nomodifiable 76 | endfunction 77 | 78 | function! s:remove_blank_lines_from_top_and_bottom() 79 | while line('$') > 1 && getline(1) =~ '^\s*$' 80 | silent keepj norm! ggdd 81 | endwhile 82 | while line('$') > 1 && getline('$') =~ '^\s*$' 83 | silent keepj norm! Gdd 84 | endwhile 85 | silent keepj norm! gg 86 | endfunction 87 | 88 | " }}} 89 | " man#helpers#manwidth {{{1 90 | 91 | " Default manpage width is the width of the screen. Change this with 92 | " 'g:man_width'. Example: 'let g:man_width = 120'. 93 | function! man#helpers#manwidth() 94 | if exists('g:man_width') 95 | return g:man_width 96 | else 97 | return winwidth(0) 98 | end 99 | endfunction 100 | 101 | " }}} 102 | " man#helpers#extract_permitted_section_value {{{1 103 | 104 | function! man#helpers#extract_permitted_section_value(section_arg) 105 | if a:section_arg =~# '^*$' 106 | " matches all dirs with a glob 'man*' 107 | return a:section_arg 108 | elseif a:section_arg =~# '^\d[xp]\?$' 109 | " matches dirs: man1, man1x, man1p 110 | return a:section_arg 111 | elseif a:section_arg =~# '^[nlpo]$' 112 | " matches dirs: mann, manl, manp, mano 113 | return a:section_arg 114 | elseif a:section_arg =~# '^\d\a\+$' 115 | " take only first digit, sections 3pm, 3ssl, 3tiff, 3tcl are searched in man3 116 | return matchstr(a:section_arg, '^\d') 117 | else 118 | return '' 119 | endif 120 | endfunction 121 | 122 | " }}} 123 | " man#helpers#get_path_glob {{{1 124 | 125 | " creates a string containing shell globs suitable to finding matching manpages 126 | function! man#helpers#get_path_glob(manpath, section, file, separator) 127 | let section_part = empty(a:section) ? '*' : a:section 128 | let file_part = empty(a:file) ? '' : a:file 129 | let man_globs = substitute(a:manpath.':', ':', '/*man'.section_part.'/'.file_part.a:separator, 'g') 130 | let cat_globs = substitute(a:manpath.':', ':', '/*cat'.section_part.'/'.file_part.a:separator, 'g') 131 | " remove one unecessary path separator from the end 132 | let cat_globs = substitute(cat_globs, a:separator.'$', '', '') 133 | return man_globs.cat_globs 134 | endfunction 135 | 136 | " }}}1 137 | " man#helpers#expand_path_glob {{{1 138 | 139 | " path glob expansion to get filenames 140 | function! man#helpers#expand_path_glob(path_glob, manpage_prefix) 141 | if empty(a:manpage_prefix) 142 | let manpage_part = '*' 143 | elseif a:manpage_prefix =~# '*$' 144 | " asterisk is already present 145 | let manpage_part = a:manpage_prefix 146 | else 147 | let manpage_part = a:manpage_prefix.'*' 148 | endif 149 | return split(globpath(a:path_glob, manpage_part, 1), '\n') 150 | endfunction 151 | 152 | " }}} 153 | " man#helpers#strip_dirname_and_extension {{{1 154 | 155 | " first strips the directory name from the match, then the extension 156 | function! man#helpers#strip_dirname_and_extension(manpage_path) 157 | return man#helpers#strip_extension(fnamemodify(a:manpage_path, ':t')) 158 | endfunction 159 | 160 | " }}} 161 | " man#helpers#strip_extension {{{1 162 | 163 | " Public function so it can be used for testing. 164 | " Check 'manpage_extension_stripping_test.vim' for example input and output 165 | " this regex produces. 166 | function! man#helpers#strip_extension(filename) 167 | return substitute(a:filename, '\.\(\d\a*\|n\|ntcl\)\(\.\a*\|\.bz2\)\?$', '', '') 168 | endfunction 169 | 170 | " }}} 171 | " man#helpers#manpath {{{1 172 | 173 | " fetches a colon separated list of paths where manpages are stored 174 | function! man#helpers#manpath() 175 | " We don't expect manpath to change, so after first invocation it's 176 | " saved/cached in a script variable to speed things up on later invocations. 177 | if !exists('s:manpath') 178 | " perform a series of commands until manpath is found 179 | let s:manpath = $MANPATH 180 | if s:manpath ==# '' 181 | let s:manpath = system('manpath 2>/dev/null') 182 | if s:manpath ==# '' 183 | let s:manpath = system('man '.man#helpers#find_arg.' 2>/dev/null') 184 | endif 185 | endif 186 | " strip trailing newline for output from the shell 187 | let s:manpath = substitute(s:manpath, '\n$', '', '') 188 | endif 189 | return s:manpath 190 | endfunction 191 | 192 | " }}} 193 | " vim:set ft=vim et sw=2: 194 | -------------------------------------------------------------------------------- /test/manpage_extension_stripping_test.vim: -------------------------------------------------------------------------------- 1 | " run the test with this command from project root dir: 2 | " $ vim "+so test/manpage_extension_stripping_test.vim" 3 | " 4 | " Output is 'OK' if tests pass, otherwise failing test cases are displayed. 5 | " Shell exit code is set to 0 if tests pass, 1 otherwise. 6 | 7 | source autoload/man/helpers.vim 8 | 9 | " test cases are split into sections: 10 | " 1. simplest cases 11 | " 2. various extensions (3pm, 1ssl, 3tcl etc) 12 | " 3. harder cases (with numbers) 13 | " 4. extensions without numbers 14 | " 5. various zip extensions (xz, bz2, lzma) 15 | let s:test_cases = [ 16 | \ { 'in': 'bison.1', 'out': 'bison' }, 17 | \ { 'in': 'bison.1.gz', 'out': 'bison' }, 18 | \ { 'in': '[.1', 'out': '[' }, 19 | \ { 'in': '[.1.gz', 'out': '[' }, 20 | \ { 'in': 'c++.1', 'out': 'c++' }, 21 | \ { 'in': 'c++.1.gz', 'out': 'c++' }, 22 | \ { 'in': 'c99.1', 'out': 'c99' }, 23 | \ { 'in': 'c99.1.gz', 'out': 'c99' }, 24 | \ { 'in': 'X.7', 'out': 'X' }, 25 | \ { 'in': 'X.7.gz', 'out': 'X' }, 26 | \ { 'in': 'codesign_allocate.1', 'out': 'codesign_allocate' }, 27 | \ { 'in': 'codesign_allocate.1.gz', 'out': 'codesign_allocate' }, 28 | \ { 'in': 'llvm-cov.1', 'out': 'llvm-cov' }, 29 | \ { 'in': 'llvm-cov.1.gz', 'out': 'llvm-cov' }, 30 | \ { 'in': 'm4.1', 'out': 'm4' }, 31 | \ { 'in': 'm4.1.gz', 'out': 'm4' }, 32 | \ { 'in': 'arch.3', 'out': 'arch' }, 33 | \ { 'in': 'arch.3.gz', 'out': 'arch' }, 34 | \ { 'in': 'a.out.5', 'out': 'a.out' }, 35 | \ { 'in': 'a.out.5.gz', 'out': 'a.out' }, 36 | \ { 'in': 'RunTargetUnitTests.1', 'out': 'RunTargetUnitTests' }, 37 | \ { 'in': 'RunTargetUnitTests.1.gz', 'out': 'RunTargetUnitTests' }, 38 | \ { 'in': 'git-credential-cache--daemon.1', 'out': 'git-credential-cache--daemon' }, 39 | \ { 'in': 'git-credential-cache--daemon.1.gz', 'out': 'git-credential-cache--daemon' }, 40 | \ { 'in': 'xcb_dri2_connect.3', 'out': 'xcb_dri2_connect' }, 41 | \ { 'in': 'xcb_dri2_connect.3.gz', 'out': 'xcb_dri2_connect' }, 42 | \ { 'in': 'Magick++-config.1', 'out': 'Magick++-config' }, 43 | \ { 'in': 'Magick++-config.1.gz', 'out': 'Magick++-config' }, 44 | \ 45 | \ { 'in': 'Date::Format.3pm', 'out': 'Date::Format' }, 46 | \ { 'in': 'Date::Format.3pm.gz', 'out': 'Date::Format' }, 47 | \ { 'in': 'Date::Format5.16.3pm', 'out': 'Date::Format5.16' }, 48 | \ { 'in': 'Date::Format5.16.3pm.gz', 'out': 'Date::Format5.16' }, 49 | \ { 'in': 'ca.1ssl', 'out': 'ca' }, 50 | \ { 'in': 'ca.1ssl.gz', 'out': 'ca' }, 51 | \ { 'in': 'CA.pl.1ssl', 'out': 'CA.pl' }, 52 | \ { 'in': 'CA.pl.1ssl.gz', 'out': 'CA.pl' }, 53 | \ { 'in': 'crl2pkcs7.1ssl', 'out': 'crl2pkcs7' }, 54 | \ { 'in': 'crl2pkcs7.1ssl.gz', 'out': 'crl2pkcs7' }, 55 | \ { 'in': 'TIFFClose.3tiff', 'out': 'TIFFClose' }, 56 | \ { 'in': 'TIFFClose.3tiff.gz', 'out': 'TIFFClose' }, 57 | \ { 'in': 'Tcl_Access.3tcl', 'out': 'Tcl_Access' }, 58 | \ { 'in': 'Tcl_Access.3tcl.gz', 'out': 'Tcl_Access' }, 59 | \ { 'in': 'errinfo.1m', 'out': 'errinfo' }, 60 | \ { 'in': 'errinfo.1m.gz', 'out': 'errinfo' }, 61 | \ { 'in': 'filebyproc.d.1m', 'out': 'filebyproc.d' }, 62 | \ { 'in': 'filebyproc.d.1m.gz', 'out': 'filebyproc.d' }, 63 | \ { 'in': 'CCCrypt.3cc', 'out': 'CCCrypt' }, 64 | \ { 'in': 'CCCrypt.3cc.gz', 'out': 'CCCrypt' }, 65 | \ { 'in': 'CC_SHA256_Final.3cc', 'out': 'CC_SHA256_Final' }, 66 | \ { 'in': 'CC_SHA256_Final.3cc.gz', 'out': 'CC_SHA256_Final' }, 67 | \ { 'in': 'PAIR_NUMBER.3x', 'out': 'PAIR_NUMBER' }, 68 | \ { 'in': 'PAIR_NUMBER.3x.gz', 'out': 'PAIR_NUMBER' }, 69 | \ { 'in': '_nc_freeall.3x', 'out': '_nc_freeall' }, 70 | \ { 'in': '_nc_freeall.3x.gz', 'out': '_nc_freeall' }, 71 | \ { 'in': 'pcap.3pcap', 'out': 'pcap' }, 72 | \ { 'in': 'pcap.3pcap.gz', 'out': 'pcap' }, 73 | \ { 'in': 'glColorMask.3G', 'out': 'glColorMask' }, 74 | \ { 'in': 'glColorMask.3G.gz', 'out': 'glColorMask' }, 75 | \ { 'in': 'glUniform1f.3G', 'out': 'glUniform1f' }, 76 | \ { 'in': 'glUniform1f.3G.gz', 'out': 'glUniform1f' }, 77 | \ { 'in': 'readline.3readline.gz', 'out': 'readline' }, 78 | \ { 'in': 'alias.1p', 'out': 'alias' }, 79 | \ { 'in': 'alias.1p.gz', 'out': 'alias' }, 80 | \ 81 | \ { 'in': 'aclocal-1.15.1', 'out': 'aclocal-1.15' }, 82 | \ { 'in': 'aclocal-1.15.1.gz', 'out': 'aclocal-1.15' }, 83 | \ { 'in': '2to3-2.7.1.gz', 'out': '2to3-2.7' }, 84 | \ { 'in': 'c++-4.2.1', 'out': 'c++-4.2' }, 85 | \ { 'in': 'g++.1.gz', 'out': 'g++' }, 86 | \ { 'in': 'g++-4.6.1.gz', 'out': 'g++-4.6' }, 87 | \ 88 | \ { 'in': 'S3.n', 'out': 'S3' }, 89 | \ { 'in': 'S3.n.gz', 'out': 'S3' }, 90 | \ { 'in': 'TclX.n', 'out': 'TclX' }, 91 | \ { 'in': 'TclX.n.gz', 'out': 'TclX' }, 92 | \ { 'in': 'apply.ntcl', 'out': 'apply' }, 93 | \ { 'in': 'apply.ntcl.gz', 'out': 'apply' }, 94 | \ { 'in': 'base32.n', 'out': 'base32' }, 95 | \ { 'in': 'base32.n.gz', 'out': 'base32' }, 96 | \ { 'in': 'ttk::button.ntcl', 'out': 'ttk::button' }, 97 | \ { 'in': 'ttk::button.ntcl.gz', 'out': 'ttk::button' }, 98 | \ 99 | \ { 'in': 'bison.1.z', 'out': 'bison' }, 100 | \ { 'in': 'bison.1.Z', 'out': 'bison' }, 101 | \ { 'in': 'bison.1.lz', 'out': 'bison' }, 102 | \ { 'in': 'bison.1.xz', 'out': 'bison' }, 103 | \ { 'in': 'bison.1.bz2', 'out': 'bison' }, 104 | \ { 'in': 'bison.1.lzma', 'out': 'bison' }, 105 | \ { 'in': '2to3-2.7.1.z', 'out': '2to3-2.7' }, 106 | \ { 'in': '2to3-2.7.1.Z', 'out': '2to3-2.7' }, 107 | \ { 'in': '2to3-2.7.1.lz', 'out': '2to3-2.7' }, 108 | \ { 'in': '2to3-2.7.1.xz', 'out': '2to3-2.7' }, 109 | \ { 'in': '2to3-2.7.1.bz2', 'out': '2to3-2.7' }, 110 | \ { 'in': '2to3-2.7.1.lzma', 'out': '2to3-2.7' } 111 | \ ] 112 | 113 | function! s:display_test_output(failing_cases) 114 | let output_string = '' 115 | for case in a:failing_cases 116 | let output_string .= 'In: '.case['in'].', out: '.case['out'].', result: '.case['got']."\n" 117 | endfor 118 | exec '!echo '.shellescape(output_string) 119 | endfunction 120 | 121 | function! s:exit(failing_cases) 122 | if empty(a:failing_cases) 123 | exec '!echo "OK"' 124 | qa! 125 | else 126 | call s:display_test_output(a:failing_cases) 127 | cq! 128 | endif 129 | endfunction 130 | 131 | function! s:run_test_cases() 132 | let failing_cases = [] 133 | for case in s:test_cases 134 | let output = man#helpers#strip_extension(case.in) 135 | if output !=# case.out 136 | call add(failing_cases, {'in': case.in, 'out': case.out, 'got': output}) 137 | endif 138 | endfor 139 | call s:exit(failing_cases) 140 | endfunction 141 | 142 | call s:run_test_cases() 143 | --------------------------------------------------------------------------------