semantic_tokens_spec.lua (65066B)
1 local t = require('test.testutil') 2 local n = require('test.functional.testnvim')() 3 local Screen = require('test.functional.ui.screen') 4 local t_lsp = require('test.functional.plugin.lsp.testutil') 5 6 local command = n.command 7 local dedent = t.dedent 8 local eq = t.eq 9 local exec_lua = n.exec_lua 10 local feed = n.feed 11 local insert = n.insert 12 local api = n.api 13 14 local clear_notrace = t_lsp.clear_notrace 15 local create_server_definition = t_lsp.create_server_definition 16 17 before_each(function() 18 clear_notrace() 19 end) 20 21 after_each(function() 22 api.nvim_exec_autocmds('VimLeavePre', { modeline = false }) 23 end) 24 25 describe('semantic token highlighting', function() 26 local screen --- @type test.functional.ui.screen 27 before_each(function() 28 screen = Screen.new(40, 16) 29 screen:set_default_attr_ids { 30 [1] = { bold = true, foreground = Screen.colors.Blue1 }, 31 [2] = { foreground = Screen.colors.DarkCyan }, 32 [3] = { foreground = Screen.colors.SlateBlue }, 33 [4] = { bold = true, foreground = Screen.colors.SeaGreen }, 34 [5] = { foreground = tonumber('0x6a0dad') }, 35 [6] = { foreground = Screen.colors.Blue1 }, 36 [7] = { bold = true, foreground = Screen.colors.DarkCyan }, 37 [8] = { bold = true, foreground = Screen.colors.SlateBlue }, 38 [9] = { bold = true, foreground = tonumber('0x6a0dad') }, 39 [10] = { bold = true, foreground = Screen.colors.Brown }, 40 [11] = { foreground = Screen.colors.Magenta1 }, 41 } 42 command([[ hi link @lsp.type.namespace Type ]]) 43 command([[ hi link @lsp.type.function Special ]]) 44 command([[ hi link @lsp.type.comment Comment ]]) 45 command([[ hi @lsp.mod.declaration gui=bold ]]) 46 end) 47 48 describe('general', function() 49 local text = dedent([[ 50 #include <iostream> 51 52 int main() 53 { 54 int x; 55 #ifdef __cplusplus 56 std::cout << x << "\n"; 57 #else 58 printf("%d\n", x); 59 #endif 60 } 61 }]]) 62 63 local legend = [[{ 64 "tokenTypes": [ 65 "variable", "variable", "parameter", "function", "method", "function", "property", "variable", "class", "interface", "enum", "enumMember", "type", "type", "unknown", "namespace", "typeParameter", "concept", "type", "macro", "comment" 66 ], 67 "tokenModifiers": [ 68 "declaration", "deprecated", "deduced", "readonly", "static", "abstract", "virtual", "dependentName", "defaultLibrary", "usedAsMutableReference", "functionScope", "classScope", "fileScope", "globalScope" 69 ] 70 }]] 71 72 local response = [[{ 73 "data": [ 2, 4, 4, 3, 8193, 2, 8, 1, 1, 1025, 1, 7, 11, 19, 8192, 1, 4, 3, 15, 8448, 0, 5, 4, 0, 8448, 0, 8, 1, 1, 1024, 1, 0, 5, 20, 0, 1, 0, 22, 20, 0, 1, 0, 6, 20, 0 ], 74 "resultId": "1" 75 }]] 76 77 local range_response = [[{ 78 "data": [ 2, 4, 4, 3, 8193, 2, 8, 1, 1, 1025 ], 79 "resultId": "2" 80 }]] 81 82 local edit_response = [[{ 83 "edits": [ {"data": [ 2, 8, 1, 3, 8193, 1, 7, 11, 19, 8192, 1, 4, 3, 15, 8448, 0, 5, 4, 0, 8448, 0, 8, 1, 3, 8192 ], "deleteCount": 25, "start": 5 } ], 84 "resultId": "3" 85 }]] 86 87 before_each(function() 88 exec_lua(create_server_definition) 89 exec_lua(function() 90 _G.server = _G._create_server({ 91 capabilities = { 92 semanticTokensProvider = { 93 full = { delta = true }, 94 range = false, 95 legend = vim.fn.json_decode(legend), 96 }, 97 }, 98 handlers = { 99 ['textDocument/semanticTokens/full'] = function(_, _, callback) 100 callback(nil, vim.fn.json_decode(response)) 101 end, 102 ['textDocument/semanticTokens/full/delta'] = function(_, _, callback) 103 callback(nil, vim.fn.json_decode(edit_response)) 104 end, 105 }, 106 }) 107 end) 108 end) 109 110 it('buffer is highlighted when attached', function() 111 insert(text) 112 exec_lua(function() 113 local bufnr = vim.api.nvim_get_current_buf() 114 vim.api.nvim_win_set_buf(0, bufnr) 115 vim.bo[bufnr].filetype = 'some-filetype' 116 vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) 117 end) 118 119 screen:expect { 120 grid = [[ 121 #include <iostream> | 122 | 123 int {8:main}() | 124 { | 125 int {7:x}; | 126 #ifdef {5:__cplusplus} | 127 {4:std}::{2:cout} << {2:x} << "\n"; | 128 {6:#else} | 129 {6: printf("%d\n", x);} | 130 {6:#endif} | 131 } | 132 ^} | 133 {1:~ }|*3 134 | 135 ]], 136 } 137 end) 138 139 it('buffer is highlighted with multiline tokens', function() 140 insert(text) 141 exec_lua(function() 142 _G.server2 = _G._create_server({ 143 capabilities = { 144 semanticTokensProvider = { 145 full = { delta = false }, 146 legend = vim.fn.json_decode(legend), 147 }, 148 }, 149 handlers = { 150 ['textDocument/semanticTokens/full'] = function(_, _, callback) 151 callback(nil, { 152 data = { 5, 0, 82, 0, 0 }, 153 resultId = 1, 154 }) 155 end, 156 }, 157 }) 158 end, legend, response, edit_response) 159 exec_lua(function() 160 local bufnr = vim.api.nvim_get_current_buf() 161 vim.api.nvim_win_set_buf(0, bufnr) 162 vim.bo[bufnr].filetype = 'some-filetype' 163 vim.lsp.start({ name = 'dummy', cmd = _G.server2.cmd }) 164 end) 165 166 screen:expect { 167 grid = [[ 168 #include <iostream> | 169 | 170 int main() | 171 { | 172 int x; | 173 {2:#ifdef __cplusplus} | 174 {2: std::cout << x << "\n";} | 175 {2:#else} | 176 {2: printf("%d\n", x);} | 177 {2:#endif} | 178 } | 179 ^} | 180 {1:~ }|*3 181 | 182 ]], 183 } 184 end) 185 186 it('calls both range and full when range is supported', function() 187 insert(text) 188 exec_lua(function() 189 _G.server_range = _G._create_server({ 190 capabilities = { 191 semanticTokensProvider = { 192 full = { delta = false }, 193 range = true, 194 legend = vim.fn.json_decode(legend), 195 }, 196 }, 197 handlers = { 198 ['textDocument/semanticTokens/range'] = function(_, _, callback) 199 callback(nil, vim.fn.json_decode(range_response)) 200 end, 201 ['textDocument/semanticTokens/full'] = function(_, _, callback) 202 callback(nil, vim.fn.json_decode(response)) 203 end, 204 }, 205 }) 206 end) 207 exec_lua(function() 208 local bufnr = vim.api.nvim_get_current_buf() 209 vim.api.nvim_win_set_buf(0, bufnr) 210 vim.lsp.start({ name = 'dummy', cmd = _G.server_range.cmd }) 211 end) 212 213 screen:expect { 214 grid = [[ 215 #include <iostream> | 216 | 217 int {8:main}() | 218 { | 219 int {7:x}; | 220 #ifdef {5:__cplusplus} | 221 {4:std}::{2:cout} << {2:x} << "\n"; | 222 {6:#else} | 223 {6: printf("%d\n", x);} | 224 {6:#endif} | 225 } | 226 ^} | 227 {1:~ }|*3 228 | 229 ]], 230 } 231 232 local messages = exec_lua('return _G.server_range.messages') 233 local called_range = false 234 local called_full = false 235 for _, m in ipairs(messages) do 236 if m.method == 'textDocument/semanticTokens/range' then 237 called_range = true 238 end 239 if m.method == 'textDocument/semanticTokens/full' then 240 called_full = true 241 end 242 end 243 eq(true, called_range) 244 eq(true, called_full) 245 end) 246 247 it('does not call range when only full is supported', function() 248 exec_lua(create_server_definition) 249 insert(text) 250 exec_lua(function() 251 _G.server_full = _G._create_server({ 252 capabilities = { 253 semanticTokensProvider = { 254 full = { delta = false }, 255 range = false, 256 legend = vim.fn.json_decode(legend), 257 }, 258 }, 259 handlers = { 260 ['textDocument/semanticTokens/full'] = function(_, _, callback) 261 callback(nil, vim.fn.json_decode(response)) 262 end, 263 ['textDocument/semanticTokens/range'] = function(_, _, callback) 264 callback(nil, vim.fn.json_decode(range_response)) 265 end, 266 }, 267 }) 268 return vim.lsp.start({ name = 'dummy', cmd = _G.server_full.cmd }) 269 end) 270 271 local messages = exec_lua('return _G.server_full.messages') 272 local called_full = false 273 local called_range = false 274 for _, m in ipairs(messages) do 275 if m.method == 'textDocument/semanticTokens/full' then 276 called_full = true 277 end 278 if m.method == 'textDocument/semanticTokens/range' then 279 called_range = true 280 end 281 end 282 eq(true, called_full) 283 eq(false, called_range) 284 end) 285 286 it('does not call range after full request received', function() 287 exec_lua(create_server_definition) 288 insert(text) 289 exec_lua(function() 290 _G.server_full = _G._create_server({ 291 capabilities = { 292 semanticTokensProvider = { 293 full = { delta = false }, 294 range = true, 295 legend = vim.fn.json_decode(legend), 296 }, 297 }, 298 handlers = { 299 ['textDocument/semanticTokens/full'] = function(_, _, callback) 300 callback(nil, vim.fn.json_decode(response)) 301 end, 302 ['textDocument/semanticTokens/range'] = function(_, _, callback) 303 callback(nil, vim.fn.json_decode(range_response)) 304 end, 305 }, 306 }) 307 return vim.lsp.start({ name = 'dummy', cmd = _G.server_full.cmd }) 308 end) 309 310 -- ensure initial semantic token requests have been sent before feeding input 311 n.poke_eventloop() 312 -- modify the buffer 313 feed('o<ESC>') 314 315 local messages = exec_lua('return _G.server_full.messages') 316 local called_full = 0 317 local called_range = 0 318 for _, m in ipairs(messages) do 319 if m.method == 'textDocument/semanticTokens/full' then 320 called_full = called_full + 1 321 end 322 if m.method == 'textDocument/semanticTokens/range' then 323 called_range = called_range + 1 324 end 325 end 326 eq(2, called_full) 327 eq(1, called_range) 328 end) 329 330 it('range requests preserve highlights outside updated range', function() 331 screen:try_resize(40, 6) 332 insert(text) 333 feed('gg') 334 335 local client_id, bufnr = exec_lua(function() 336 _G.response = [[{ 337 "data": [ 2, 4, 4, 3, 8193, 2, 8, 1, 1, 1025 ] 338 }]] 339 _G.server2 = _G._create_server({ 340 capabilities = { 341 semanticTokensProvider = { 342 range = true, 343 legend = vim.fn.json_decode(legend), 344 }, 345 }, 346 handlers = { 347 ['textDocument/semanticTokens/range'] = function(_, _, callback) 348 callback(nil, vim.fn.json_decode(_G.response)) 349 end, 350 }, 351 }) 352 local bufnr = vim.api.nvim_get_current_buf() 353 local client_id = assert(vim.lsp.start({ name = 'dummy', cmd = _G.server2.cmd })) 354 vim.schedule(function() 355 vim.lsp.semantic_tokens._start(bufnr, client_id, 0) 356 end) 357 return client_id, bufnr 358 end) 359 360 screen:expect { 361 grid = [[ 362 ^#include <iostream> | 363 | 364 int {8:main}() | 365 { | 366 int {7:x}; | 367 | 368 ]], 369 } 370 371 eq( 372 2, 373 exec_lua(function() 374 return #vim.lsp.semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights 375 end) 376 ) 377 378 exec_lua(function() 379 _G.response = [[{ 380 "data": [ 7, 0, 5, 20, 0, 1, 0, 22, 20, 0, 1, 0, 6, 20, 0 ] 381 }]] 382 end) 383 384 feed('G') 385 386 screen:expect { 387 grid = [[ 388 {6:#else} | 389 {6: printf("%d\n", x);} | 390 {6:#endif} | 391 } | 392 ^} | 393 | 394 ]], 395 } 396 397 eq( 398 5, 399 exec_lua(function() 400 return #vim.lsp.semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights 401 end) 402 ) 403 404 exec_lua(function() 405 _G.response = [[{ 406 "data": [ 2, 4, 4, 3, 8193, 2, 8, 1, 1, 1025, 1, 7, 11, 19, 8192 ] 407 }]] 408 end) 409 feed('ggLj0') 410 411 screen:expect { 412 grid = [[ 413 | 414 int {8:main}() | 415 { | 416 int {7:x}; | 417 ^#ifdef {5:__cplusplus} | 418 | 419 ]], 420 } 421 422 eq( 423 6, 424 exec_lua(function() 425 return #vim.lsp.semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights 426 end) 427 ) 428 429 eq( 430 { 431 { 432 line = 2, 433 end_line = 2, 434 start_col = 4, 435 end_col = 8, 436 marked = true, 437 modifiers = { 438 declaration = true, 439 globalScope = true, 440 }, 441 type = 'function', 442 }, 443 { 444 line = 4, 445 end_line = 4, 446 start_col = 8, 447 end_col = 9, 448 marked = true, 449 modifiers = { 450 declaration = true, 451 functionScope = true, 452 }, 453 type = 'variable', 454 }, 455 { 456 line = 5, 457 end_line = 5, 458 start_col = 7, 459 end_col = 18, 460 marked = true, 461 modifiers = { 462 globalScope = true, 463 }, 464 type = 'macro', 465 }, 466 { 467 line = 7, 468 end_line = 7, 469 start_col = 0, 470 end_col = 5, 471 marked = true, 472 modifiers = {}, 473 type = 'comment', 474 }, 475 { 476 line = 8, 477 end_line = 8, 478 start_col = 0, 479 end_col = 22, 480 marked = true, 481 modifiers = {}, 482 type = 'comment', 483 }, 484 { 485 line = 9, 486 end_line = 9, 487 start_col = 0, 488 end_col = 6, 489 marked = true, 490 modifiers = {}, 491 type = 'comment', 492 }, 493 }, 494 exec_lua(function() 495 return vim.lsp.semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights 496 end) 497 ) 498 end) 499 500 it('use LspTokenUpdate and highlight_token', function() 501 insert(text) 502 exec_lua(function() 503 vim.api.nvim_create_autocmd('LspTokenUpdate', { 504 callback = function(args) 505 local token = args.data.token --- @type STTokenRange 506 if token.type == 'function' and token.modifiers.declaration then 507 vim.lsp.semantic_tokens.highlight_token(token, args.buf, args.data.client_id, 'Macro') 508 end 509 end, 510 }) 511 local bufnr = vim.api.nvim_get_current_buf() 512 vim.api.nvim_win_set_buf(0, bufnr) 513 vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) 514 end) 515 516 screen:expect { 517 grid = [[ 518 #include <iostream> | 519 | 520 int {9:main}() | 521 { | 522 int {7:x}; | 523 #ifdef {5:__cplusplus} | 524 {4:std}::{2:cout} << {2:x} << "\n"; | 525 {6:#else} | 526 {6: printf("%d\n", x);} | 527 {6:#endif} | 528 } | 529 ^} | 530 {1:~ }|*3 531 | 532 ]], 533 } 534 end) 535 536 it('buffer is unhighlighted when client is detached', function() 537 insert(text) 538 539 local bufnr = n.api.nvim_get_current_buf() 540 local client_id = exec_lua(function() 541 vim.api.nvim_win_set_buf(0, bufnr) 542 local client_id = vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) 543 vim.wait(1000, function() 544 return #_G.server.messages > 1 545 end) 546 return client_id 547 end) 548 549 exec_lua(function() 550 --- @diagnostic disable-next-line:duplicate-set-field 551 vim.notify = function() end 552 vim.lsp.buf_detach_client(bufnr, client_id) 553 end) 554 555 screen:expect { 556 grid = [[ 557 #include <iostream> | 558 | 559 int main() | 560 { | 561 int x; | 562 #ifdef __cplusplus | 563 std::cout << x << "\n"; | 564 #else | 565 printf("%d\n", x); | 566 #endif | 567 } | 568 ^} | 569 {1:~ }|*3 570 | 571 ]], 572 } 573 end) 574 575 it( 576 'buffer is highlighted and unhighlighted when semantic token highlighting is enabled and disabled', 577 function() 578 local bufnr = n.api.nvim_get_current_buf() 579 exec_lua(function() 580 vim.api.nvim_win_set_buf(0, bufnr) 581 return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) 582 end) 583 584 insert(text) 585 586 exec_lua(function() 587 --- @diagnostic disable-next-line:duplicate-set-field 588 vim.notify = function() end 589 vim.lsp.semantic_tokens.enable(false) 590 end) 591 592 screen:expect { 593 grid = [[ 594 #include <iostream> | 595 | 596 int main() | 597 { | 598 int x; | 599 #ifdef __cplusplus | 600 std::cout << x << "\n"; | 601 #else | 602 printf("%d\n", x); | 603 #endif | 604 } | 605 ^} | 606 {1:~ }|*3 607 | 608 ]], 609 } 610 611 exec_lua(function() 612 vim.lsp.semantic_tokens.enable(true) 613 end) 614 615 screen:expect { 616 grid = [[ 617 #include <iostream> | 618 | 619 int {8:main}() | 620 { | 621 int {7:x}; | 622 #ifdef {5:__cplusplus} | 623 {4:std}::{2:cout} << {2:x} << "\n"; | 624 {6:#else} | 625 {6: printf("%d\n", x);} | 626 {6:#endif} | 627 } | 628 ^} | 629 {1:~ }|*3 630 | 631 ]], 632 } 633 end 634 ) 635 636 it('highlights start and stop when using "0" for current buffer', function() 637 exec_lua(function() 638 return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) 639 end) 640 641 insert(text) 642 643 exec_lua(function() 644 --- @diagnostic disable-next-line:duplicate-set-field 645 vim.notify = function() end 646 vim.lsp.semantic_tokens.enable(false, { bufnr = 0 }) 647 end) 648 649 screen:expect { 650 grid = [[ 651 #include <iostream> | 652 | 653 int main() | 654 { | 655 int x; | 656 #ifdef __cplusplus | 657 std::cout << x << "\n"; | 658 #else | 659 printf("%d\n", x); | 660 #endif | 661 } | 662 ^} | 663 {1:~ }|*3 664 | 665 ]], 666 } 667 668 exec_lua(function() 669 vim.lsp.semantic_tokens.enable(true, { bufnr = 0 }) 670 end) 671 672 screen:expect { 673 grid = [[ 674 #include <iostream> | 675 | 676 int {8:main}() | 677 { | 678 int {7:x}; | 679 #ifdef {5:__cplusplus} | 680 {4:std}::{2:cout} << {2:x} << "\n"; | 681 {6:#else} | 682 {6: printf("%d\n", x);} | 683 {6:#endif} | 684 } | 685 ^} | 686 {1:~ }|*3 687 | 688 ]], 689 } 690 end) 691 692 it('buffer is re-highlighted when force refreshed', function() 693 insert(text) 694 exec_lua(function() 695 vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) 696 end) 697 698 screen:expect { 699 grid = [[ 700 #include <iostream> | 701 | 702 int {8:main}() | 703 { | 704 int {7:x}; | 705 #ifdef {5:__cplusplus} | 706 {4:std}::{2:cout} << {2:x} << "\n"; | 707 {6:#else} | 708 {6: printf("%d\n", x);} | 709 {6:#endif} | 710 } | 711 ^} | 712 {1:~ }|*3 713 | 714 ]], 715 } 716 717 exec_lua(function() 718 vim.lsp.semantic_tokens.force_refresh() 719 end) 720 721 screen:expect { 722 grid = [[ 723 #include <iostream> | 724 | 725 int {8:main}() | 726 { | 727 int {7:x}; | 728 #ifdef {5:__cplusplus} | 729 {4:std}::{2:cout} << {2:x} << "\n"; | 730 {6:#else} | 731 {6: printf("%d\n", x);} | 732 {6:#endif} | 733 } | 734 ^} | 735 {1:~ }|*3 736 | 737 ]], 738 unchanged = true, 739 } 740 741 local messages = exec_lua('return server.messages') 742 local token_request_count = 0 743 for _, message in 744 ipairs(messages --[[@as {method:string,params:table}[] ]]) 745 do 746 assert(message.method ~= 'textDocument/semanticTokens/full/delta', 'delta request received') 747 if message.method == 'textDocument/semanticTokens/full' then 748 token_request_count = token_request_count + 1 749 end 750 end 751 eq(2, token_request_count) 752 end) 753 754 it('destroys the highlighter if the buffer is deleted', function() 755 exec_lua(function() 756 vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) 757 end) 758 759 insert(text) 760 761 eq( 762 {}, 763 exec_lua(function() 764 local bufnr = vim.api.nvim_get_current_buf() 765 vim.api.nvim_buf_delete(bufnr, { force = true }) 766 return vim.lsp.semantic_tokens.__STHighlighter.active 767 end) 768 ) 769 end) 770 771 it('updates highlights with delta request on buffer change', function() 772 insert(text) 773 774 exec_lua(function() 775 vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) 776 end) 777 778 screen:expect { 779 grid = [[ 780 #include <iostream> | 781 | 782 int {8:main}() | 783 { | 784 int {7:x}; | 785 #ifdef {5:__cplusplus} | 786 {4:std}::{2:cout} << {2:x} << "\n"; | 787 {6:#else} | 788 {6: printf("%d\n", x);} | 789 {6:#endif} | 790 } | 791 ^} | 792 {1:~ }|*3 793 | 794 ]], 795 } 796 feed(':%s/int x/int x()/<CR>') 797 feed(':noh<CR>') 798 screen:expect { 799 grid = [[ 800 #include <iostream> | 801 | 802 int {8:main}() | 803 { | 804 ^int {8:x}(); | 805 #ifdef {5:__cplusplus} | 806 {4:std}::{2:cout} << {3:x} << "\n"; | 807 {6:#else} | 808 {6: printf("%d\n", x);} | 809 {6:#endif} | 810 } |*2 811 {1:~ }|*3 812 :noh | 813 ]], 814 } 815 end) 816 817 it( 818 'opt-out: does not activate semantic token highlighting if disabled in client attach', 819 function() 820 local client_id = exec_lua(function() 821 return vim.lsp.start({ 822 name = 'dummy', 823 cmd = _G.server.cmd, 824 --- @param client vim.lsp.Client 825 on_attach = vim.schedule_wrap(function(client, _bufnr) 826 client.server_capabilities.semanticTokensProvider = nil 827 end), 828 }) 829 end) 830 eq(true, exec_lua('return vim.lsp.buf_is_attached(0, ...)', client_id)) 831 832 insert(text) 833 834 screen:expect { 835 grid = [[ 836 #include <iostream> | 837 | 838 int main() | 839 { | 840 int x; | 841 #ifdef __cplusplus | 842 std::cout << x << "\n"; | 843 #else | 844 printf("%d\n", x); | 845 #endif | 846 } | 847 ^} | 848 {1:~ }|*3 849 | 850 ]], 851 } 852 853 screen:expect { 854 grid = [[ 855 #include <iostream> | 856 | 857 int main() | 858 { | 859 int x; | 860 #ifdef __cplusplus | 861 std::cout << x << "\n"; | 862 #else | 863 printf("%d\n", x); | 864 #endif | 865 } | 866 ^} | 867 {1:~ }|*3 868 | 869 ]], 870 unchanged = true, 871 } 872 end 873 ) 874 875 it('ignores null responses from the server', function() 876 local client_id = exec_lua(function() 877 _G.server2 = _G._create_server({ 878 capabilities = { 879 semanticTokensProvider = { 880 full = { delta = false }, 881 }, 882 }, 883 handlers = { 884 --- @param callback function 885 ['textDocument/semanticTokens/full'] = function(_, _, callback) 886 callback(nil, nil) 887 end, 888 --- @param callback function 889 ['textDocument/semanticTokens/full/delta'] = function(_, _, callback) 890 callback(nil, nil) 891 end, 892 }, 893 }) 894 return vim.lsp.start({ name = 'dummy', cmd = _G.server2.cmd }) 895 end) 896 eq( 897 true, 898 exec_lua(function() 899 return vim.lsp.buf_is_attached(0, client_id) 900 end) 901 ) 902 903 insert(text) 904 905 screen:expect { 906 grid = [[ 907 #include <iostream> | 908 | 909 int main() | 910 { | 911 int x; | 912 #ifdef __cplusplus | 913 std::cout << x << "\n"; | 914 #else | 915 printf("%d\n", x); | 916 #endif | 917 } | 918 ^} | 919 {1:~ }|*3 920 | 921 ]], 922 } 923 end) 924 925 it('resets active request after receiving error responses from the server', function() 926 local error = { code = -32801, message = 'Content modified' } 927 exec_lua(function() 928 _G.server2 = _G._create_server({ 929 capabilities = { 930 semanticTokensProvider = { 931 full = { delta = false }, 932 }, 933 }, 934 handlers = { 935 -- There is same logic for handling nil responses and error responses, 936 -- so keep responses not nil. 937 -- 938 -- if an error response was not be handled, this test will hang on here. 939 --- @param callback function 940 ['textDocument/semanticTokens/full'] = function(_, _, callback) 941 callback(error, vim.fn.json_decode(response)) 942 end, 943 --- @param callback function 944 ['textDocument/semanticTokens/full/delta'] = function(_, _, callback) 945 callback(error, vim.fn.json_decode(response)) 946 end, 947 }, 948 }) 949 return vim.lsp.start({ name = 'dummy', cmd = _G.server2.cmd }) 950 end) 951 screen:expect([[ 952 ^ | 953 {1:~ }|*14 954 | 955 ]]) 956 end) 957 958 it('does not send delta requests if not supported by server', function() 959 insert(text) 960 exec_lua(function() 961 _G.server2 = _G._create_server({ 962 capabilities = { 963 semanticTokensProvider = { 964 full = { delta = false }, 965 legend = vim.fn.json_decode(legend), 966 }, 967 }, 968 handlers = { 969 ['textDocument/semanticTokens/full'] = function(_, _, callback) 970 callback(nil, vim.fn.json_decode(response)) 971 end, 972 ['textDocument/semanticTokens/full/delta'] = function(_, _, callback) 973 callback(nil, vim.fn.json_decode(edit_response)) 974 end, 975 }, 976 }) 977 return vim.lsp.start({ name = 'dummy', cmd = _G.server2.cmd }) 978 end) 979 980 screen:expect { 981 grid = [[ 982 #include <iostream> | 983 | 984 int {8:main}() | 985 { | 986 int {7:x}; | 987 #ifdef {5:__cplusplus} | 988 {4:std}::{2:cout} << {2:x} << "\n"; | 989 {6:#else} | 990 {6: printf("%d\n", x);} | 991 {6:#endif} | 992 } | 993 ^} | 994 {1:~ }|*3 995 | 996 ]], 997 } 998 feed(':%s/int x/int x()/<CR>') 999 feed(':noh<CR>') 1000 1001 -- the highlights don't change because our fake server sent the exact 1002 -- same result for the same method (the full request). "x" would have 1003 -- changed to highlight index 3 had we sent a delta request 1004 screen:expect { 1005 grid = [[ 1006 #include <iostream> | 1007 | 1008 int {8:main}() | 1009 { | 1010 ^int {7:x}(); | 1011 #ifdef {5:__cplusplus} | 1012 {4:std}::{2:cout} << {2:x} << "\n"; | 1013 {6:#else} | 1014 {6: printf("%d\n", x);} | 1015 {6:#endif} | 1016 } |*2 1017 {1:~ }|*3 1018 :noh | 1019 ]], 1020 } 1021 local messages = exec_lua('return server2.messages') 1022 local token_request_count = 0 1023 for _, message in 1024 ipairs(messages --[[@as {method:string,params:table}[] ]]) 1025 do 1026 assert(message.method ~= 'textDocument/semanticTokens/full/delta', 'delta request received') 1027 if message.method == 'textDocument/semanticTokens/full' then 1028 token_request_count = token_request_count + 1 1029 end 1030 end 1031 eq(2, token_request_count) 1032 end) 1033 end) 1034 1035 describe('token array decoding', function() 1036 for _, test in ipairs({ 1037 { 1038 it = 'clangd-15 on C', 1039 text = [[char* foo = "\n";]], 1040 response = [[{"data": [0, 6, 3, 0, 8193], "resultId": "1"}]], 1041 legend = [[{ 1042 "tokenTypes": [ 1043 "variable", "variable", "parameter", "function", "method", "function", "property", "variable", "class", "interface", "enum", "enumMember", "type", "type", "unknown", "namespace", "typeParameter", "concept", "type", "macro", "comment" 1044 ], 1045 "tokenModifiers": [ 1046 "declaration", "deprecated", "deduced", "readonly", "static", "abstract", "virtual", "dependentName", "defaultLibrary", "usedAsMutableReference", "functionScope", "classScope", "fileScope", "globalScope" 1047 ] 1048 }]], 1049 expected = { 1050 { 1051 line = 0, 1052 end_line = 0, 1053 modifiers = { declaration = true, globalScope = true }, 1054 start_col = 6, 1055 end_col = 9, 1056 type = 'variable', 1057 marked = true, 1058 }, 1059 }, 1060 expected_screen = function() 1061 screen:expect { 1062 grid = [[ 1063 char* {7:foo} = "\n"^; | 1064 {1:~ }|*14 1065 | 1066 ]], 1067 } 1068 end, 1069 }, 1070 { 1071 it = 'clangd-15 on C++', 1072 text = [[#include <iostream> 1073 int main() 1074 { 1075 #ifdef __cplusplus 1076 const int x = 1; 1077 std::cout << x << std::endl; 1078 #else 1079 comment 1080 #endif 1081 }]], 1082 response = [[{"data": [1, 4, 4, 3, 8193, 2, 9, 11, 19, 8192, 1, 12, 1, 1, 1033, 1, 2, 3, 15, 8448, 0, 5, 4, 0, 8448, 0, 8, 1, 1, 1032, 0, 5, 3, 15, 8448, 0, 5, 4, 3, 8448, 1, 0, 7, 20, 0, 1, 0, 11, 20, 0, 1, 0, 8, 20, 0], "resultId": "1"}]], 1083 legend = [[{ 1084 "tokenTypes": [ 1085 "variable", "variable", "parameter", "function", "method", "function", "property", "variable", "class", "interface", "enum", "enumMember", "type", "type", "unknown", "namespace", "typeParameter", "concept", "type", "macro", "comment" 1086 ], 1087 "tokenModifiers": [ 1088 "declaration", "deprecated", "deduced", "readonly", "static", "abstract", "virtual", "dependentName", "defaultLibrary", "usedAsMutableReference", "functionScope", "classScope", "fileScope", "globalScope" 1089 ] 1090 }]], 1091 expected = { 1092 { -- main 1093 line = 1, 1094 end_line = 1, 1095 modifiers = { declaration = true, globalScope = true }, 1096 start_col = 4, 1097 end_col = 8, 1098 type = 'function', 1099 marked = true, 1100 }, 1101 { -- __cplusplus 1102 line = 3, 1103 end_line = 3, 1104 modifiers = { globalScope = true }, 1105 start_col = 9, 1106 end_col = 20, 1107 type = 'macro', 1108 marked = true, 1109 }, 1110 { -- x 1111 line = 4, 1112 end_line = 4, 1113 modifiers = { declaration = true, readonly = true, functionScope = true }, 1114 start_col = 12, 1115 end_col = 13, 1116 type = 'variable', 1117 marked = true, 1118 }, 1119 { -- std 1120 line = 5, 1121 end_line = 5, 1122 modifiers = { defaultLibrary = true, globalScope = true }, 1123 start_col = 2, 1124 end_col = 5, 1125 type = 'namespace', 1126 marked = true, 1127 }, 1128 { -- cout 1129 line = 5, 1130 end_line = 5, 1131 modifiers = { defaultLibrary = true, globalScope = true }, 1132 start_col = 7, 1133 end_col = 11, 1134 type = 'variable', 1135 marked = true, 1136 }, 1137 { -- x 1138 line = 5, 1139 end_line = 5, 1140 modifiers = { readonly = true, functionScope = true }, 1141 start_col = 15, 1142 end_col = 16, 1143 type = 'variable', 1144 marked = true, 1145 }, 1146 { -- std 1147 line = 5, 1148 end_line = 5, 1149 modifiers = { defaultLibrary = true, globalScope = true }, 1150 start_col = 20, 1151 end_col = 23, 1152 type = 'namespace', 1153 marked = true, 1154 }, 1155 { -- endl 1156 line = 5, 1157 end_line = 5, 1158 modifiers = { defaultLibrary = true, globalScope = true }, 1159 start_col = 25, 1160 end_col = 29, 1161 type = 'function', 1162 marked = true, 1163 }, 1164 { -- #else comment #endif 1165 line = 6, 1166 end_line = 6, 1167 modifiers = {}, 1168 start_col = 0, 1169 end_col = 7, 1170 type = 'comment', 1171 marked = true, 1172 }, 1173 { 1174 line = 7, 1175 end_line = 7, 1176 modifiers = {}, 1177 start_col = 0, 1178 end_col = 11, 1179 type = 'comment', 1180 marked = true, 1181 }, 1182 { 1183 line = 8, 1184 end_line = 8, 1185 modifiers = {}, 1186 start_col = 0, 1187 end_col = 8, 1188 type = 'comment', 1189 marked = true, 1190 }, 1191 }, 1192 expected_screen = function() 1193 screen:expect { 1194 grid = [[ 1195 #include <iostream> | 1196 int {8:main}() | 1197 { | 1198 #ifdef {5:__cplusplus} | 1199 const int {7:x} = 1; | 1200 {4:std}::{2:cout} << {2:x} << {4:std}::{3:endl}; | 1201 {6: #else} | 1202 {6: comment} | 1203 {6: #endif} | 1204 ^} | 1205 {1:~ }|*5 1206 | 1207 ]], 1208 } 1209 end, 1210 }, 1211 { 1212 it = 'sumneko_lua', 1213 text = [[-- comment 1214 local a = 1 1215 b = "as"]], 1216 response = [[{"data": [0, 0, 10, 17, 0, 1, 6, 1, 8, 1, 1, 0, 1, 8, 8]}]], 1217 legend = [[{ 1218 "tokenTypes": [ 1219 "namespace", "type", "class", "enum", "interface", "struct", "typeParameter", "parameter", "variable", "property", "enumMember", "event", "function", "method", "macro", "keyword", "modifier", "comment", "string", "number", "regexp", "operator" 1220 ], 1221 "tokenModifiers": [ 1222 "declaration", "definition", "readonly", "static", "deprecated", "abstract", "async", "modification", "documentation", "defaultLibrary" 1223 ] 1224 }]], 1225 expected = { 1226 { 1227 line = 0, 1228 end_line = 0, 1229 modifiers = {}, 1230 start_col = 0, 1231 end_col = 10, 1232 type = 'comment', -- comment 1233 marked = true, 1234 }, 1235 { 1236 line = 1, 1237 end_line = 1, 1238 modifiers = { declaration = true }, -- a 1239 start_col = 6, 1240 end_col = 7, 1241 type = 'variable', 1242 marked = true, 1243 }, 1244 { 1245 line = 2, 1246 end_line = 2, 1247 modifiers = { static = true }, -- b (global) 1248 start_col = 0, 1249 end_col = 1, 1250 type = 'variable', 1251 marked = true, 1252 }, 1253 }, 1254 expected_screen = function() 1255 screen:expect { 1256 grid = [[ 1257 {6:-- comment} | 1258 local {7:a} = 1 | 1259 {2:b} = "as^" | 1260 {1:~ }|*12 1261 | 1262 ]], 1263 } 1264 end, 1265 }, 1266 { 1267 it = 'rust-analyzer', 1268 text = [[pub fn main() { 1269 println!("Hello world!"); 1270 break rust; 1271 /// what? 1272 } 1273 ]], 1274 response = [[{"data": [0, 0, 3, 1, 0, 0, 4, 2, 1, 0, 0, 3, 4, 14, 524290, 0, 4, 1, 45, 0, 0, 1, 1, 45, 0, 0, 2, 1, 26, 0, 1, 4, 8, 17, 0, 0, 8, 1, 45, 0, 0, 1, 14, 2, 0, 0, 14, 1, 45, 0, 0, 1, 1, 48, 0, 1, 4, 5, 1, 8192, 0, 6, 4, 52, 0, 0, 4, 1, 48, 0, 1, 4, 9, 0, 1, 1, 0, 1, 26, 0 ], "resultId": "1"}]], 1275 1276 legend = [[{ 1277 "tokenTypes": [ 1278 "comment", "keyword", "string", "number", "regexp", "operator", "namespace", "type", "struct", "class", "interface", "enum", "enumMember", "typeParameter", "function", "method", "property", "macro", "variable", 1279 "parameter", "angle", "arithmetic", "attribute", "attributeBracket", "bitwise", "boolean", "brace", "bracket", "builtinAttribute", "builtinType", "character", "colon", "comma", "comparison", "constParameter", "derive", 1280 "dot", "escapeSequence", "formatSpecifier", "generic", "label", "lifetime", "logical", "macroBang", "operator", "parenthesis", "punctuation", "selfKeyword", "semicolon", "typeAlias", "toolModule", "union", "unresolvedReference" 1281 ], 1282 "tokenModifiers": [ 1283 "documentation", "declaration", "definition", "static", "abstract", "deprecated", "readonly", "defaultLibrary", "async", "attribute", "callable", "constant", "consuming", "controlFlow", "crateRoot", "injected", "intraDocLink", 1284 "library", "mutable", "public", "reference", "trait", "unsafe" 1285 ] 1286 }]], 1287 expected = { 1288 { 1289 line = 0, 1290 end_line = 0, 1291 modifiers = {}, 1292 start_col = 0, 1293 end_col = 3, -- pub 1294 type = 'keyword', 1295 marked = true, 1296 }, 1297 { 1298 line = 0, 1299 end_line = 0, 1300 modifiers = {}, 1301 start_col = 4, 1302 end_col = 6, -- fn 1303 type = 'keyword', 1304 marked = true, 1305 }, 1306 { 1307 line = 0, 1308 end_line = 0, 1309 modifiers = { declaration = true, public = true }, 1310 start_col = 7, 1311 end_col = 11, -- main 1312 type = 'function', 1313 marked = true, 1314 }, 1315 { 1316 line = 0, 1317 end_line = 0, 1318 modifiers = {}, 1319 start_col = 11, 1320 end_col = 12, 1321 type = 'parenthesis', 1322 marked = true, 1323 }, 1324 { 1325 line = 0, 1326 end_line = 0, 1327 modifiers = {}, 1328 start_col = 12, 1329 end_col = 13, 1330 type = 'parenthesis', 1331 marked = true, 1332 }, 1333 { 1334 line = 0, 1335 end_line = 0, 1336 modifiers = {}, 1337 start_col = 14, 1338 end_col = 15, 1339 type = 'brace', 1340 marked = true, 1341 }, 1342 { 1343 line = 1, 1344 end_line = 1, 1345 modifiers = {}, 1346 start_col = 4, 1347 end_col = 12, 1348 type = 'macro', -- println! 1349 marked = true, 1350 }, 1351 { 1352 line = 1, 1353 end_line = 1, 1354 modifiers = {}, 1355 start_col = 12, 1356 end_col = 13, 1357 type = 'parenthesis', 1358 marked = true, 1359 }, 1360 { 1361 line = 1, 1362 end_line = 1, 1363 modifiers = {}, 1364 start_col = 13, 1365 end_col = 27, 1366 type = 'string', -- "Hello world!" 1367 marked = true, 1368 }, 1369 { 1370 line = 1, 1371 end_line = 1, 1372 modifiers = {}, 1373 start_col = 27, 1374 end_col = 28, 1375 type = 'parenthesis', 1376 marked = true, 1377 }, 1378 { 1379 line = 1, 1380 end_line = 1, 1381 modifiers = {}, 1382 start_col = 28, 1383 end_col = 29, 1384 type = 'semicolon', 1385 marked = true, 1386 }, 1387 { 1388 line = 2, 1389 end_line = 2, 1390 modifiers = { controlFlow = true }, 1391 start_col = 4, 1392 end_col = 9, -- break 1393 type = 'keyword', 1394 marked = true, 1395 }, 1396 { 1397 line = 2, 1398 end_line = 2, 1399 modifiers = {}, 1400 start_col = 10, 1401 end_col = 14, -- rust 1402 type = 'unresolvedReference', 1403 marked = true, 1404 }, 1405 { 1406 line = 2, 1407 end_line = 2, 1408 modifiers = {}, 1409 start_col = 14, 1410 end_col = 15, 1411 type = 'semicolon', 1412 marked = true, 1413 }, 1414 { 1415 line = 3, 1416 end_line = 3, 1417 modifiers = { documentation = true }, 1418 start_col = 4, 1419 end_col = 13, 1420 type = 'comment', -- /// what? 1421 marked = true, 1422 }, 1423 { 1424 line = 4, 1425 end_line = 4, 1426 modifiers = {}, 1427 start_col = 0, 1428 end_col = 1, 1429 type = 'brace', 1430 marked = true, 1431 }, 1432 }, 1433 expected_screen = function() 1434 screen:expect { 1435 grid = [[ 1436 {10:pub} {10:fn} {8:main}() { | 1437 {5:println!}({11:"Hello world!"}); | 1438 {10:break} rust; | 1439 {6:/// what?} | 1440 } | 1441 ^ | 1442 {1:~ }|*9 1443 | 1444 ]], 1445 } 1446 end, 1447 }, 1448 }) do 1449 it(test.it, function() 1450 exec_lua(create_server_definition) 1451 local client_id = exec_lua(function(legend, resp) 1452 _G.server = _G._create_server({ 1453 capabilities = { 1454 semanticTokensProvider = { 1455 full = { delta = false }, 1456 legend = vim.fn.json_decode(legend), 1457 }, 1458 }, 1459 handlers = { 1460 ['textDocument/semanticTokens/full'] = function(_, _, callback) 1461 callback(nil, vim.fn.json_decode(resp)) 1462 end, 1463 }, 1464 }) 1465 return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) 1466 end, test.legend, test.response) 1467 1468 insert(test.text) 1469 1470 test.expected_screen() 1471 1472 eq( 1473 test.expected, 1474 exec_lua(function() 1475 local bufnr = vim.api.nvim_get_current_buf() 1476 return vim.lsp.semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights 1477 end) 1478 ) 1479 end) 1480 end 1481 end) 1482 1483 describe('token decoding with deltas', function() 1484 for _, test in ipairs({ 1485 { 1486 it = 'semantic_tokens_delta: clangd-15 on C', 1487 legend = [[{ 1488 "tokenTypes": [ 1489 "variable", "variable", "parameter", "function", "method", "function", "property", "variable", "class", "interface", "enum", "enumMember", "type", "type", "unknown", "namespace", "typeParameter", "concept", "type", "macro", "comment" 1490 ], 1491 "tokenModifiers": [ 1492 "declaration", "deprecated", "deduced", "readonly", "static", "abstract", "virtual", "dependentName", "defaultLibrary", "usedAsMutableReference", "functionScope", "classScope", "fileScope", "globalScope" 1493 ] 1494 }]], 1495 text1 = [[char* foo = "\n";]], 1496 edit = [[ggO<Esc>]], 1497 response1 = [[{"data": [0, 6, 3, 0, 8193], "resultId": "1"}]], 1498 response2 = [[{"edits": [{ "start": 0, "deleteCount": 1, "data": [1] }], "resultId": "2"}]], 1499 expected1 = { 1500 { 1501 line = 0, 1502 modifiers = { 1503 declaration = true, 1504 globalScope = true, 1505 }, 1506 start_col = 6, 1507 end_line = 0, 1508 end_col = 9, 1509 type = 'variable', 1510 marked = true, 1511 }, 1512 }, 1513 expected2 = { 1514 { 1515 line = 1, 1516 modifiers = { 1517 declaration = true, 1518 globalScope = true, 1519 }, 1520 start_col = 6, 1521 end_line = 1, 1522 end_col = 9, 1523 type = 'variable', 1524 marked = true, 1525 }, 1526 }, 1527 expected_screen1 = function() 1528 screen:expect { 1529 grid = [[ 1530 char* {7:foo} = "\n"^; | 1531 {1:~ }|*14 1532 | 1533 ]], 1534 } 1535 end, 1536 expected_screen2 = function() 1537 screen:expect { 1538 grid = [[ 1539 ^ | 1540 char* {7:foo} = "\n"; | 1541 {1:~ }|*13 1542 | 1543 ]], 1544 } 1545 end, 1546 }, 1547 { 1548 it = 'response with multiple delta edits', 1549 legend = [[{ 1550 "tokenTypes": [ 1551 "variable", "variable", "parameter", "function", "method", "function", "property", "variable", "class", "interface", "enum", "enumMember", "type", "type", "unknown", "namespace", "typeParameter", "concept", "type", "macro", "comment" 1552 ], 1553 "tokenModifiers": [ 1554 "declaration", "deprecated", "deduced", "readonly", "static", "abstract", "virtual", "dependentName", "defaultLibrary", "usedAsMutableReference", "functionScope", "classScope", "fileScope", "globalScope" 1555 ] 1556 }]], 1557 text1 = dedent([[ 1558 #include <iostream> 1559 1560 int main() 1561 { 1562 int x; 1563 #ifdef __cplusplus 1564 std::cout << x << "\n"; 1565 #else 1566 printf("%d\n", x); 1567 #endif 1568 }]]), 1569 text2 = [[#include <iostream> 1570 1571 int main() 1572 { 1573 int x(); 1574 double y; 1575 #ifdef __cplusplus 1576 std::cout << x << "\n"; 1577 #else 1578 printf("%d\n", x); 1579 #endif 1580 }]], 1581 response1 = [[{ 1582 "data": [ 2, 4, 4, 3, 8193, 2, 8, 1, 1, 1025, 1, 7, 11, 19, 8192, 1, 4, 3, 15, 8448, 0, 5, 4, 0, 8448, 0, 8, 1, 1, 1024, 1, 0, 5, 20, 0, 1, 0, 22, 20, 0, 1, 0, 6, 20, 0 ], 1583 "resultId": 1 1584 }]], 1585 response2 = [[{ 1586 "edits": [ {"data": [ 2, 8, 1, 3, 8193, 1, 11, 1, 1, 1025 ], "deleteCount": 5, "start": 5}, {"data": [ 0, 8, 1, 3, 8192 ], "deleteCount": 5, "start": 25 } ], 1587 "resultId":"2" 1588 }]], 1589 expected1 = { 1590 { 1591 line = 2, 1592 end_line = 2, 1593 start_col = 4, 1594 end_col = 8, 1595 modifiers = { declaration = true, globalScope = true }, 1596 type = 'function', 1597 marked = true, 1598 }, 1599 { 1600 line = 4, 1601 end_line = 4, 1602 start_col = 8, 1603 end_col = 9, 1604 modifiers = { declaration = true, functionScope = true }, 1605 type = 'variable', 1606 marked = true, 1607 }, 1608 { 1609 line = 5, 1610 end_line = 5, 1611 start_col = 7, 1612 end_col = 18, 1613 modifiers = { globalScope = true }, 1614 type = 'macro', 1615 marked = true, 1616 }, 1617 { 1618 line = 6, 1619 end_line = 6, 1620 start_col = 4, 1621 end_col = 7, 1622 modifiers = { defaultLibrary = true, globalScope = true }, 1623 type = 'namespace', 1624 marked = true, 1625 }, 1626 { 1627 line = 6, 1628 end_line = 6, 1629 start_col = 9, 1630 end_col = 13, 1631 modifiers = { defaultLibrary = true, globalScope = true }, 1632 type = 'variable', 1633 marked = true, 1634 }, 1635 { 1636 line = 6, 1637 end_line = 6, 1638 start_col = 17, 1639 end_col = 18, 1640 marked = true, 1641 modifiers = { functionScope = true }, 1642 type = 'variable', 1643 }, 1644 { 1645 line = 7, 1646 end_line = 7, 1647 start_col = 0, 1648 end_col = 5, 1649 marked = true, 1650 modifiers = {}, 1651 type = 'comment', 1652 }, 1653 { 1654 line = 8, 1655 end_line = 8, 1656 end_col = 22, 1657 modifiers = {}, 1658 start_col = 0, 1659 type = 'comment', 1660 marked = true, 1661 }, 1662 { 1663 line = 9, 1664 end_line = 9, 1665 start_col = 0, 1666 end_col = 6, 1667 modifiers = {}, 1668 type = 'comment', 1669 marked = true, 1670 }, 1671 }, 1672 expected2 = { 1673 { 1674 line = 2, 1675 end_line = 2, 1676 start_col = 4, 1677 end_col = 8, 1678 modifiers = { declaration = true, globalScope = true }, 1679 type = 'function', 1680 marked = true, 1681 }, 1682 { 1683 line = 4, 1684 end_line = 4, 1685 start_col = 8, 1686 end_col = 9, 1687 modifiers = { declaration = true, globalScope = true }, 1688 type = 'function', 1689 marked = true, 1690 }, 1691 { 1692 line = 5, 1693 end_line = 5, 1694 end_col = 12, 1695 start_col = 11, 1696 modifiers = { declaration = true, functionScope = true }, 1697 type = 'variable', 1698 marked = true, 1699 }, 1700 { 1701 line = 6, 1702 end_line = 6, 1703 start_col = 7, 1704 end_col = 18, 1705 modifiers = { globalScope = true }, 1706 type = 'macro', 1707 marked = true, 1708 }, 1709 { 1710 line = 7, 1711 end_line = 7, 1712 start_col = 4, 1713 end_col = 7, 1714 modifiers = { defaultLibrary = true, globalScope = true }, 1715 type = 'namespace', 1716 marked = true, 1717 }, 1718 { 1719 line = 7, 1720 end_line = 7, 1721 start_col = 9, 1722 end_col = 13, 1723 modifiers = { defaultLibrary = true, globalScope = true }, 1724 type = 'variable', 1725 marked = true, 1726 }, 1727 { 1728 line = 7, 1729 end_line = 7, 1730 start_col = 17, 1731 end_col = 18, 1732 marked = true, 1733 modifiers = { globalScope = true }, 1734 type = 'function', 1735 }, 1736 { 1737 line = 8, 1738 end_line = 8, 1739 start_col = 0, 1740 end_col = 5, 1741 marked = true, 1742 modifiers = {}, 1743 type = 'comment', 1744 }, 1745 { 1746 line = 9, 1747 end_line = 9, 1748 end_col = 22, 1749 modifiers = {}, 1750 start_col = 0, 1751 type = 'comment', 1752 marked = true, 1753 }, 1754 { 1755 line = 10, 1756 end_line = 10, 1757 start_col = 0, 1758 end_col = 6, 1759 modifiers = {}, 1760 type = 'comment', 1761 marked = true, 1762 }, 1763 }, 1764 expected_screen1 = function() 1765 screen:expect { 1766 grid = [[ 1767 #include <iostream> | 1768 | 1769 int {8:main}() | 1770 { | 1771 int {7:x}; | 1772 #ifdef {5:__cplusplus} | 1773 {4:std}::{2:cout} << {2:x} << "\n"; | 1774 {6:#else} | 1775 {6: printf("%d\n", x);} | 1776 {6:#endif} | 1777 ^} | 1778 {1:~ }|*4 1779 | 1780 ]], 1781 } 1782 end, 1783 expected_screen2 = function() 1784 screen:expect { 1785 grid = [[ 1786 #include <iostream> | 1787 | 1788 int {8:main}() | 1789 { | 1790 int {8:x}(); | 1791 double {7:y}; | 1792 #ifdef {5:__cplusplus} | 1793 {4:std}::{2:cout} << {3:x} << "\n"; | 1794 {6:#else} | 1795 {6: printf("%d\n", x);} | 1796 {6:^#endif} | 1797 } | 1798 {1:~ }|*3 1799 | 1800 ]], 1801 } 1802 end, 1803 }, 1804 { 1805 it = 'optional token_edit.data on deletion', 1806 legend = [[{ 1807 "tokenTypes": [ 1808 "comment", "keyword", "operator", "string", "number", "regexp", "type", "class", "interface", "enum", "enumMember", "typeParameter", "function", "method", "property", "variable", "parameter", "module", "intrinsic", "selfParameter", "clsParameter", "magicFunction", "builtinConstant", "parenthesis", "curlybrace", "bracket", "colon", "semicolon", "arrow" 1809 ], 1810 "tokenModifiers": [ 1811 "declaration", "static", "abstract", "async", "documentation", "typeHint", "typeHintComment", "readonly", "decorator", "builtin" 1812 ] 1813 }]], 1814 text1 = [[string = "test"]], 1815 text2 = [[]], 1816 response1 = [[{"data": [0, 0, 6, 15, 1], "resultId": "1"}]], 1817 response2 = [[{"edits": [{ "start": 0, "deleteCount": 5 }], "resultId": "2"}]], 1818 expected1 = { 1819 { 1820 line = 0, 1821 end_line = 0, 1822 modifiers = { 1823 declaration = true, 1824 }, 1825 start_col = 0, 1826 end_col = 6, 1827 type = 'variable', 1828 marked = true, 1829 }, 1830 }, 1831 expected2 = {}, 1832 expected_screen1 = function() 1833 screen:expect { 1834 grid = [[ 1835 {7:string} = "test^" | 1836 {1:~ }|*14 1837 | 1838 ]], 1839 } 1840 end, 1841 expected_screen2 = function() 1842 screen:expect { 1843 grid = [[ 1844 ^ | 1845 {1:~ }|*14 1846 | 1847 ]], 1848 } 1849 end, 1850 }, 1851 }) do 1852 it(test.it, function() 1853 local bufnr = n.api.nvim_get_current_buf() 1854 insert(test.text1) 1855 exec_lua(create_server_definition) 1856 local client_id = exec_lua(function(legend, resp1, resp2) 1857 _G.server = _G._create_server({ 1858 capabilities = { 1859 semanticTokensProvider = { 1860 full = { delta = true }, 1861 legend = vim.fn.json_decode(legend), 1862 }, 1863 }, 1864 handlers = { 1865 ['textDocument/semanticTokens/full'] = function(_, _, callback) 1866 callback(nil, vim.fn.json_decode(resp1)) 1867 end, 1868 ['textDocument/semanticTokens/full/delta'] = function(_, _, callback) 1869 callback(nil, vim.fn.json_decode(resp2)) 1870 end, 1871 }, 1872 }) 1873 local client_id = assert(vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })) 1874 1875 -- speed up vim.api.nvim_buf_set_lines calls by changing debounce to 10 for these tests 1876 vim.schedule(function() 1877 vim.lsp.semantic_tokens._start(bufnr, client_id, 10) 1878 end) 1879 return client_id 1880 end, test.legend, test.response1, test.response2) 1881 1882 test.expected_screen1() 1883 1884 eq( 1885 test.expected1, 1886 exec_lua(function() 1887 return vim.lsp.semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights 1888 end) 1889 ) 1890 1891 if test.edit then 1892 feed(test.edit) 1893 else 1894 exec_lua(function(text) 1895 vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, vim.fn.split(text, '\n')) 1896 vim.wait(15) -- wait for debounce 1897 end, test.text2) 1898 end 1899 1900 test.expected_screen2() 1901 1902 eq( 1903 test.expected2, 1904 exec_lua(function() 1905 return vim.lsp.semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights 1906 end) 1907 ) 1908 end) 1909 end 1910 end) 1911 end)