diff options
Diffstat (limited to 'runtime/autoload/man.vim')
-rw-r--r-- | runtime/autoload/man.vim | 216 |
1 files changed, 132 insertions, 84 deletions
diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index 153f1afed8..dab88fde23 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -1,4 +1,4 @@ -" Maintainer: Anmol Sethi <anmol@aubble.com> +" Maintainer: Anmol Sethi <hi@nhooyr.io> if exists('s:loaded_man') finish @@ -7,22 +7,10 @@ let s:loaded_man = 1 let s:find_arg = '-w' let s:localfile_arg = v:true " Always use -l if possible. #6683 -let s:section_arg = '-s' +let s:section_arg = '-S' -function! s:init_section_flag() - call system(['env', 'MANPAGER=cat', 'man', s:section_arg, '1', 'man']) - if v:shell_error - let s:section_arg = '-S' - endif -endfunction - -function! s:init() abort - call s:init_section_flag() - " TODO(nhooyr): Does `man -l` on SunOS list searched directories? +function! man#init() abort try - if !has('win32') && $OSTYPE !~? 'cygwin\|linux' && system('uname -s') =~? 'SunOS' && system('uname -r') =~# '^5' - let s:find_arg = '-l' - endif " Check for -l support. call s:get_page(s:get_path('', 'man')) catch /E145:/ @@ -52,51 +40,40 @@ function! man#open_page(count, count1, mods, ...) abort let ref = a:2.'('.a:1.')' endif try - let [sect, name] = man#extract_sect_and_name_ref(ref) + let [sect, name] = s:extract_sect_and_name_ref(ref) if a:count ==# a:count1 " v:count defaults to 0 which is a valid section, and v:count1 defaults to " 1, also a valid section. If they are equal, count explicitly set. let sect = string(a:count) endif - let [sect, name, path] = s:verify_exists(sect, name) + let path = s:verify_exists(sect, name) + let [sect, name] = s:extract_sect_and_name_path(path) catch call s:error(v:exception) return endtry - call s:push_tag() - let bufname = 'man://'.name.(empty(sect)?'':'('.sect.')') - + let [l:buf, l:save_tfu] = [bufnr(), &tagfunc] try - set eventignore+=BufReadCmd + set tagfunc=man#goto_tag + let l:target = l:name . '(' . l:sect . ')' if a:mods !~# 'tab' && s:find_man() - execute 'silent keepalt edit' fnameescape(bufname) + execute 'silent keepalt tag' l:target else - execute 'silent keepalt' a:mods 'split' fnameescape(bufname) + execute 'silent keepalt' a:mods 'stag' l:target endif finally - set eventignore-=BufReadCmd - endtry - - try - let page = s:get_page(path) - catch - if a:mods =~# 'tab' || !s:find_man() - " a new window was opened - close - endif - call s:error(v:exception) - return + call setbufvar(l:buf, '&tagfunc', l:save_tfu) endtry let b:man_sect = sect - call s:put_page(page) endfunction function! man#read_page(ref) abort try - let [sect, name] = man#extract_sect_and_name_ref(a:ref) - let [sect, name, path] = s:verify_exists(sect, name) + let [sect, name] = s:extract_sect_and_name_ref(a:ref) + let path = s:verify_exists(sect, name) + let [sect, name] = s:extract_sect_and_name_path(path) let page = s:get_page(path) catch call s:error(v:exception) @@ -152,7 +129,7 @@ function! s:get_page(path) abort " Disable hard-wrap by using a big $MANWIDTH (max 1000 on some systems #9065). " Soft-wrap: ftplugin/man.vim sets wrap/breakindent/…. " Hard-wrap: driven by `man`. - let manwidth = !get(g:,'man_hardwrap') ? 999 : (empty($MANWIDTH) ? winwidth(0) : $MANWIDTH) + let manwidth = !get(g:, 'man_hardwrap', 1) ? 999 : (empty($MANWIDTH) ? winwidth(0) : $MANWIDTH) " Force MANPAGER=cat to ensure Vim is not recursively invoked (by man-db). " http://comments.gmane.org/gmane.editors.vim.devel/29085 " Set MAN_KEEP_FORMATTING so Debian man doesn't discard backspaces. @@ -163,6 +140,9 @@ endfunction function! s:put_page(page) abort setlocal modifiable setlocal noreadonly + setlocal noswapfile + " git-ls-files(1) is all one keyword/tag-target + setlocal iskeyword+=(,) silent keepjumps %delete _ silent put =a:page while getline(1) =~# '^\s*$' @@ -204,7 +184,7 @@ endfunction " attempt to extract the name and sect out of 'name(sect)' " otherwise just return the largest string of valid characters in ref -function! man#extract_sect_and_name_ref(ref) abort +function! s:extract_sect_and_name_ref(ref) abort if a:ref[0] ==# '-' " try ':Man -pandoc' with this disabled. throw 'manpage name cannot start with ''-''' endif @@ -214,7 +194,7 @@ function! man#extract_sect_and_name_ref(ref) abort if empty(name) throw 'manpage reference cannot contain only parentheses' endif - return [get(b:, 'man_default_sects', ''), name] + return ['', name] endif let left = split(ref, '(') " see ':Man 3X curses' on why tolower. @@ -237,42 +217,62 @@ function! s:get_path(sect, name) abort return substitute(get(split(s:system(['man', s:find_arg, s:section_arg, a:sect, a:name])), 0, ''), '\n\+$', '', '') endfunction +" s:verify_exists attempts to find the path to a manpage +" based on the passed section and name. +" +" 1. If the passed section is empty, b:man_default_sects is used. +" 2. If manpage could not be found with the given sect and name, +" then another attempt is made with b:man_default_sects. +" 3. If it still could not be found, then we try again without a section. +" 4. If still not found but $MANSECT is set, then we try again with $MANSECT +" unset. +" +" This function is careful to avoid duplicating a search if a previous +" step has already done it. i.e if we use b:man_default_sects in step 1, +" then we don't do it again in step 2. function! s:verify_exists(sect, name) abort + let sect = a:sect + if empty(sect) + let sect = get(b:, 'man_default_sects', '') + endif + try - let path = s:get_path(a:sect, a:name) + return s:get_path(sect, a:name) catch /^command error (/ + endtry + + if !empty(get(b:, 'man_default_sects', '')) && sect !=# b:man_default_sects try - let path = s:get_path(get(b:, 'man_default_sects', ''), a:name) + return s:get_path(b:man_default_sects, a:name) catch /^command error (/ - let path = s:get_path('', a:name) endtry - endtry - " Extract the section from the path, because sometimes the actual section is - " more specific than what we provided to `man` (try `:Man 3 App::CLI`). - " Also on linux, name seems to be case-insensitive. So for `:Man PRIntf`, we - " still want the name of the buffer to be 'printf'. - return s:extract_sect_and_name_path(path) + [path] -endfunction - -let s:tag_stack = [] + endif -function! s:push_tag() abort - let s:tag_stack += [{ - \ 'buf': bufnr('%'), - \ 'lnum': line('.'), - \ 'col': col('.'), - \ }] -endfunction + if !empty(sect) + try + return s:get_path('', a:name) + catch /^command error (/ + endtry + endif -function! man#pop_tag() abort - if !empty(s:tag_stack) - let tag = remove(s:tag_stack, -1) - execute 'silent' tag['buf'].'buffer' - call cursor(tag['lnum'], tag['col']) + if !empty($MANSECT) + try + let MANSECT = $MANSECT + unset $MANSECT + return s:get_path('', a:name) + catch /^command error (/ + finally + let $MANSECT = MANSECT + endtry endif + + throw 'no manual entry for ' . a:name endfunction -" extracts the name and sect out of 'path/name.sect' +" Extracts the name/section from the 'path/name.sect', because sometimes the actual section is +" more specific than what we provided to `man` (try `:Man 3 App::CLI`). +" Also on linux, name seems to be case-insensitive. So for `:Man PRIntf`, we +" still want the name of the buffer to be 'printf'. function! s:extract_sect_and_name_path(path) abort let tail = fnamemodify(a:path, ':t') if a:path =~# '\.\%([glx]z\|bz2\|lzma\|Z\)$' " valid extensions @@ -284,20 +284,16 @@ function! s:extract_sect_and_name_path(path) abort endfunction function! s:find_man() abort - if &filetype ==# 'man' - return 1 - elseif winnr('$') ==# 1 - return 0 - endif - let thiswin = winnr() - while 1 - wincmd w - if &filetype ==# 'man' + let l:win = 1 + while l:win <= winnr('$') + let l:buf = winbufnr(l:win) + if getbufvar(l:buf, '&filetype', '') ==# 'man' + execute l:win.'wincmd w' return 1 - elseif thiswin ==# winnr() - return 0 endif + let l:win += 1 endwhile + return 0 endfunction function! s:error(msg) abort @@ -307,7 +303,7 @@ function! s:error(msg) abort echohl None endfunction -" see man#extract_sect_and_name_ref on why tolower(sect) +" see s:extract_sect_and_name_ref on why tolower(sect) function! man#complete(arg_lead, cmd_line, cursor_pos) abort let args = split(a:cmd_line) let cmd_offset = index(args, 'Man') @@ -360,14 +356,35 @@ function! man#complete(arg_lead, cmd_line, cursor_pos) abort return s:complete(sect, sect, name) endfunction -function! s:complete(sect, psect, name) abort +function! s:get_paths(sect, name, do_fallback) abort + " callers must try-catch this, as some `man` implementations don't support `s:find_arg` try let mandirs = join(split(s:system(['man', s:find_arg]), ':\|\n'), ',') + let paths = globpath(mandirs, 'man?/'.a:name.'*.'.a:sect.'*', 0, 1) + try + " Prioritize the result from verify_exists as it obeys b:man_default_sects. + let first = s:verify_exists(a:sect, a:name) + let paths = filter(paths, 'v:val !=# first') + let paths = [first] + paths + catch + endtry + return paths catch - call s:error(v:exception) - return + if !a:do_fallback + throw v:exception + endif + + " Fallback to a single path, with the page we're trying to find. + try + return [s:verify_exists(a:sect, a:name)] + catch + return [] + endtry endtry - let pages = globpath(mandirs,'man?/'.a:name.'*.'.a:sect.'*', 0, 1) +endfunction + +function! s:complete(sect, psect, name) abort + let pages = s:get_paths(a:sect, a:name, v:false) " We remove duplicates in case the same manpage in different languages was found. return uniq(sort(map(pages, 's:format_candidate(v:val, a:psect)'), 'i')) endfunction @@ -387,6 +404,10 @@ function! s:format_candidate(path, psect) abort endfunction function! man#init_pager() abort + " https://github.com/neovim/neovim/issues/6828 + let og_modifiable = &modifiable + setlocal modifiable + if getline(1) =~# '^\s*$' silent keepjumps 1delete _ else @@ -397,13 +418,40 @@ function! man#init_pager() abort " know the correct casing, cf. `man glDrawArraysInstanced`). let ref = substitute(matchstr(getline(1), '^[^)]\+)'), ' ', '_', 'g') try - let b:man_sect = man#extract_sect_and_name_ref(ref)[0] + let b:man_sect = s:extract_sect_and_name_ref(ref)[0] catch let b:man_sect = '' endtry if -1 == match(bufname('%'), 'man:\/\/') " Avoid duplicate buffers, E95. execute 'silent file man://'.tolower(fnameescape(ref)) endif + + let &l:modifiable = og_modifiable +endfunction + +function! man#goto_tag(pattern, flags, info) abort + let [l:sect, l:name] = s:extract_sect_and_name_ref(a:pattern) + + let l:paths = s:get_paths(l:sect, l:name, v:true) + let l:structured = [] + + for l:path in l:paths + let l:n = s:extract_sect_and_name_path(l:path)[1] + let l:structured += [{ 'name': l:n, 'path': l:path }] + endfor + + if &cscopetag + " return only a single entry so we work well with :cstag (#11675) + let l:structured = l:structured[:0] + endif + + return map(l:structured, { + \ _, entry -> { + \ 'name': entry.name, + \ 'filename': 'man://' . entry.path, + \ 'cmd': '1' + \ } + \ }) endfunction -call s:init() +call man#init() |