man_spec.lua 9.3 KB

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