hlstate_spec.lua 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. local t = require('test.testutil')
  2. local n = require('test.functional.testnvim')()
  3. local Screen = require('test.functional.ui.screen')
  4. local tt = require('test.functional.testterm')
  5. local clear, insert = n.clear, n.insert
  6. local command = n.command
  7. local api = n.api
  8. local testprg = n.testprg
  9. local skip = t.skip
  10. local is_os = t.is_os
  11. describe('ext_hlstate detailed highlights', function()
  12. local screen
  13. before_each(function()
  14. clear()
  15. command('syntax on')
  16. command('hi VertSplit gui=reverse')
  17. screen = Screen.new(40, 8, { ext_hlstate = true })
  18. end)
  19. it('work with combined UI and syntax highlights', function()
  20. insert([[
  21. these are some lines
  22. with colorful text]])
  23. api.nvim_buf_add_highlight(0, -1, 'String', 0, 10, 14)
  24. api.nvim_buf_add_highlight(0, -1, 'Statement', 1, 5, -1)
  25. command('/th co')
  26. screen:expect {
  27. grid = [[
  28. these are {1:some} lines |
  29. ^wi{2:th }{4:co}{3:lorful text} |
  30. {5:~ }|*5
  31. {8:search hit BOTTOM, continuing at TOP}{6: }|
  32. ]],
  33. attr_ids = {
  34. [1] = {
  35. { foreground = Screen.colors.Magenta1 },
  36. { { kind = 'syntax', hi_name = 'Constant' } },
  37. },
  38. [2] = {
  39. { background = Screen.colors.Yellow1 },
  40. { { kind = 'ui', ui_name = 'Search', hi_name = 'Search' } },
  41. },
  42. [3] = {
  43. { foreground = Screen.colors.Brown, bold = true },
  44. { { kind = 'syntax', hi_name = 'Statement' } },
  45. },
  46. [4] = {
  47. { background = Screen.colors.Yellow1, bold = true, foreground = Screen.colors.Brown },
  48. { 3, 2 },
  49. },
  50. [5] = {
  51. { foreground = Screen.colors.Blue, bold = true },
  52. { { kind = 'ui', ui_name = 'EndOfBuffer', hi_name = 'NonText' } },
  53. },
  54. [6] = { {}, { { kind = 'ui', ui_name = 'MsgArea', hi_name = 'MsgArea' } } },
  55. [7] = {
  56. { foreground = Screen.colors.Red1 },
  57. { { kind = 'syntax', hi_name = 'WarningMsg' } },
  58. },
  59. [8] = { { foreground = Screen.colors.Red1 }, { 6, 7 } },
  60. },
  61. }
  62. end)
  63. it('work with cleared UI highlights', function()
  64. screen:set_default_attr_ids({
  65. [1] = { {}, { { hi_name = 'Normal', ui_name = 'WinSeparator', kind = 'ui' } } },
  66. [2] = {
  67. { bold = true, foreground = Screen.colors.Blue1 },
  68. { { hi_name = 'NonText', ui_name = 'EndOfBuffer', kind = 'ui' } },
  69. },
  70. [3] = {
  71. { bold = true, reverse = true },
  72. { { hi_name = 'StatusLine', ui_name = 'StatusLine', kind = 'ui' } },
  73. },
  74. [4] = {
  75. { reverse = true },
  76. { { hi_name = 'StatusLineNC', ui_name = 'StatusLineNC', kind = 'ui' } },
  77. },
  78. [5] = { {}, { { hi_name = 'StatusLine', ui_name = 'StatusLine', kind = 'ui' } } },
  79. [6] = { {}, { { hi_name = 'StatusLineNC', ui_name = 'StatusLineNC', kind = 'ui' } } },
  80. [7] = { {}, { { hi_name = 'MsgArea', ui_name = 'MsgArea', kind = 'ui' } } },
  81. })
  82. command('hi clear WinSeparator')
  83. command('vsplit')
  84. screen:expect([[
  85. ^ {1:│} |
  86. {2:~ }{1:│}{2:~ }|*5
  87. {3:[No Name] }{4:[No Name] }|
  88. {7: }|
  89. ]])
  90. command('hi clear StatusLine | hi clear StatuslineNC')
  91. screen:expect([[
  92. ^ {1:│} |
  93. {2:~ }{1:│}{2:~ }|*5
  94. {5:[No Name] }{6:[No Name] }|
  95. {7: }|
  96. ]])
  97. -- redrawing is done even if visible highlights didn't change
  98. command('wincmd w')
  99. screen:expect([[
  100. {1:│}^ |
  101. {2:~ }{1:│}{2:~ }|*5
  102. {6:[No Name] }{5:[No Name] }|
  103. {7: }|
  104. ]])
  105. end)
  106. it('work with window-local highlights', function()
  107. screen:set_default_attr_ids({
  108. [1] = {
  109. { foreground = Screen.colors.Brown },
  110. { { hi_name = 'LineNr', ui_name = 'LineNr', kind = 'ui' } },
  111. },
  112. [2] = {
  113. { bold = true, foreground = Screen.colors.Blue1 },
  114. { { hi_name = 'NonText', ui_name = 'EndOfBuffer', kind = 'ui' } },
  115. },
  116. [3] = {
  117. { bold = true, reverse = true },
  118. { { hi_name = 'StatusLine', ui_name = 'StatusLine', kind = 'ui' } },
  119. },
  120. [4] = {
  121. { reverse = true },
  122. { { hi_name = 'StatusLineNC', ui_name = 'StatusLineNC', kind = 'ui' } },
  123. },
  124. [5] = {
  125. { background = Screen.colors.Red, foreground = Screen.colors.Grey100 },
  126. { { hi_name = 'ErrorMsg', ui_name = 'LineNr', kind = 'ui' } },
  127. },
  128. [6] = {
  129. { bold = true, reverse = true },
  130. { { hi_name = 'Normal', ui_name = 'Normal', kind = 'ui' } },
  131. },
  132. [7] = { { foreground = Screen.colors.Brown, bold = true, reverse = true }, { 6, 1 } },
  133. [8] = { { foreground = Screen.colors.Blue1, bold = true, reverse = true }, { 6, 14 } },
  134. [9] = {
  135. { bold = true, foreground = Screen.colors.Brown },
  136. { { hi_name = 'NormalNC', ui_name = 'NormalNC', kind = 'ui' } },
  137. },
  138. [10] = { { bold = true, foreground = Screen.colors.Brown }, { 9, 1 } },
  139. [11] = { { bold = true, foreground = Screen.colors.Blue1 }, { 9, 14 } },
  140. [12] = { {}, { { hi_name = 'MsgArea', ui_name = 'MsgArea', kind = 'ui' } } },
  141. [13] = {
  142. { background = Screen.colors.Red1, foreground = Screen.colors.Gray100 },
  143. { { ui_name = 'LineNr', kind = 'ui', hi_name = 'LineNr' } },
  144. },
  145. [14] = {
  146. { bold = true, foreground = Screen.colors.Blue },
  147. { { ui_name = 'EndOfBuffer', kind = 'ui', hi_name = 'EndOfBuffer' } },
  148. },
  149. })
  150. command('set number')
  151. command('split')
  152. -- NormalNC is not applied if not set, to avoid spurious redraws
  153. screen:expect([[
  154. {1: 1 }^ |
  155. {2:~ }|*2
  156. {3:[No Name] }|
  157. {1: 1 } |
  158. {2:~ }|
  159. {4:[No Name] }|
  160. {12: }|
  161. ]])
  162. command('set winhl=LineNr:ErrorMsg')
  163. screen:expect {
  164. grid = [[
  165. {13: 1 }^ |
  166. {14:~ }|*2
  167. {3:[No Name] }|
  168. {1: 1 } |
  169. {2:~ }|
  170. {4:[No Name] }|
  171. {12: }|
  172. ]],
  173. }
  174. command('set winhl=Normal:MsgSeparator,NormalNC:Statement')
  175. screen:expect([[
  176. {7: 1 }{6:^ }|
  177. {8:~ }|*2
  178. {3:[No Name] }|
  179. {1: 1 } |
  180. {2:~ }|
  181. {4:[No Name] }|
  182. {12: }|
  183. ]])
  184. command('wincmd w')
  185. screen:expect([[
  186. {10: 1 }{9: }|
  187. {11:~ }|*2
  188. {4:[No Name] }|
  189. {1: 1 }^ |
  190. {2:~ }|
  191. {3:[No Name] }|
  192. {12: }|
  193. ]])
  194. end)
  195. it('work with :terminal', function()
  196. skip(is_os('win'))
  197. screen:set_default_attr_ids({
  198. [1] = { {}, { { hi_name = 'TermCursorNC', ui_name = 'TermCursorNC', kind = 'ui' } } },
  199. [2] = { { foreground = tonumber('0x00ccff'), fg_indexed = true }, { { kind = 'term' } } },
  200. [3] = {
  201. { bold = true, foreground = tonumber('0x00ccff'), fg_indexed = true },
  202. {
  203. { kind = 'term' },
  204. },
  205. },
  206. [4] = { { foreground = tonumber('0x00ccff'), fg_indexed = true }, { 2, 1 } },
  207. [5] = { { foreground = tonumber('0x40ffff'), fg_indexed = true }, { { kind = 'term' } } },
  208. [6] = { { foreground = tonumber('0x40ffff'), fg_indexed = true }, { 5, 1 } },
  209. [7] = { {}, { { hi_name = 'MsgArea', ui_name = 'MsgArea', kind = 'ui' } } },
  210. })
  211. command(("enew | call jobstart(['%s'],{'term':v:true})"):format(testprg('tty-test')))
  212. screen:expect([[
  213. ^tty ready |
  214. |
  215. |*5
  216. {7: }|
  217. ]])
  218. tt.feed_data('x ')
  219. tt.set_fg(45)
  220. tt.feed_data('y ')
  221. tt.set_bold()
  222. tt.feed_data('z\n')
  223. -- TODO(bfredl): check if this distinction makes sense
  224. if is_os('win') then
  225. screen:expect([[
  226. ^tty ready |
  227. x {5:y z} |
  228. |
  229. |*4
  230. {7: }|
  231. ]])
  232. else
  233. screen:expect([[
  234. ^tty ready |
  235. x {2:y }{3:z} |
  236. |
  237. |*4
  238. {7: }|
  239. ]])
  240. end
  241. tt.feed_termcode('[A')
  242. tt.feed_termcode('[2C')
  243. if is_os('win') then
  244. screen:expect([[
  245. ^tty ready |
  246. x {6:y}{5: z} |
  247. |*5
  248. {7: }|
  249. ]])
  250. else
  251. screen:expect([[
  252. ^tty ready |
  253. x {2:y }{3:z} |
  254. |*5
  255. {7: }|
  256. ]])
  257. end
  258. end)
  259. it('can use independent cterm and rgb colors', function()
  260. -- tell test module to save all attributes (doesn't change nvim options)
  261. screen:set_rgb_cterm(true)
  262. screen:set_default_attr_ids({
  263. [1] = {
  264. { bold = true, foreground = Screen.colors.Blue1 },
  265. { foreground = 12 },
  266. { { hi_name = 'NonText', ui_name = 'EndOfBuffer', kind = 'ui' } },
  267. },
  268. [2] = {
  269. { reverse = true, foreground = Screen.colors.Red },
  270. { foreground = 10, italic = true },
  271. { { hi_name = 'NonText', ui_name = 'EndOfBuffer', kind = 'ui' } },
  272. },
  273. [3] = { {}, {}, { { hi_name = 'MsgArea', ui_name = 'MsgArea', kind = 'ui' } } },
  274. })
  275. screen:expect([[
  276. ^ |
  277. {1:~ }|*6
  278. {3: }|
  279. ]])
  280. command('hi NonText guifg=Red gui=reverse ctermfg=Green cterm=italic')
  281. screen:expect([[
  282. ^ |
  283. {2:~ }|*6
  284. {3: }|
  285. ]])
  286. end)
  287. it('combines deleted extmark highlights', function()
  288. insert([[
  289. line1
  290. line2
  291. line3
  292. line4
  293. line5
  294. line6]])
  295. screen:expect {
  296. grid = [[
  297. line1 |
  298. line2 |
  299. line3 |
  300. line4 |
  301. line5 |
  302. line^6 |
  303. {1:~ }|
  304. {2: }|
  305. ]],
  306. attr_ids = {
  307. [1] = {
  308. { foreground = Screen.colors.Blue, bold = true },
  309. { { ui_name = 'EndOfBuffer', hi_name = 'NonText', kind = 'ui' } },
  310. },
  311. [2] = { {}, { { ui_name = 'MsgArea', hi_name = 'MsgArea', kind = 'ui' } } },
  312. },
  313. }
  314. local ns = api.nvim_create_namespace('test')
  315. local add_indicator = function(line, col)
  316. api.nvim_buf_set_extmark(0, ns, line, col, {
  317. hl_mode = 'combine',
  318. priority = 2,
  319. right_gravity = false,
  320. virt_text = { { '|', 'Delimiter' } },
  321. virt_text_win_col = 0,
  322. virt_text_pos = 'overlay',
  323. })
  324. end
  325. add_indicator(1, 0)
  326. add_indicator(2, 0)
  327. add_indicator(3, 0)
  328. add_indicator(4, 0)
  329. screen:expect {
  330. grid = [[
  331. line1 |
  332. {1:|} line2 |
  333. {1:|} line3 |
  334. {1:|} line4 |
  335. {1:|} line5 |
  336. line^6 |
  337. {2:~ }|
  338. {3: }|
  339. ]],
  340. attr_ids = {
  341. [1] = {
  342. { foreground = Screen.colors.SlateBlue },
  343. { { hi_name = 'Special', kind = 'syntax' } },
  344. },
  345. [2] = {
  346. { bold = true, foreground = Screen.colors.Blue },
  347. { { ui_name = 'EndOfBuffer', kind = 'ui', hi_name = 'NonText' } },
  348. },
  349. [3] = { {}, { { ui_name = 'MsgArea', kind = 'ui', hi_name = 'MsgArea' } } },
  350. },
  351. }
  352. n.feed('3ggV2jd')
  353. --screen:redraw_debug()
  354. screen:expect {
  355. grid = [[
  356. line1 |
  357. {1:|} line2 |
  358. {2:^|}ine6 |
  359. {3:~ }|*4
  360. {4:3 fewer lines }|
  361. ]],
  362. attr_ids = {
  363. [1] = {
  364. { foreground = Screen.colors.SlateBlue },
  365. { { kind = 'syntax', hi_name = 'Special' } },
  366. },
  367. [2] = { { foreground = Screen.colors.SlateBlue }, { 1, 1, 1 } },
  368. [3] = {
  369. { bold = true, foreground = Screen.colors.Blue },
  370. { { kind = 'ui', ui_name = 'EndOfBuffer', hi_name = 'NonText' } },
  371. },
  372. [4] = { {}, { { kind = 'ui', ui_name = 'MsgArea', hi_name = 'MsgArea' } } },
  373. },
  374. }
  375. end)
  376. it('removes deleted extmark highlights with invalidate', function()
  377. insert([[
  378. line1
  379. line2
  380. line3
  381. line4
  382. line5
  383. line6]])
  384. screen:expect {
  385. grid = [[
  386. line1 |
  387. line2 |
  388. line3 |
  389. line4 |
  390. line5 |
  391. line^6 |
  392. {1:~ }|
  393. {2: }|
  394. ]],
  395. attr_ids = {
  396. [1] = {
  397. { foreground = Screen.colors.Blue, bold = true },
  398. { { ui_name = 'EndOfBuffer', hi_name = 'NonText', kind = 'ui' } },
  399. },
  400. [2] = { {}, { { ui_name = 'MsgArea', hi_name = 'MsgArea', kind = 'ui' } } },
  401. },
  402. }
  403. local ns = api.nvim_create_namespace('test')
  404. local add_indicator = function(line, col)
  405. api.nvim_buf_set_extmark(0, ns, line, col, {
  406. hl_mode = 'combine',
  407. priority = 2,
  408. right_gravity = false,
  409. virt_text = { { '|', 'Delimiter' } },
  410. virt_text_win_col = 0,
  411. virt_text_pos = 'overlay',
  412. invalidate = true,
  413. })
  414. end
  415. add_indicator(1, 0)
  416. add_indicator(2, 0)
  417. add_indicator(3, 0)
  418. add_indicator(4, 0)
  419. screen:expect {
  420. grid = [[
  421. line1 |
  422. {1:|} line2 |
  423. {1:|} line3 |
  424. {1:|} line4 |
  425. {1:|} line5 |
  426. line^6 |
  427. {2:~ }|
  428. {3: }|
  429. ]],
  430. attr_ids = {
  431. [1] = {
  432. { foreground = Screen.colors.SlateBlue },
  433. { { hi_name = 'Special', kind = 'syntax' } },
  434. },
  435. [2] = {
  436. { bold = true, foreground = Screen.colors.Blue },
  437. { { ui_name = 'EndOfBuffer', kind = 'ui', hi_name = 'NonText' } },
  438. },
  439. [3] = { {}, { { ui_name = 'MsgArea', kind = 'ui', hi_name = 'MsgArea' } } },
  440. },
  441. }
  442. n.feed('3ggV2jd')
  443. --screen:redraw_debug()
  444. screen:expect {
  445. grid = [[
  446. line1 |
  447. {1:|} line2 |
  448. ^line6 |
  449. {2:~ }|*4
  450. {3:3 fewer lines }|
  451. ]],
  452. attr_ids = {
  453. [1] = {
  454. { foreground = Screen.colors.SlateBlue },
  455. { { kind = 'syntax', hi_name = 'Special' } },
  456. },
  457. [2] = {
  458. { foreground = Screen.colors.Blue, bold = true },
  459. { { kind = 'ui', ui_name = 'EndOfBuffer', hi_name = 'NonText' } },
  460. },
  461. [3] = { {}, { { kind = 'ui', ui_name = 'MsgArea', hi_name = 'MsgArea' } } },
  462. },
  463. }
  464. end)
  465. it('does not hang when combining too many highlights', function()
  466. local num_lines = 500
  467. insert('first line\n')
  468. for _ = 1, num_lines do
  469. insert([[
  470. line
  471. ]])
  472. end
  473. insert('last line')
  474. n.feed('gg')
  475. screen:expect {
  476. grid = [[
  477. ^first line |
  478. line |*6
  479. {1: }|
  480. ]],
  481. attr_ids = {
  482. [1] = { {}, { { kind = 'ui', hi_name = 'MsgArea', ui_name = 'MsgArea' } } },
  483. },
  484. }
  485. local ns = api.nvim_create_namespace('test')
  486. local add_indicator = function(line, col)
  487. api.nvim_buf_set_extmark(0, ns, line, col, {
  488. hl_mode = 'combine',
  489. priority = 2,
  490. right_gravity = false,
  491. virt_text = { { '|', 'Delimiter' } },
  492. virt_text_win_col = 0,
  493. virt_text_pos = 'overlay',
  494. })
  495. end
  496. for i = 1, num_lines do
  497. add_indicator(i, 0)
  498. end
  499. screen:expect {
  500. grid = [[
  501. ^first line |
  502. {1:|} line |*6
  503. {2: }|
  504. ]],
  505. attr_ids = {
  506. [1] = {
  507. { foreground = Screen.colors.SlateBlue },
  508. { { kind = 'syntax', hi_name = 'Special' } },
  509. },
  510. [2] = { {}, { { kind = 'ui', ui_name = 'MsgArea', hi_name = 'MsgArea' } } },
  511. },
  512. }
  513. n.feed(string.format('3ggV%ijd', num_lines - 2))
  514. --screen:redraw_debug(nil, nil, 100000)
  515. local expected_ids = {}
  516. for i = 1, num_lines - 1 do
  517. expected_ids[i] = 1
  518. end
  519. screen:expect {
  520. grid = string.format(
  521. [[
  522. first line |
  523. {1:|} line |
  524. {2:^|}ast line |
  525. {3:~ }|*4
  526. {4:%-40s}|
  527. ]],
  528. tostring(num_lines - 1) .. ' fewer lines'
  529. ),
  530. attr_ids = {
  531. [1] = {
  532. { foreground = Screen.colors.SlateBlue },
  533. { { kind = 'syntax', hi_name = 'Special' } },
  534. },
  535. [2] = { { foreground = Screen.colors.SlateBlue }, expected_ids },
  536. [3] = {
  537. { foreground = Screen.colors.Blue, bold = true },
  538. { { kind = 'ui', hi_name = 'NonText', ui_name = 'EndOfBuffer' } },
  539. },
  540. [4] = { {}, { { kind = 'ui', hi_name = 'MsgArea', ui_name = 'MsgArea' } } },
  541. },
  542. timeout = 100000,
  543. }
  544. end)
  545. end)