highlight_spec.lua 48 KB

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