highlight_spec.lua 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178
  1. local t = require('test.testutil')
  2. local n = require('test.functional.testnvim')()
  3. local Screen = require('test.functional.ui.screen')
  4. local clear = n.clear
  5. local insert = n.insert
  6. local exec_lua = n.exec_lua
  7. local feed = n.feed
  8. local command = n.command
  9. local api = n.api
  10. local fn = n.fn
  11. local eq = t.eq
  12. local hl_query_c = [[
  13. (ERROR) @error
  14. "if" @keyword
  15. "else" @keyword
  16. "for" @keyword
  17. "return" @keyword
  18. "const" @type
  19. "static" @type
  20. "struct" @type
  21. "enum" @type
  22. "extern" @type
  23. ; nonexistent specializer for string should fallback to string
  24. (string_literal) @string.nonexistent_specializer
  25. (number_literal) @number
  26. (char_literal) @string
  27. (type_identifier) @type
  28. ((type_identifier) @constant.builtin (#eq? @constant.builtin "LuaRef"))
  29. (primitive_type) @type
  30. (sized_type_specifier) @type
  31. ; Use lua regexes
  32. ((identifier) @function (#contains? @function "lua_"))
  33. ((identifier) @Constant (#lua-match? @Constant "^[A-Z_]+$"))
  34. ((identifier) @Normal (#vim-match? @Normal "^lstate$"))
  35. ((binary_expression left: (identifier) @warning.left right: (identifier) @warning.right) (#eq? @warning.left @warning.right))
  36. (comment) @comment
  37. ]]
  38. local hl_text_c = [[
  39. /// Schedule Lua callback on main loop's event queue
  40. static int nlua_schedule(lua_State *const lstate)
  41. {
  42. if (lua_type(lstate, 1) != LUA_TFUNCTION
  43. || lstate != lstate) {
  44. lua_pushliteral(lstate, "vim.schedule: expected function");
  45. return lua_error(lstate);
  46. }
  47. LuaRef cb = nlua_ref(lstate, 1);
  48. multiqueue_put(main_loop.events, nlua_schedule_event,
  49. 1, (void *)(ptrdiff_t)cb);
  50. return 0;
  51. }]]
  52. local hl_grid_legacy_c = [[
  53. {2:^/// Schedule Lua callback on main loop's event queue} |
  54. {3:static} {3:int} nlua_schedule(lua_State *{3:const} lstate) |
  55. { |
  56. {4:if} (lua_type(lstate, {5:1}) != LUA_TFUNCTION |
  57. || lstate != lstate) { |
  58. lua_pushliteral(lstate, {5:"vim.schedule: expected function"}); |
  59. {4:return} lua_error(lstate); |
  60. } |
  61. |
  62. LuaRef cb = nlua_ref(lstate, {5:1}); |
  63. |
  64. multiqueue_put(main_loop.events, nlua_schedule_event, |
  65. {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
  66. {4:return} {5:0}; |
  67. } |
  68. {1:~ }|*2
  69. |
  70. ]]
  71. local hl_grid_ts_c = [[
  72. {2:^/// Schedule Lua callback on main loop's event queue} |
  73. {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
  74. { |
  75. {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} |
  76. || {6:lstate} != {6:lstate}) { |
  77. {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); |
  78. {4:return} {11:lua_error}(lstate); |
  79. } |
  80. |
  81. {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); |
  82. |
  83. multiqueue_put(main_loop.events, {11:nlua_schedule_event}, |
  84. {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
  85. {4:return} {5:0}; |
  86. } |
  87. {1:~ }|*2
  88. |
  89. ]]
  90. local test_text_c = [[
  91. void ui_refresh(void)
  92. {
  93. int width = INT_MAX, height = INT_MAX;
  94. bool ext_widgets[kUIExtCount];
  95. for (UIExtension i = 0; (int)i < kUIExtCount; i++) {
  96. ext_widgets[i] = true;
  97. }
  98. bool inclusive = ui_override();
  99. for (size_t i = 0; i < ui_count; i++) {
  100. UI *ui = uis[i];
  101. width = MIN(ui->width, width);
  102. height = MIN(ui->height, height);
  103. foo = BAR(ui->bazaar, bazaar);
  104. for (UIExtension j = 0; (int)j < kUIExtCount; j++) {
  105. ext_widgets[j] &= (ui->ui_ext[j] || inclusive);
  106. }
  107. }
  108. }]]
  109. local injection_text_c = [[
  110. int x = INT_MAX;
  111. #define READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
  112. #define foo void main() { \
  113. return 42; \
  114. }
  115. ]]
  116. local injection_grid_c = [[
  117. int x = INT_MAX; |
  118. #define READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) |
  119. #define foo void main() { \ |
  120. return 42; \ |
  121. } |
  122. ^ |
  123. {1:~ }|*11
  124. |
  125. ]]
  126. local injection_grid_expected_c = [[
  127. {3:int} x = {5:INT_MAX}; |
  128. #define {5:READ_STRING}(x, y) ({3:char} *)read_string((x), ({3:size_t})(y)) |
  129. #define foo {3:void} main() { \ |
  130. {4:return} {5:42}; \ |
  131. } |
  132. ^ |
  133. {1:~ }|*11
  134. |
  135. ]]
  136. describe('treesitter highlighting (C)', function()
  137. local screen --- @type test.functional.ui.screen
  138. before_each(function()
  139. clear()
  140. screen = Screen.new(65, 18)
  141. screen:set_default_attr_ids {
  142. [1] = { bold = true, foreground = Screen.colors.Blue1 },
  143. [2] = { foreground = Screen.colors.Blue1 },
  144. [3] = { bold = true, foreground = Screen.colors.SeaGreen4 },
  145. [4] = { bold = true, foreground = Screen.colors.Brown },
  146. [5] = { foreground = Screen.colors.Magenta },
  147. [6] = { foreground = Screen.colors.Red },
  148. [7] = { bold = true, foreground = Screen.colors.SlateBlue },
  149. [8] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red },
  150. [9] = { foreground = Screen.colors.Magenta, background = Screen.colors.Red },
  151. [10] = { foreground = Screen.colors.Red, background = Screen.colors.Red },
  152. [11] = { foreground = Screen.colors.Cyan4 },
  153. }
  154. command [[ hi link @error ErrorMsg ]]
  155. command [[ hi link @warning WarningMsg ]]
  156. end)
  157. it('starting and stopping treesitter highlight works', function()
  158. command('setfiletype c | syntax on')
  159. fn.setreg('r', hl_text_c)
  160. feed('i<C-R><C-O>r<Esc>gg')
  161. -- legacy syntax highlighting is used by default
  162. screen:expect(hl_grid_legacy_c)
  163. exec_lua(function()
  164. vim.treesitter.query.set('c', 'highlights', hl_query_c)
  165. vim.treesitter.start()
  166. end)
  167. -- treesitter highlighting is used
  168. screen:expect(hl_grid_ts_c)
  169. exec_lua(function()
  170. vim.treesitter.stop()
  171. end)
  172. -- legacy syntax highlighting is used
  173. screen:expect(hl_grid_legacy_c)
  174. exec_lua(function()
  175. vim.treesitter.start()
  176. end)
  177. -- treesitter highlighting is used
  178. screen:expect(hl_grid_ts_c)
  179. exec_lua(function()
  180. vim.treesitter.stop()
  181. end)
  182. -- legacy syntax highlighting is used
  183. screen:expect(hl_grid_legacy_c)
  184. end)
  185. it('is updated with edits', function()
  186. insert(hl_text_c)
  187. feed('gg')
  188. screen:expect {
  189. grid = [[
  190. ^/// Schedule Lua callback on main loop's event queue |
  191. static int nlua_schedule(lua_State *const lstate) |
  192. { |
  193. if (lua_type(lstate, 1) != LUA_TFUNCTION |
  194. || lstate != lstate) { |
  195. lua_pushliteral(lstate, "vim.schedule: expected function"); |
  196. return lua_error(lstate); |
  197. } |
  198. |
  199. LuaRef cb = nlua_ref(lstate, 1); |
  200. |
  201. multiqueue_put(main_loop.events, nlua_schedule_event, |
  202. 1, (void *)(ptrdiff_t)cb); |
  203. return 0; |
  204. } |
  205. {1:~ }|*2
  206. |
  207. ]],
  208. }
  209. exec_lua(function()
  210. local parser = vim.treesitter.get_parser(0, 'c')
  211. local highlighter = vim.treesitter.highlighter
  212. highlighter.new(parser, { queries = { c = hl_query_c } })
  213. end)
  214. screen:expect(hl_grid_ts_c)
  215. feed('5Goc<esc>dd')
  216. screen:expect {
  217. grid = [[
  218. {2:/// Schedule Lua callback on main loop's event queue} |
  219. {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
  220. { |
  221. {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} |
  222. || {6:lstate} != {6:lstate}) { |
  223. {11:^lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); |
  224. {4:return} {11:lua_error}(lstate); |
  225. } |
  226. |
  227. {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); |
  228. |
  229. multiqueue_put(main_loop.events, {11:nlua_schedule_event}, |
  230. {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
  231. {4:return} {5:0}; |
  232. } |
  233. {1:~ }|*2
  234. |
  235. ]],
  236. }
  237. feed('7Go*/<esc>')
  238. screen:expect {
  239. grid = [[
  240. {2:/// Schedule Lua callback on main loop's event queue} |
  241. {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
  242. { |
  243. {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} |
  244. || {6:lstate} != {6:lstate}) { |
  245. {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); |
  246. {4:return} {11:lua_error}(lstate); |
  247. {8:*^/} |
  248. } |
  249. |
  250. {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); |
  251. |
  252. multiqueue_put(main_loop.events, {11:nlua_schedule_event}, |
  253. {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
  254. {4:return} {5:0}; |
  255. } |
  256. {1:~ }|
  257. |
  258. ]],
  259. }
  260. feed('3Go/*<esc>')
  261. screen:expect {
  262. grid = [[
  263. {2:/// Schedule Lua callback on main loop's event queue} |
  264. {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
  265. { |
  266. {2:/^*} |
  267. {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} |
  268. {2: || lstate != lstate) {} |
  269. {2: lua_pushliteral(lstate, "vim.schedule: expected function");} |
  270. {2: return lua_error(lstate);} |
  271. {2:*/} |
  272. } |
  273. |
  274. {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); |
  275. |
  276. multiqueue_put(main_loop.events, {11:nlua_schedule_event}, |
  277. {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
  278. {4:return} {5:0}; |
  279. {8:}} |
  280. |
  281. ]],
  282. }
  283. feed('gg$')
  284. feed('~')
  285. screen:expect {
  286. grid = [[
  287. {2:/// Schedule Lua callback on main loop's event queu^E} |
  288. {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
  289. { |
  290. {2:/*} |
  291. {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} |
  292. {2: || lstate != lstate) {} |
  293. {2: lua_pushliteral(lstate, "vim.schedule: expected function");} |
  294. {2: return lua_error(lstate);} |
  295. {2:*/} |
  296. } |
  297. |
  298. {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); |
  299. |
  300. multiqueue_put(main_loop.events, {11:nlua_schedule_event}, |
  301. {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
  302. {4:return} {5:0}; |
  303. {8:}} |
  304. |
  305. ]],
  306. }
  307. feed('re')
  308. screen:expect {
  309. grid = [[
  310. {2:/// Schedule Lua callback on main loop's event queu^e} |
  311. {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
  312. { |
  313. {2:/*} |
  314. {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} |
  315. {2: || lstate != lstate) {} |
  316. {2: lua_pushliteral(lstate, "vim.schedule: expected function");} |
  317. {2: return lua_error(lstate);} |
  318. {2:*/} |
  319. } |
  320. |
  321. {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); |
  322. |
  323. multiqueue_put(main_loop.events, {11:nlua_schedule_event}, |
  324. {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
  325. {4:return} {5:0}; |
  326. {8:}} |
  327. |
  328. ]],
  329. }
  330. end)
  331. it('is updated with :sort', function()
  332. insert(test_text_c)
  333. exec_lua(function()
  334. local parser = vim.treesitter.get_parser(0, 'c')
  335. vim.treesitter.highlighter.new(parser, { queries = { c = hl_query_c } })
  336. end)
  337. screen:expect {
  338. grid = [[
  339. {3:int} width = {5:INT_MAX}, height = {5:INT_MAX}; |
  340. {3:bool} ext_widgets[kUIExtCount]; |
  341. {4:for} ({3:UIExtension} i = {5:0}; ({3:int})i < kUIExtCount; i++) { |
  342. ext_widgets[i] = true; |
  343. } |
  344. |
  345. {3:bool} inclusive = ui_override(); |
  346. {4:for} ({3:size_t} i = {5:0}; i < ui_count; i++) { |
  347. {3:UI} *ui = uis[i]; |
  348. width = {5:MIN}(ui->width, width); |
  349. height = {5:MIN}(ui->height, height); |
  350. foo = {5:BAR}(ui->bazaar, bazaar); |
  351. {4:for} ({3:UIExtension} j = {5:0}; ({3:int})j < kUIExtCount; j++) { |
  352. ext_widgets[j] &= (ui->ui_ext[j] || inclusive); |
  353. } |
  354. } |
  355. ^} |
  356. |
  357. ]],
  358. }
  359. feed ':sort<cr>'
  360. screen:expect {
  361. grid = [[
  362. ^ |
  363. ext_widgets[j] &= (ui->ui_ext[j] || inclusive); |
  364. {3:UI} *ui = uis[i]; |
  365. ext_widgets[i] = true; |
  366. foo = {5:BAR}(ui->bazaar, bazaar); |
  367. {4:for} ({3:UIExtension} j = {5:0}; ({3:int})j < kUIExtCount; j++) { |
  368. height = {5:MIN}(ui->height, height); |
  369. width = {5:MIN}(ui->width, width); |
  370. } |
  371. {3:bool} ext_widgets[kUIExtCount]; |
  372. {3:bool} inclusive = ui_override(); |
  373. {4:for} ({3:UIExtension} i = {5:0}; ({3:int})i < kUIExtCount; i++) { |
  374. {4:for} ({3:size_t} i = {5:0}; i < ui_count; i++) { |
  375. {3:int} width = {5:INT_MAX}, height = {5:INT_MAX}; |
  376. } |*2
  377. {3:void} ui_refresh({3:void}) |
  378. :sort |
  379. ]],
  380. }
  381. feed 'u'
  382. screen:expect {
  383. grid = [[
  384. {3:int} width = {5:INT_MAX}, height = {5:INT_MAX}; |
  385. {3:bool} ext_widgets[kUIExtCount]; |
  386. {4:for} ({3:UIExtension} i = {5:0}; ({3:int})i < kUIExtCount; i++) { |
  387. ext_widgets[i] = true; |
  388. } |
  389. |
  390. {3:bool} inclusive = ui_override(); |
  391. {4:for} ({3:size_t} i = {5:0}; i < ui_count; i++) { |
  392. {3:UI} *ui = uis[i]; |
  393. width = {5:MIN}(ui->width, width); |
  394. height = {5:MIN}(ui->height, height); |
  395. foo = {5:BAR}(ui->bazaar, bazaar); |
  396. {4:for} ({3:UIExtension} j = {5:0}; ({3:int})j < kUIExtCount; j++) { |
  397. ext_widgets[j] &= (ui->ui_ext[j] || inclusive); |
  398. } |
  399. } |
  400. ^} |
  401. 19 changes; before #2 {MATCH:.*}|
  402. ]],
  403. }
  404. end)
  405. it('supports with custom parser', function()
  406. screen:set_default_attr_ids {
  407. [1] = { bold = true, foreground = Screen.colors.SeaGreen4 },
  408. }
  409. insert(test_text_c)
  410. screen:expect {
  411. grid = [[
  412. int width = INT_MAX, height = INT_MAX; |
  413. bool ext_widgets[kUIExtCount]; |
  414. for (UIExtension i = 0; (int)i < kUIExtCount; i++) { |
  415. ext_widgets[i] = true; |
  416. } |
  417. |
  418. bool inclusive = ui_override(); |
  419. for (size_t i = 0; i < ui_count; i++) { |
  420. UI *ui = uis[i]; |
  421. width = MIN(ui->width, width); |
  422. height = MIN(ui->height, height); |
  423. foo = BAR(ui->bazaar, bazaar); |
  424. for (UIExtension j = 0; (int)j < kUIExtCount; j++) { |
  425. ext_widgets[j] &= (ui->ui_ext[j] || inclusive); |
  426. } |
  427. } |
  428. ^} |
  429. |
  430. ]],
  431. }
  432. exec_lua(function()
  433. local parser = vim.treesitter.get_parser(0, 'c')
  434. local query = vim.treesitter.query.parse('c', '(declaration) @decl')
  435. local nodes = {}
  436. for _, node in query:iter_captures(parser:parse()[1]:root(), 0, 0, 19) do
  437. table.insert(nodes, node)
  438. end
  439. parser:set_included_regions({ nodes })
  440. vim.treesitter.highlighter.new(parser, { queries = { c = '(identifier) @type' } })
  441. end)
  442. screen:expect {
  443. grid = [[
  444. int {1:width} = {1:INT_MAX}, {1:height} = {1:INT_MAX}; |
  445. bool {1:ext_widgets}[{1:kUIExtCount}]; |
  446. for (UIExtension {1:i} = 0; (int)i < kUIExtCount; i++) { |
  447. ext_widgets[i] = true; |
  448. } |
  449. |
  450. bool {1:inclusive} = {1:ui_override}(); |
  451. for (size_t {1:i} = 0; i < ui_count; i++) { |
  452. UI *{1:ui} = {1:uis}[{1:i}]; |
  453. width = MIN(ui->width, width); |
  454. height = MIN(ui->height, height); |
  455. foo = BAR(ui->bazaar, bazaar); |
  456. for (UIExtension {1:j} = 0; (int)j < kUIExtCount; j++) { |
  457. ext_widgets[j] &= (ui->ui_ext[j] || inclusive); |
  458. } |
  459. } |
  460. ^} |
  461. |
  462. ]],
  463. }
  464. end)
  465. it('supports injected languages', function()
  466. insert(injection_text_c)
  467. screen:expect { grid = injection_grid_c }
  468. exec_lua(function()
  469. local parser = vim.treesitter.get_parser(0, 'c', {
  470. injections = {
  471. c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c")) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c"))',
  472. },
  473. })
  474. local highlighter = vim.treesitter.highlighter
  475. highlighter.new(parser, { queries = { c = hl_query_c } })
  476. end)
  477. screen:expect { grid = injection_grid_expected_c }
  478. end)
  479. it("supports injecting by ft name in metadata['injection.language']", function()
  480. insert(injection_text_c)
  481. screen:expect { grid = injection_grid_c }
  482. exec_lua(function()
  483. vim.treesitter.language.register('c', 'foo')
  484. local parser = vim.treesitter.get_parser(0, 'c', {
  485. injections = {
  486. c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "foo")) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "foo"))',
  487. },
  488. })
  489. local highlighter = vim.treesitter.highlighter
  490. highlighter.new(parser, { queries = { c = hl_query_c } })
  491. end)
  492. screen:expect { grid = injection_grid_expected_c }
  493. end)
  494. it('supports overriding queries, like ', function()
  495. insert([[
  496. int x = INT_MAX;
  497. #define READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
  498. #define foo void main() { \
  499. return 42; \
  500. }
  501. ]])
  502. exec_lua(function()
  503. local injection_query =
  504. '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c")) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c"))'
  505. vim.treesitter.query.set('c', 'highlights', hl_query_c)
  506. vim.treesitter.query.set('c', 'injections', injection_query)
  507. vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, 'c'))
  508. end)
  509. screen:expect {
  510. grid = [[
  511. {3:int} x = {5:INT_MAX}; |
  512. #define {5:READ_STRING}(x, y) ({3:char} *)read_string((x), ({3:size_t})(y)) |
  513. #define foo {3:void} main() { \ |
  514. {4:return} {5:42}; \ |
  515. } |
  516. ^ |
  517. {1:~ }|*11
  518. |
  519. ]],
  520. }
  521. end)
  522. it('supports highlighting with custom highlight groups', function()
  523. insert(hl_text_c)
  524. feed('gg')
  525. exec_lua(function()
  526. local parser = vim.treesitter.get_parser(0, 'c')
  527. vim.treesitter.highlighter.new(parser, { queries = { c = hl_query_c } })
  528. end)
  529. screen:expect(hl_grid_ts_c)
  530. -- This will change ONLY the literal strings to look like comments
  531. -- The only literal string is the "vim.schedule: expected function" in this test.
  532. exec_lua [[vim.cmd("highlight link @string.nonexistent_specializer comment")]]
  533. screen:expect {
  534. grid = [[
  535. {2:^/// Schedule Lua callback on main loop's event queue} |
  536. {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
  537. { |
  538. {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} |
  539. || {6:lstate} != {6:lstate}) { |
  540. {11:lua_pushliteral}(lstate, {2:"vim.schedule: expected function"}); |
  541. {4:return} {11:lua_error}(lstate); |
  542. } |
  543. |
  544. {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); |
  545. |
  546. multiqueue_put(main_loop.events, {11:nlua_schedule_event}, |
  547. {5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
  548. {4:return} {5:0}; |
  549. } |
  550. {1:~ }|*2
  551. |
  552. ]],
  553. }
  554. screen:expect { unchanged = true }
  555. end)
  556. it('supports highlighting with priority', function()
  557. insert([[
  558. int x = INT_MAX;
  559. #define READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
  560. #define foo void main() { \
  561. return 42; \
  562. }
  563. ]])
  564. exec_lua(function()
  565. local parser = vim.treesitter.get_parser(0, 'c')
  566. vim.treesitter.highlighter.new(parser, {
  567. queries = {
  568. c = hl_query_c .. '\n((translation_unit) @constant (#set! "priority" 101))\n',
  569. },
  570. })
  571. end)
  572. -- expect everything to have Constant highlight
  573. screen:expect {
  574. grid = [[
  575. {12:int}{8: x = INT_MAX;} |
  576. {8:#define READ_STRING(x, y) (}{12:char}{8: *)read_string((x), (}{12:size_t}{8:)(y))} |
  577. {8:#define foo }{12:void}{8: main() { \} |
  578. {8: }{12:return}{8: 42; \} |
  579. {8: }} |
  580. ^ |
  581. {1:~ }|*11
  582. |
  583. ]],
  584. attr_ids = {
  585. [1] = { bold = true, foreground = Screen.colors.Blue1 },
  586. [8] = { foreground = Screen.colors.Magenta1 },
  587. -- bold will not be overwritten at the moment
  588. [12] = { bold = true, foreground = Screen.colors.Magenta1 },
  589. },
  590. }
  591. eq({
  592. { capture = 'constant', metadata = { priority = '101' }, lang = 'c' },
  593. { capture = 'type', metadata = {}, lang = 'c' },
  594. }, exec_lua [[ return vim.treesitter.get_captures_at_pos(0, 0, 2) ]])
  595. end)
  596. it(
  597. "allows to use captures with dots (don't use fallback when specialization of foo exists)",
  598. function()
  599. insert([[
  600. char* x = "Will somebody ever read this?";
  601. ]])
  602. screen:expect {
  603. grid = [[
  604. char* x = "Will somebody ever read this?"; |
  605. ^ |
  606. {1:~ }|*15
  607. |
  608. ]],
  609. }
  610. command [[
  611. hi link @foo.bar Type
  612. hi link @foo String
  613. ]]
  614. exec_lua(function()
  615. local parser = vim.treesitter.get_parser(0, 'c', {})
  616. local highlighter = vim.treesitter.highlighter
  617. highlighter.new(
  618. parser,
  619. { queries = { c = '(primitive_type) @foo.bar (string_literal) @foo' } }
  620. )
  621. end)
  622. screen:expect {
  623. grid = [[
  624. {3:char}* x = {5:"Will somebody ever read this?"}; |
  625. ^ |
  626. {1:~ }|*15
  627. |
  628. ]],
  629. }
  630. -- clearing specialization reactivates fallback
  631. command [[ hi clear @foo.bar ]]
  632. screen:expect {
  633. grid = [[
  634. {5:char}* x = {5:"Will somebody ever read this?"}; |
  635. ^ |
  636. {1:~ }|*15
  637. |
  638. ]],
  639. }
  640. end
  641. )
  642. it('supports conceal attribute', function()
  643. insert(hl_text_c)
  644. -- conceal can be empty or a single cchar.
  645. exec_lua(function()
  646. vim.opt.cole = 2
  647. local parser = vim.treesitter.get_parser(0, 'c')
  648. vim.treesitter.highlighter.new(parser, {
  649. queries = {
  650. c = [[
  651. ("static" @keyword
  652. (#set! conceal "R"))
  653. ((identifier) @Identifier
  654. (#set! conceal "")
  655. (#eq? @Identifier "lstate"))
  656. ((call_expression
  657. function: (identifier) @function
  658. arguments: (argument_list) @arguments)
  659. (#eq? @function "multiqueue_put")
  660. (#set! @function conceal "V"))
  661. ]],
  662. },
  663. })
  664. end)
  665. screen:expect {
  666. grid = [[
  667. /// Schedule Lua callback on main loop's event queue |
  668. {4:R} int nlua_schedule(lua_State *const ) |
  669. { |
  670. if (lua_type(, 1) != LUA_TFUNCTION |
  671. || != ) { |
  672. lua_pushliteral(, "vim.schedule: expected function"); |
  673. return lua_error(); |
  674. } |
  675. |
  676. LuaRef cb = nlua_ref(, 1); |
  677. |
  678. {11:V}(main_loop.events, nlua_schedule_event, |
  679. 1, (void *)(ptrdiff_t)cb); |
  680. return 0; |
  681. ^} |
  682. {1:~ }|*2
  683. |
  684. ]],
  685. }
  686. end)
  687. it('@foo.bar groups has the correct fallback behavior', function()
  688. local get_hl = function(name)
  689. return api.nvim_get_hl_by_name(name, 1).foreground
  690. end
  691. api.nvim_set_hl(0, '@foo', { fg = 1 })
  692. api.nvim_set_hl(0, '@foo.bar', { fg = 2 })
  693. api.nvim_set_hl(0, '@foo.bar.baz', { fg = 3 })
  694. eq(1, get_hl '@foo')
  695. eq(1, get_hl '@foo.a.b.c.d')
  696. eq(2, get_hl '@foo.bar')
  697. eq(2, get_hl '@foo.bar.a.b.c.d')
  698. eq(3, get_hl '@foo.bar.baz')
  699. eq(3, get_hl '@foo.bar.baz.d')
  700. -- lookup is case insensitive
  701. eq(2, get_hl '@FOO.BAR.SPAM')
  702. api.nvim_set_hl(0, '@foo.missing.exists', { fg = 3 })
  703. eq(1, get_hl '@foo.missing')
  704. eq(3, get_hl '@foo.missing.exists')
  705. eq(3, get_hl '@foo.missing.exists.bar')
  706. eq(nil, get_hl '@total.nonsense.but.a.lot.of.dots')
  707. end)
  708. it('supports multiple nodes assigned to the same capture #17060', function()
  709. insert([[
  710. int x = 4;
  711. int y = 5;
  712. int z = 6;
  713. ]])
  714. exec_lua(function()
  715. local query = '((declaration)+ @string)'
  716. vim.treesitter.query.set('c', 'highlights', query)
  717. vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, 'c'))
  718. end)
  719. screen:expect {
  720. grid = [[
  721. {5:int x = 4;} |
  722. {5:int y = 5;} |
  723. {5:int z = 6;} |
  724. ^ |
  725. {1:~ }|*13
  726. |
  727. ]],
  728. }
  729. end)
  730. it('gives higher priority to more specific captures #27895', function()
  731. insert([[
  732. void foo(int *bar);
  733. ]])
  734. local query = [[
  735. "*" @operator
  736. (parameter_declaration
  737. declarator: (pointer_declarator) @variable.parameter)
  738. ]]
  739. exec_lua(function()
  740. vim.treesitter.query.set('c', 'highlights', query)
  741. vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, 'c'))
  742. end)
  743. screen:expect {
  744. grid = [[
  745. void foo(int {4:*}{11:bar}); |
  746. ^ |
  747. {1:~ }|*15
  748. |
  749. ]],
  750. }
  751. end)
  752. end)
  753. describe('treesitter highlighting (lua)', function()
  754. local screen
  755. before_each(function()
  756. clear()
  757. screen = Screen.new(65, 18)
  758. screen:set_default_attr_ids {
  759. [1] = { bold = true, foreground = Screen.colors.Blue },
  760. [2] = { foreground = Screen.colors.DarkCyan },
  761. [3] = { foreground = Screen.colors.Magenta },
  762. [4] = { foreground = Screen.colors.SlateBlue },
  763. [5] = { bold = true, foreground = Screen.colors.Brown },
  764. }
  765. end)
  766. it('supports language injections', function()
  767. insert [[
  768. local ffi = require('ffi')
  769. ffi.cdef("int (*fun)(int, char *);")
  770. ]]
  771. exec_lua(function()
  772. vim.bo.filetype = 'lua'
  773. vim.treesitter.start()
  774. end)
  775. screen:expect {
  776. grid = [[
  777. {5:local} {2:ffi} {5:=} {4:require(}{3:'ffi'}{4:)} |
  778. {2:ffi}{4:.}{2:cdef}{4:(}{3:"}{4:int}{3: }{4:(}{5:*}{3:fun}{4:)(int,}{3: }{4:char}{3: }{5:*}{4:);}{3:"}{4:)} |
  779. ^ |
  780. {1:~ }|*14
  781. |
  782. ]],
  783. }
  784. end)
  785. end)
  786. describe('treesitter highlighting (help)', function()
  787. local screen
  788. before_each(function()
  789. clear()
  790. screen = Screen.new(40, 6)
  791. screen:set_default_attr_ids {
  792. [1] = { foreground = Screen.colors.Blue1 },
  793. [2] = { bold = true, foreground = Screen.colors.Blue1 },
  794. [3] = { bold = true, foreground = Screen.colors.Brown },
  795. [4] = { foreground = Screen.colors.Cyan4 },
  796. [5] = { foreground = Screen.colors.Magenta1 },
  797. title = { bold = true, foreground = Screen.colors.Magenta1 },
  798. h1_delim = { nocombine = true, underdouble = true },
  799. h2_delim = { nocombine = true, underline = true },
  800. }
  801. end)
  802. it('defaults in vimdoc/highlights.scm', function()
  803. -- Avoid regressions when syncing upstream vimdoc queries.
  804. insert [[
  805. ==============================================================================
  806. NVIM DOCUMENTATION
  807. ------------------------------------------------------------------------------
  808. ABOUT NVIM *tag-1* *tag-2*
  809. |news| News
  810. |nvim| NVim
  811. ]]
  812. feed('gg')
  813. exec_lua(function()
  814. vim.wo.wrap = false
  815. vim.bo.filetype = 'help'
  816. vim.treesitter.start()
  817. end)
  818. screen:expect({
  819. grid = [[
  820. {h1_delim:^========================================}|
  821. {title:NVIM DOCUMENTATION} |
  822. |
  823. {h2_delim:----------------------------------------}|
  824. {title:ABOUT NVIM} |
  825. |
  826. ]],
  827. })
  828. end)
  829. it('correctly redraws added/removed injections', function()
  830. insert [[
  831. >ruby
  832. -- comment
  833. local this_is = 'actually_lua'
  834. <
  835. ]]
  836. exec_lua(function()
  837. vim.bo.filetype = 'help'
  838. vim.treesitter.start()
  839. end)
  840. screen:expect {
  841. grid = [[
  842. {1:>}{3:ruby} |
  843. {1: -- comment} |
  844. {1: local this_is = 'actually_lua'} |
  845. {1:<} |
  846. ^ |
  847. |
  848. ]],
  849. }
  850. n.api.nvim_buf_set_text(0, 0, 1, 0, 5, { 'lua' })
  851. screen:expect {
  852. grid = [[
  853. {1:>}{3:lua} |
  854. {1: -- comment} |
  855. {1: }{3:local}{1: }{4:this_is}{1: }{3:=}{1: }{5:'actually_lua'} |
  856. {1:<} |
  857. ^ |
  858. |
  859. ]],
  860. }
  861. n.api.nvim_buf_set_text(0, 0, 1, 0, 4, { 'ruby' })
  862. screen:expect {
  863. grid = [[
  864. {1:>}{3:ruby} |
  865. {1: -- comment} |
  866. {1: local this_is = 'actually_lua'} |
  867. {1:<} |
  868. ^ |
  869. |
  870. ]],
  871. }
  872. end)
  873. it('correctly redraws injections subpriorities', function()
  874. -- The top level string node will be highlighted first
  875. -- with an extmark spanning multiple lines.
  876. -- When the next line is drawn, which includes an injection,
  877. -- make sure the highlight appears above the base tree highlight
  878. insert([=[
  879. local s = [[
  880. local also = lua
  881. ]]
  882. ]=])
  883. exec_lua(function()
  884. local parser = vim.treesitter.get_parser(0, 'lua', {
  885. injections = {
  886. lua = '(string content: (_) @injection.content (#set! injection.language lua))',
  887. },
  888. })
  889. vim.treesitter.highlighter.new(parser)
  890. end)
  891. screen:expect {
  892. grid = [=[
  893. {3:local} {4:s} {3:=} {5:[[} |
  894. {5: }{3:local}{5: }{4:also}{5: }{3:=}{5: }{4:lua} |
  895. {5:]]} |
  896. ^ |
  897. {2:~ }|
  898. |
  899. ]=],
  900. }
  901. end)
  902. end)
  903. describe('treesitter highlighting (nested injections)', function()
  904. local screen --- @type test.functional.ui.screen
  905. before_each(function()
  906. clear()
  907. screen = Screen.new(80, 7)
  908. screen:set_default_attr_ids {
  909. [1] = { foreground = Screen.colors.SlateBlue },
  910. [2] = { bold = true, foreground = Screen.colors.Brown },
  911. [3] = { foreground = Screen.colors.Cyan4 },
  912. [4] = { foreground = Screen.colors.Fuchsia },
  913. }
  914. end)
  915. it('correctly redraws nested injections (GitHub #25252)', function()
  916. insert [=[
  917. function foo() print("Lua!") end
  918. local lorem = {
  919. ipsum = {},
  920. bar = {},
  921. }
  922. vim.cmd([[
  923. augroup RustLSP
  924. autocmd CursorHold silent! lua vim.lsp.buf.document_highlight()
  925. augroup END
  926. ]])
  927. ]=]
  928. exec_lua(function()
  929. vim.opt.scrolloff = 0
  930. vim.bo.filetype = 'lua'
  931. vim.treesitter.start()
  932. end)
  933. -- invalidate the language tree
  934. feed('ggi--[[<ESC>04x')
  935. screen:expect {
  936. grid = [[
  937. {2:^function} {3:foo}{1:()} {1:print(}{4:"Lua!"}{1:)} {2:end} |
  938. |
  939. {2:local} {3:lorem} {2:=} {1:{} |
  940. {3:ipsum} {2:=} {1:{},} |
  941. {3:bar} {2:=} {1:{},} |
  942. {1:}} |
  943. |
  944. ]],
  945. }
  946. -- spam newline insert/delete to invalidate Lua > Vim > Lua region
  947. feed('3jo<ESC>ddko<ESC>ddko<ESC>ddko<ESC>ddk0')
  948. screen:expect {
  949. grid = [[
  950. {2:function} {3:foo}{1:()} {1:print(}{4:"Lua!"}{1:)} {2:end} |
  951. |
  952. {2:local} {3:lorem} {2:=} {1:{} |
  953. ^ {3:ipsum} {2:=} {1:{},} |
  954. {3:bar} {2:=} {1:{},} |
  955. {1:}} |
  956. |
  957. ]],
  958. }
  959. end)
  960. end)
  961. describe('treesitter highlighting (markdown)', function()
  962. local screen
  963. before_each(function()
  964. clear()
  965. screen = Screen.new(40, 6)
  966. exec_lua(function()
  967. vim.bo.filetype = 'markdown'
  968. vim.treesitter.start()
  969. end)
  970. end)
  971. it('supports hyperlinks', function()
  972. local url = 'https://example.com'
  973. insert(string.format('[This link text](%s) is a hyperlink.', url))
  974. screen:add_extra_attr_ids({
  975. [100] = { foreground = Screen.colors.DarkCyan, url = 'https://example.com' },
  976. [101] = {
  977. foreground = Screen.colors.SlateBlue,
  978. url = 'https://example.com',
  979. underline = true,
  980. },
  981. })
  982. screen:expect({
  983. grid = [[
  984. {25:[}{100:This link text}{25:](}{101:https://example.com}{25:)} is|
  985. a hyperlink^. |
  986. {1:~ }|*3
  987. |
  988. ]],
  989. })
  990. end)
  991. it('works with spellchecked and smoothscrolled topline', function()
  992. insert([[
  993. - $f(0)=\sum_{k=1}^{\infty}\frac{2}{\pi^{2}k^{2}}+\lim_{w \to 0}x$.
  994. ```c
  995. printf('Hello World!');
  996. ```
  997. ]])
  998. command('set spell smoothscroll')
  999. feed('gg<C-E>')
  1000. screen:add_extra_attr_ids({ [100] = { undercurl = true, special = Screen.colors.Red } })
  1001. screen:expect({
  1002. grid = [[
  1003. {1:<<<}k^{2}}+\{100:lim}_{w \to 0}x$^. |
  1004. |
  1005. {18:```}{15:c} |
  1006. {25:printf}{16:(}{26:'Hello World!'}{16:);} |
  1007. {18:```} |
  1008. |
  1009. ]],
  1010. })
  1011. end)
  1012. end)
  1013. it('starting and stopping treesitter highlight in init.lua works #29541', function()
  1014. t.write_file(
  1015. 'Xinit.lua',
  1016. [[
  1017. vim.bo.ft = 'c'
  1018. vim.treesitter.start()
  1019. vim.treesitter.stop()
  1020. ]]
  1021. )
  1022. finally(function()
  1023. os.remove('Xinit.lua')
  1024. end)
  1025. clear({ args = { '-u', 'Xinit.lua' } })
  1026. eq('', api.nvim_get_vvar('errmsg'))
  1027. local screen = Screen.new(65, 18)
  1028. screen:set_default_attr_ids {
  1029. [1] = { bold = true, foreground = Screen.colors.Blue1 },
  1030. [2] = { foreground = Screen.colors.Blue1 },
  1031. [3] = { bold = true, foreground = Screen.colors.SeaGreen4 },
  1032. [4] = { bold = true, foreground = Screen.colors.Brown },
  1033. [5] = { foreground = Screen.colors.Magenta },
  1034. [6] = { foreground = Screen.colors.Red },
  1035. [7] = { bold = true, foreground = Screen.colors.SlateBlue },
  1036. [8] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red },
  1037. [9] = { foreground = Screen.colors.Magenta, background = Screen.colors.Red },
  1038. [10] = { foreground = Screen.colors.Red, background = Screen.colors.Red },
  1039. [11] = { foreground = Screen.colors.Cyan4 },
  1040. }
  1041. fn.setreg('r', hl_text_c)
  1042. feed('i<C-R><C-O>r<Esc>gg')
  1043. -- legacy syntax highlighting is used
  1044. screen:expect(hl_grid_legacy_c)
  1045. end)