man.vim 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. " Maintainer: Anmol Sethi <anmol@aubble.com>
  2. if exists('s:loaded_man')
  3. finish
  4. endif
  5. let s:loaded_man = 1
  6. let s:find_arg = '-w'
  7. let s:localfile_arg = v:true " Always use -l if possible. #6683
  8. let s:section_arg = '-s'
  9. function! s:init_section_flag()
  10. call system(['env', 'MANPAGER=cat', 'man', s:section_arg, '1', 'man'])
  11. if v:shell_error
  12. let s:section_arg = '-S'
  13. endif
  14. endfunction
  15. function! s:init() abort
  16. call s:init_section_flag()
  17. " TODO(nhooyr): Does `man -l` on SunOS list searched directories?
  18. try
  19. if !has('win32') && $OSTYPE !~? 'cygwin\|linux' && system('uname -s') =~? 'SunOS' && system('uname -r') =~# '^5'
  20. let s:find_arg = '-l'
  21. endif
  22. " Check for -l support.
  23. call s:get_page(s:get_path('', 'man'))
  24. catch /E145:/
  25. " Ignore the error in restricted mode
  26. catch /command error .*/
  27. let s:localfile_arg = v:false
  28. endtry
  29. endfunction
  30. function! man#open_page(count, count1, mods, ...) abort
  31. if a:0 > 2
  32. call s:error('too many arguments')
  33. return
  34. elseif a:0 == 0
  35. let ref = &filetype ==# 'man' ? expand('<cWORD>') : expand('<cword>')
  36. if empty(ref)
  37. call s:error('no identifier under cursor')
  38. return
  39. endif
  40. elseif a:0 ==# 1
  41. let ref = a:1
  42. else
  43. " Combine the name and sect into a manpage reference so that all
  44. " verification/extraction can be kept in a single function.
  45. " If a:2 is a reference as well, that is fine because it is the only
  46. " reference that will match.
  47. let ref = a:2.'('.a:1.')'
  48. endif
  49. try
  50. let [sect, name] = man#extract_sect_and_name_ref(ref)
  51. if a:count ==# a:count1
  52. " v:count defaults to 0 which is a valid section, and v:count1 defaults to
  53. " 1, also a valid section. If they are equal, count explicitly set.
  54. let sect = string(a:count)
  55. endif
  56. let [sect, name, path] = s:verify_exists(sect, name)
  57. catch
  58. call s:error(v:exception)
  59. return
  60. endtry
  61. call s:push_tag()
  62. let bufname = 'man://'.name.(empty(sect)?'':'('.sect.')')
  63. try
  64. set eventignore+=BufReadCmd
  65. if a:mods !~# 'tab' && s:find_man()
  66. execute 'silent keepalt edit' fnameescape(bufname)
  67. else
  68. execute 'silent keepalt' a:mods 'split' fnameescape(bufname)
  69. endif
  70. finally
  71. set eventignore-=BufReadCmd
  72. endtry
  73. try
  74. let page = s:get_page(path)
  75. catch
  76. if a:mods =~# 'tab' || !s:find_man()
  77. " a new window was opened
  78. close
  79. endif
  80. call s:error(v:exception)
  81. return
  82. endtry
  83. let b:man_sect = sect
  84. call s:put_page(page)
  85. endfunction
  86. function! man#read_page(ref) abort
  87. try
  88. let [sect, name] = man#extract_sect_and_name_ref(a:ref)
  89. let [sect, name, path] = s:verify_exists(sect, name)
  90. let page = s:get_page(path)
  91. catch
  92. call s:error(v:exception)
  93. return
  94. endtry
  95. let b:man_sect = sect
  96. call s:put_page(page)
  97. endfunction
  98. " Handler for s:system() function.
  99. function! s:system_handler(jobid, data, event) dict abort
  100. if a:event is# 'stdout' || a:event is# 'stderr'
  101. let self[a:event] .= join(a:data, "\n")
  102. else
  103. let self.exit_code = a:data
  104. endif
  105. endfunction
  106. " Run a system command and timeout after 30 seconds.
  107. function! s:system(cmd, ...) abort
  108. let opts = {
  109. \ 'stdout': '',
  110. \ 'stderr': '',
  111. \ 'exit_code': 0,
  112. \ 'on_stdout': function('s:system_handler'),
  113. \ 'on_stderr': function('s:system_handler'),
  114. \ 'on_exit': function('s:system_handler'),
  115. \ }
  116. let jobid = jobstart(a:cmd, opts)
  117. if jobid < 1
  118. throw printf('command error %d: %s', jobid, join(a:cmd))
  119. endif
  120. let res = jobwait([jobid], 30000)
  121. if res[0] == -1
  122. try
  123. call jobstop(jobid)
  124. throw printf('command timed out: %s', join(a:cmd))
  125. catch /^Vim(call):E900:/
  126. endtry
  127. elseif res[0] == -2
  128. throw printf('command interrupted: %s', join(a:cmd))
  129. endif
  130. if opts.exit_code != 0
  131. throw printf("command error (%d) %s: %s", jobid, join(a:cmd), substitute(opts.stderr, '\_s\+$', '', &gdefault ? '' : 'g'))
  132. endif
  133. return opts.stdout
  134. endfunction
  135. function! s:get_page(path) abort
  136. " Disable hard-wrap by using a big $MANWIDTH (max 1000 on some systems #9065).
  137. " Soft-wrap: ftplugin/man.vim sets wrap/breakindent/….
  138. " Hard-wrap: driven by `man`.
  139. let manwidth = !get(g:,'man_hardwrap') ? 999 : (empty($MANWIDTH) ? winwidth(0) : $MANWIDTH)
  140. " Force MANPAGER=cat to ensure Vim is not recursively invoked (by man-db).
  141. " http://comments.gmane.org/gmane.editors.vim.devel/29085
  142. " Set MAN_KEEP_FORMATTING so Debian man doesn't discard backspaces.
  143. let cmd = ['env', 'MANPAGER=cat', 'MANWIDTH='.manwidth, 'MAN_KEEP_FORMATTING=1', 'man']
  144. return s:system(cmd + (s:localfile_arg ? ['-l', a:path] : [a:path]))
  145. endfunction
  146. function! s:put_page(page) abort
  147. setlocal modifiable
  148. setlocal noreadonly
  149. silent keepjumps %delete _
  150. silent put =a:page
  151. while getline(1) =~# '^\s*$'
  152. silent keepjumps 1delete _
  153. endwhile
  154. " XXX: nroff justifies text by filling it with whitespace. That interacts
  155. " badly with our use of $MANWIDTH=999. Hack around this by using a fixed
  156. " size for those whitespace regions.
  157. silent! keeppatterns keepjumps %s/\s\{199,}/\=repeat(' ', 10)/g
  158. 1
  159. lua require("man").highlight_man_page()
  160. setlocal filetype=man
  161. endfunction
  162. function! man#show_toc() abort
  163. let bufname = bufname('%')
  164. let info = getloclist(0, {'winid': 1})
  165. if !empty(info) && getwinvar(info.winid, 'qf_toc') ==# bufname
  166. lopen
  167. return
  168. endif
  169. let toc = []
  170. let lnum = 2
  171. let last_line = line('$') - 1
  172. while lnum && lnum < last_line
  173. let text = getline(lnum)
  174. if text =~# '^\%( \{3\}\)\=\S.*$'
  175. call add(toc, {'bufnr': bufnr('%'), 'lnum': lnum, 'text': text})
  176. endif
  177. let lnum = nextnonblank(lnum + 1)
  178. endwhile
  179. call setloclist(0, toc, ' ')
  180. call setloclist(0, [], 'a', {'title': 'Man TOC'})
  181. lopen
  182. let w:qf_toc = bufname
  183. endfunction
  184. " attempt to extract the name and sect out of 'name(sect)'
  185. " otherwise just return the largest string of valid characters in ref
  186. function! man#extract_sect_and_name_ref(ref) abort
  187. if a:ref[0] ==# '-' " try ':Man -pandoc' with this disabled.
  188. throw 'manpage name cannot start with ''-'''
  189. endif
  190. let ref = matchstr(a:ref, '[^()]\+([^()]\+)')
  191. if empty(ref)
  192. let name = matchstr(a:ref, '[^()]\+')
  193. if empty(name)
  194. throw 'manpage reference cannot contain only parentheses'
  195. endif
  196. return [get(b:, 'man_default_sects', ''), name]
  197. endif
  198. let left = split(ref, '(')
  199. " see ':Man 3X curses' on why tolower.
  200. " TODO(nhooyr) Not sure if this is portable across OSs
  201. " but I have not seen a single uppercase section.
  202. return [tolower(split(left[1], ')')[0]), left[0]]
  203. endfunction
  204. function! s:get_path(sect, name) abort
  205. " Some man implementations (OpenBSD) return all available paths from the
  206. " search command, so we get() the first one. #8341
  207. if empty(a:sect)
  208. return substitute(get(split(s:system(['man', s:find_arg, a:name])), 0, ''), '\n\+$', '', '')
  209. endif
  210. " '-s' flag handles:
  211. " - tokens like 'printf(echo)'
  212. " - sections starting with '-'
  213. " - 3pcap section (found on macOS)
  214. " - commas between sections (for section priority)
  215. return substitute(get(split(s:system(['man', s:find_arg, s:section_arg, a:sect, a:name])), 0, ''), '\n\+$', '', '')
  216. endfunction
  217. function! s:verify_exists(sect, name) abort
  218. try
  219. let path = s:get_path(a:sect, a:name)
  220. catch /^command error (/
  221. try
  222. let path = s:get_path(get(b:, 'man_default_sects', ''), a:name)
  223. catch /^command error (/
  224. let path = s:get_path('', a:name)
  225. endtry
  226. endtry
  227. " Extract the section from the path, because sometimes the actual section is
  228. " more specific than what we provided to `man` (try `:Man 3 App::CLI`).
  229. " Also on linux, name seems to be case-insensitive. So for `:Man PRIntf`, we
  230. " still want the name of the buffer to be 'printf'.
  231. return s:extract_sect_and_name_path(path) + [path]
  232. endfunction
  233. let s:tag_stack = []
  234. function! s:push_tag() abort
  235. let s:tag_stack += [{
  236. \ 'buf': bufnr('%'),
  237. \ 'lnum': line('.'),
  238. \ 'col': col('.'),
  239. \ }]
  240. endfunction
  241. function! man#pop_tag() abort
  242. if !empty(s:tag_stack)
  243. let tag = remove(s:tag_stack, -1)
  244. execute 'silent' tag['buf'].'buffer'
  245. call cursor(tag['lnum'], tag['col'])
  246. endif
  247. endfunction
  248. " extracts the name and sect out of 'path/name.sect'
  249. function! s:extract_sect_and_name_path(path) abort
  250. let tail = fnamemodify(a:path, ':t')
  251. if a:path =~# '\.\%([glx]z\|bz2\|lzma\|Z\)$' " valid extensions
  252. let tail = fnamemodify(tail, ':r')
  253. endif
  254. let sect = matchstr(tail, '\.\zs[^.]\+$')
  255. let name = matchstr(tail, '^.\+\ze\.')
  256. return [sect, name]
  257. endfunction
  258. function! s:find_man() abort
  259. if &filetype ==# 'man'
  260. return 1
  261. elseif winnr('$') ==# 1
  262. return 0
  263. endif
  264. let thiswin = winnr()
  265. while 1
  266. wincmd w
  267. if &filetype ==# 'man'
  268. return 1
  269. elseif thiswin ==# winnr()
  270. return 0
  271. endif
  272. endwhile
  273. endfunction
  274. function! s:error(msg) abort
  275. redraw
  276. echohl ErrorMsg
  277. echon 'man.vim: ' a:msg
  278. echohl None
  279. endfunction
  280. " see man#extract_sect_and_name_ref on why tolower(sect)
  281. function! man#complete(arg_lead, cmd_line, cursor_pos) abort
  282. let args = split(a:cmd_line)
  283. let cmd_offset = index(args, 'Man')
  284. if cmd_offset > 0
  285. " Prune all arguments up to :Man itself. Otherwise modifier commands like
  286. " :tab, :vertical, etc. would lead to a wrong length.
  287. let args = args[cmd_offset:]
  288. endif
  289. let l = len(args)
  290. if l > 3
  291. return
  292. elseif l ==# 1
  293. let name = ''
  294. let sect = ''
  295. elseif a:arg_lead =~# '^[^()]\+([^()]*$'
  296. " cursor (|) is at ':Man printf(|' or ':Man 1 printf(|'
  297. " The later is is allowed because of ':Man pri<TAB>'.
  298. " It will offer 'priclass.d(1m)' even though section is specified as 1.
  299. let tmp = split(a:arg_lead, '(')
  300. let name = tmp[0]
  301. let sect = tolower(get(tmp, 1, ''))
  302. return s:complete(sect, '', name)
  303. elseif args[1] !~# '^[^()]\+$'
  304. " cursor (|) is at ':Man 3() |' or ':Man (3|' or ':Man 3() pri|'
  305. " or ':Man 3() pri |'
  306. return
  307. elseif l ==# 2
  308. if empty(a:arg_lead)
  309. " cursor (|) is at ':Man 1 |'
  310. let name = ''
  311. let sect = tolower(args[1])
  312. else
  313. " cursor (|) is at ':Man pri|'
  314. if a:arg_lead =~# '\/'
  315. " if the name is a path, complete files
  316. " TODO(nhooyr) why does this complete the last one automatically
  317. return glob(a:arg_lead.'*', 0, 1)
  318. endif
  319. let name = a:arg_lead
  320. let sect = ''
  321. endif
  322. elseif a:arg_lead !~# '^[^()]\+$'
  323. " cursor (|) is at ':Man 3 printf |' or ':Man 3 (pr)i|'
  324. return
  325. else
  326. " cursor (|) is at ':Man 3 pri|'
  327. let name = a:arg_lead
  328. let sect = tolower(args[1])
  329. endif
  330. return s:complete(sect, sect, name)
  331. endfunction
  332. function! s:complete(sect, psect, name) abort
  333. try
  334. let mandirs = join(split(s:system(['man', s:find_arg]), ':\|\n'), ',')
  335. catch
  336. call s:error(v:exception)
  337. return
  338. endtry
  339. let pages = globpath(mandirs,'man?/'.a:name.'*.'.a:sect.'*', 0, 1)
  340. " We remove duplicates in case the same manpage in different languages was found.
  341. return uniq(sort(map(pages, 's:format_candidate(v:val, a:psect)'), 'i'))
  342. endfunction
  343. function! s:format_candidate(path, psect) abort
  344. if a:path =~# '\.\%(pdf\|in\)$' " invalid extensions
  345. return
  346. endif
  347. let [sect, name] = s:extract_sect_and_name_path(a:path)
  348. if sect ==# a:psect
  349. return name
  350. elseif sect =~# a:psect.'.\+$'
  351. " We include the section if the user provided section is a prefix
  352. " of the actual section.
  353. return name.'('.sect.')'
  354. endif
  355. endfunction
  356. function! man#init_pager() abort
  357. if getline(1) =~# '^\s*$'
  358. silent keepjumps 1delete _
  359. else
  360. keepjumps 1
  361. endif
  362. lua require("man").highlight_man_page()
  363. " Guess the ref from the heading (which is usually uppercase, so we cannot
  364. " know the correct casing, cf. `man glDrawArraysInstanced`).
  365. let ref = substitute(matchstr(getline(1), '^[^)]\+)'), ' ', '_', 'g')
  366. try
  367. let b:man_sect = man#extract_sect_and_name_ref(ref)[0]
  368. catch
  369. let b:man_sect = ''
  370. endtry
  371. if -1 == match(bufname('%'), 'man:\/\/') " Avoid duplicate buffers, E95.
  372. execute 'silent file man://'.tolower(fnameescape(ref))
  373. endif
  374. endfunction
  375. call s:init()