aboutsummaryrefslogtreecommitdiff
path: root/runtime/autoload/man.vim
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/autoload/man.vim')
-rw-r--r--runtime/autoload/man.vim216
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()