man_spec.lua 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. local t = require('test.testutil')
  2. local n = require('test.functional.testnvim')()
  3. local Screen = require('test.functional.ui.screen')
  4. local command, feed = n.command, n.feed
  5. local dedent = t.dedent
  6. local clear = n.clear
  7. local exec_lua = n.exec_lua
  8. local fn = n.fn
  9. local nvim_prog = n.nvim_prog
  10. local matches = t.matches
  11. local tmpname = t.tmpname
  12. local eq = t.eq
  13. local pesc = vim.pesc
  14. local skip = t.skip
  15. local is_ci = t.is_ci
  16. -- Collects all names passed to find_path() after attempting ":Man foo".
  17. local function get_search_history(name)
  18. return exec_lua(function()
  19. local args = vim.split(name, ' ')
  20. local man = require('man')
  21. local res = {}
  22. --- @diagnostic disable-next-line:duplicate-set-field
  23. man._find_path = function(name0, sect)
  24. table.insert(res, { sect, name0 })
  25. return nil
  26. end
  27. local err = man.open_page(-1, { tab = 0 }, args)
  28. assert(err and err:match('no manual entry'))
  29. return res
  30. end)
  31. end
  32. clear()
  33. if fn.executable('man') == 0 then
  34. pending('missing "man" command', function() end)
  35. return
  36. end
  37. describe(':Man', function()
  38. before_each(function()
  39. clear()
  40. end)
  41. describe('man.lua: highlight_line()', function()
  42. local screen --- @type test.functional.ui.screen
  43. before_each(function()
  44. command('syntax on')
  45. command('set filetype=man')
  46. command('syntax off') -- Ignore syntax groups
  47. screen = Screen.new(52, 5)
  48. screen:set_default_attr_ids({
  49. b = { bold = true },
  50. i = { italic = true },
  51. u = { underline = true },
  52. bi = { bold = true, italic = true },
  53. biu = { bold = true, italic = true, underline = true },
  54. c = { foreground = Screen.colors.Blue }, -- control chars
  55. eob = { bold = true, foreground = Screen.colors.Blue }, -- empty line '~'s
  56. })
  57. end)
  58. it('clears backspaces from text and adds highlights', function()
  59. feed(
  60. dedent(
  61. [[
  62. ithis i<C-v><C-h>is<C-v><C-h>s a<C-v><C-h>a test
  63. with _<C-v><C-h>o_<C-v><C-h>v_<C-v><C-h>e_<C-v><C-h>r_<C-v><C-h>s_<C-v><C-h>t_<C-v><C-h>r_<C-v><C-h>u_<C-v><C-h>c_<C-v><C-h>k text<ESC>]]
  64. )
  65. )
  66. screen:expect {
  67. grid = [[
  68. this i{c:^H}is{c:^H}s a{c:^H}a test |
  69. with _{c:^H}o_{c:^H}v_{c:^H}e_{c:^H}r_{c:^H}s_{c:^H}t_{c:^H}r_{c:^H}u_{c:^H}c_{c:^H}k tex^t |
  70. {eob:~ }|*2
  71. |
  72. ]],
  73. }
  74. exec_lua [[require'man'.init_pager()]]
  75. screen:expect([[
  76. ^this {b:is} {b:a} test |
  77. with {i:overstruck} text |
  78. {eob:~ }|*2
  79. |
  80. ]])
  81. end)
  82. it('clears escape sequences from text and adds highlights', function()
  83. feed(
  84. dedent(
  85. [[
  86. ithis <C-v><ESC>[1mis <C-v><ESC>[3ma <C-v><ESC>[4mtest<C-v><ESC>[0m
  87. <C-v><ESC>[4mwith<C-v><ESC>[24m <C-v><ESC>[4mescaped<C-v><ESC>[24m <C-v><ESC>[4mtext<C-v><ESC>[24m<ESC>]]
  88. )
  89. )
  90. screen:expect {
  91. grid = [=[
  92. this {c:^[}[1mis {c:^[}[3ma {c:^[}[4mtest{c:^[}[0m |
  93. {c:^[}[4mwith{c:^[}[24m {c:^[}[4mescaped{c:^[}[24m {c:^[}[4mtext{c:^[}[24^m |
  94. {eob:~ }|*2
  95. |
  96. ]=],
  97. }
  98. exec_lua [[require'man'.init_pager()]]
  99. screen:expect([[
  100. ^this {b:is }{bi:a }{biu:test} |
  101. {u:with} {u:escaped} {u:text} |
  102. {eob:~ }|*2
  103. |
  104. ]])
  105. end)
  106. it('clears OSC 8 hyperlink markup from text', function()
  107. feed(
  108. dedent(
  109. [[
  110. ithis <C-v><ESC>]8;;http://example.com<C-v><ESC>\Link Title<C-v><ESC>]8;;<C-v><ESC>\<ESC>]]
  111. )
  112. )
  113. screen:expect {
  114. grid = [=[
  115. this {c:^[}]8;;http://example.com{c:^[}\Link Title{c:^[}]8;;{c:^[}^\ |
  116. {eob:~ }|*3
  117. |
  118. ]=],
  119. }
  120. exec_lua [[require'man'.init_pager()]]
  121. screen:expect([[
  122. ^this Link Title |
  123. {eob:~ }|*3
  124. |
  125. ]])
  126. end)
  127. it('highlights multibyte text', function()
  128. feed(
  129. dedent(
  130. [[
  131. ithis i<C-v><C-h>is<C-v><C-h>s あ<C-v><C-h>あ test
  132. with _<C-v><C-h>ö_<C-v><C-h>v_<C-v><C-h>e_<C-v><C-h>r_<C-v><C-h>s_<C-v><C-h>t_<C-v><C-h>r_<C-v><C-h>u_<C-v><C-h>̃_<C-v><C-h>c_<C-v><C-h>k te<C-v><ESC>[3mxt¶<C-v><ESC>[0m<ESC>]]
  133. )
  134. )
  135. exec_lua [[require'man'.init_pager()]]
  136. screen:expect([[
  137. ^this {b:is} {b:あ} test |
  138. with {i:överstrũck} te{i:xt¶} |
  139. {eob:~ }|*2
  140. |
  141. ]])
  142. end)
  143. it('highlights underscores based on context', function()
  144. feed(
  145. dedent(
  146. [[
  147. i_<C-v><C-h>_b<C-v><C-h>be<C-v><C-h>eg<C-v><C-h>gi<C-v><C-h>in<C-v><C-h>ns<C-v><C-h>s
  148. m<C-v><C-h>mi<C-v><C-h>id<C-v><C-h>d_<C-v><C-h>_d<C-v><C-h>dl<C-v><C-h>le<C-v><C-h>e
  149. _<C-v><C-h>m_<C-v><C-h>i_<C-v><C-h>d_<C-v><C-h>__<C-v><C-h>d_<C-v><C-h>l_<C-v><C-h>e<ESC>]]
  150. )
  151. )
  152. exec_lua [[require'man'.init_pager()]]
  153. screen:expect([[
  154. {b:^_begins} |
  155. {b:mid_dle} |
  156. {i:mid_dle} |
  157. {eob:~ }|
  158. |
  159. ]])
  160. end)
  161. it('highlights various bullet formats', function()
  162. feed(dedent([[
  163. i· ·<C-v><C-h>·
  164. +<C-v><C-h>o
  165. +<C-v><C-h>+<C-v><C-h>o<C-v><C-h>o double<ESC>]]))
  166. exec_lua [[require'man'.init_pager()]]
  167. screen:expect([[
  168. ^· {b:·} |
  169. {b:·} |
  170. {b:·} double |
  171. {eob:~ }|
  172. |
  173. ]])
  174. end)
  175. it('handles : characters in input', function()
  176. feed(dedent([[
  177. i<C-v><C-[>[40m 0 <C-v><C-[>[41m 1 <C-v><C-[>[42m 2 <C-v><C-[>[43m 3
  178. <C-v><C-[>[44m 4 <C-v><C-[>[45m 5 <C-v><C-[>[46m 6 <C-v><C-[>[47m 7 <C-v><C-[>[100m 8 <C-v><C-[>[101m 9
  179. <C-v><C-[>[102m 10 <C-v><C-[>[103m 11 <C-v><C-[>[104m 12 <C-v><C-[>[105m 13 <C-v><C-[>[106m 14 <C-v><C-[>[107m 15
  180. <C-v><C-[>[48:5:16m 16 <ESC>]]))
  181. exec_lua [[require'man'.init_pager()]]
  182. screen:expect([[
  183. ^ 0 1 2 3 |
  184. 4 5 6 7 8 9 |
  185. 10 11 12 13 14 15 |
  186. 16 |
  187. |
  188. ]])
  189. end)
  190. end)
  191. it('q quits in "$MANPAGER mode" (:Man!) #18281', function()
  192. -- This will hang if #18281 regresses.
  193. local args = {
  194. nvim_prog,
  195. '--headless',
  196. '+autocmd VimLeave * echo "quit works!!"',
  197. '+Man!',
  198. '+tag ls',
  199. '+call nvim_input("q")',
  200. }
  201. matches('quit works!!', fn.system(args, { 'manpage contents' }))
  202. end)
  203. it('raw manpage into (:Man!) creates a new buffer #30132', function()
  204. local args = {
  205. nvim_prog,
  206. '--headless',
  207. '+Man! foo',
  208. '+echo bufname()',
  209. '+enew',
  210. '+Man! foo',
  211. '+echo bufname()',
  212. '+enew',
  213. '+Man! foo',
  214. '+echo bufname()',
  215. '+q',
  216. }
  217. local out = fn.system(args, { 'manpage contents' })
  218. assert(out and out:match('man://%?new=%d'))
  219. end)
  220. it('reports non-existent man pages for absolute paths', function()
  221. skip(is_ci('cirrus'))
  222. local actual_file = tmpname()
  223. -- actual_file must be an absolute path to an existent file for us to test against it
  224. matches('^/.+', actual_file)
  225. local args = { nvim_prog, '--headless', '+:Man ' .. actual_file, '+q' }
  226. matches(
  227. ('Error in command line:\r\n' .. 'man.lua: no manual entry for %s'):format(pesc(actual_file)),
  228. fn.system(args, { '' })
  229. )
  230. os.remove(actual_file)
  231. end)
  232. it('tries variants with spaces, underscores #22503', function()
  233. eq({
  234. { vim.NIL, 'NAME WITH SPACES' },
  235. { vim.NIL, 'NAME_WITH_SPACES' },
  236. }, get_search_history('NAME WITH SPACES'))
  237. eq({
  238. { '3', 'some other man' },
  239. { '3', 'some_other_man' },
  240. }, get_search_history('3 some other man'))
  241. eq({
  242. { '3x', 'some other man' },
  243. { '3x', 'some_other_man' },
  244. }, get_search_history('3X some other man'))
  245. eq({
  246. { '3tcl', 'some other man' },
  247. { '3tcl', 'some_other_man' },
  248. }, get_search_history('3tcl some other man'))
  249. eq({
  250. { 'n', 'some other man' },
  251. { 'n', 'some_other_man' },
  252. }, get_search_history('n some other man'))
  253. eq({
  254. { vim.NIL, '123some other man' },
  255. { vim.NIL, '123some_other_man' },
  256. }, get_search_history('123some other man'))
  257. eq({
  258. { '1', 'other_man' },
  259. { '1', 'other_man' },
  260. }, get_search_history('other_man(1)'))
  261. end)
  262. it('can complete', function()
  263. eq(
  264. true,
  265. exec_lua(function()
  266. return #require('man').man_complete('f', 'Man f') > 0
  267. end)
  268. )
  269. end)
  270. end)