fake-lsp-server.lua (27000B)
1 local protocol = require 'vim.lsp.protocol' 2 3 local pid = vim.uv.os_getpid() 4 local stdin = '' 5 vim.fn.stdioopen({ 6 on_stdin = function(_, data, _) 7 stdin = stdin .. table.concat(data, '\n') 8 end, 9 }) 10 11 -- Logs to $NVIM_LOG_FILE. 12 -- 13 -- TODO(justinmk): remove after https://github.com/neovim/neovim/pull/7062 14 local function log(loglevel, area, msg) 15 vim.fn.writefile( 16 { string.format('%d %s %s: %s', pid, loglevel, area, msg) }, 17 vim.env.NVIM_LOG_FILE, 18 'a' 19 ) 20 end 21 22 local function message_parts(sep, ...) 23 local parts = {} 24 for i = 1, select('#', ...) do 25 local arg = select(i, ...) 26 if arg ~= nil then 27 table.insert(parts, arg) 28 end 29 end 30 return table.concat(parts, sep) 31 end 32 33 -- Assert utility methods 34 35 local function assert_eq(a, b, ...) 36 if not vim.deep_equal(a, b) then 37 error( 38 message_parts( 39 ': ', 40 ..., 41 'assert_eq failed', 42 string.format( 43 'left == %q, right == %q', 44 table.concat(vim.split(vim.inspect(a), '\n'), ''), 45 table.concat(vim.split(vim.inspect(b), '\n'), '') 46 ) 47 ) 48 ) 49 end 50 end 51 52 local function format_message_with_content_length(encoded_message) 53 return table.concat { 54 'Content-Length: ', 55 tostring(#encoded_message), 56 '\r\n\r\n', 57 encoded_message, 58 } 59 end 60 61 local function read_line() 62 vim.wait(math.huge, function() 63 return stdin:find('\n') ~= nil 64 end, 1) 65 local eol = assert(stdin:find('\n')) 66 local line = stdin:sub(1, eol - 1) 67 stdin = stdin:sub(eol + 1) 68 return line 69 end 70 71 --- @param len integer 72 local function read_len(len) 73 vim.wait(math.huge, function() 74 return stdin:len() >= len 75 end, 1) 76 local content = stdin:sub(1, len) 77 stdin = stdin:sub(len + 1) 78 return content 79 end 80 81 local function read_message() 82 local line = read_line() 83 local length = line:lower():match('content%-length:%s*(%d+)') 84 return vim.json.decode(read_len(2 + length):sub(2)) 85 end 86 87 local function send(payload) 88 io.stdout:write(format_message_with_content_length(vim.json.encode(payload))) 89 end 90 91 local function respond(id, err, result) 92 assert(type(id) == 'number', 'id must be a number') 93 send { jsonrpc = '2.0', id = id, error = err, result = result } 94 end 95 96 local function notify(method, params) 97 assert(type(method) == 'string', 'method must be a string') 98 send { method = method, params = params or {} } 99 end 100 101 local function expect_notification(method, params, ...) 102 local message = read_message() 103 assert_eq(method, message.method, ..., 'expect_notification', 'method') 104 if params then 105 assert_eq(params, message.params, ..., 'expect_notification', method, 'params') 106 assert_eq( 107 { jsonrpc = '2.0', method = method, params = params }, 108 message, 109 ..., 110 'expect_notification', 111 'message' 112 ) 113 end 114 end 115 116 local function expect_request(method, handler, ...) 117 local req = read_message() 118 assert_eq(method, req.method, ..., 'expect_request', 'method') 119 local err, result = handler(req.params) 120 respond(req.id, err, result) 121 end 122 123 io.stderr:setvbuf('no') 124 125 local function skeleton(config) 126 local on_init = assert(config.on_init) 127 local body = assert(config.body) 128 expect_request('initialize', function(params) 129 return nil, on_init(params) 130 end) 131 expect_notification('initialized', {}) 132 body() 133 expect_request('shutdown', function() 134 return nil, {} 135 end) 136 expect_notification('exit', nil) 137 end 138 139 -- The actual tests. 140 141 local tests = {} 142 143 function tests.basic_init() 144 skeleton { 145 on_init = function(params) 146 assert_eq(params.workDoneToken, '1') 147 return { 148 capabilities = { 149 textDocumentSync = protocol.TextDocumentSyncKind.None, 150 }, 151 } 152 end, 153 body = function() 154 notify('test') 155 end, 156 } 157 end 158 159 function tests.basic_init_did_change_configuration() 160 skeleton({ 161 on_init = function(_) 162 return { 163 capabilities = {}, 164 } 165 end, 166 body = function() 167 expect_notification('workspace/didChangeConfiguration', { settings = { dummy = 1 } }) 168 end, 169 }) 170 end 171 172 function tests.check_workspace_configuration() 173 skeleton { 174 on_init = function(_params) 175 return { capabilities = {} } 176 end, 177 body = function() 178 notify('start') 179 notify('workspace/configuration', { 180 items = { 181 { section = 'testSetting1' }, 182 { section = 'testSetting2' }, 183 { section = 'test.Setting3' }, 184 { section = 'test.Setting4' }, 185 {}, 186 { section = '' }, 187 }, 188 }) 189 local all = { 190 testSetting1 = true, 191 testSetting2 = false, 192 test = { Setting3 = 'nested' }, 193 } 194 expect_notification('workspace/configuration', { true, false, 'nested', vim.NIL, all, all }) 195 notify('shutdown') 196 end, 197 } 198 end 199 200 function tests.prepare_rename_nil() 201 skeleton { 202 on_init = function() 203 return { 204 capabilities = { 205 renameProvider = { 206 prepareProvider = true, 207 }, 208 }, 209 } 210 end, 211 body = function() 212 notify('start') 213 expect_request('textDocument/prepareRename', function() 214 return {}, nil 215 end) 216 notify('shutdown') 217 end, 218 } 219 end 220 221 function tests.prepare_rename_placeholder() 222 skeleton { 223 on_init = function() 224 return { 225 capabilities = { 226 renameProvider = { 227 prepareProvider = true, 228 }, 229 }, 230 } 231 end, 232 body = function() 233 notify('start') 234 expect_request('textDocument/prepareRename', function() 235 return nil, { placeholder = 'placeholder' } 236 end) 237 expect_request('textDocument/rename', function(params) 238 assert_eq(params.newName, 'renameto') 239 return {}, nil 240 end) 241 notify('shutdown') 242 end, 243 } 244 end 245 246 function tests.prepare_rename_range() 247 skeleton { 248 on_init = function() 249 return { 250 capabilities = { 251 renameProvider = { 252 prepareProvider = true, 253 }, 254 }, 255 } 256 end, 257 body = function() 258 notify('start') 259 expect_request('textDocument/prepareRename', function() 260 return nil, 261 { 262 start = { line = 1, character = 8 }, 263 ['end'] = { line = 1, character = 12 }, 264 } 265 end) 266 expect_request('textDocument/rename', function(params) 267 assert_eq(params.newName, 'renameto') 268 return {}, nil 269 end) 270 notify('shutdown') 271 end, 272 } 273 end 274 275 function tests.prepare_rename_error() 276 skeleton { 277 on_init = function() 278 return { 279 capabilities = { 280 renameProvider = { 281 prepareProvider = true, 282 }, 283 }, 284 } 285 end, 286 body = function() 287 notify('start') 288 expect_request('textDocument/prepareRename', function() 289 return {}, nil 290 end) 291 notify('shutdown') 292 end, 293 } 294 end 295 296 function tests.basic_check_capabilities() 297 skeleton { 298 on_init = function(params) 299 local expected_capabilities = protocol.make_client_capabilities() 300 assert_eq(params.capabilities, expected_capabilities) 301 return { 302 capabilities = { 303 textDocumentSync = protocol.TextDocumentSyncKind.Full, 304 codeLensProvider = false, 305 }, 306 } 307 end, 308 body = function() end, 309 } 310 end 311 312 function tests.text_document_save_did_open() 313 skeleton { 314 on_init = function() 315 return { 316 capabilities = { 317 textDocumentSync = { 318 save = true, 319 }, 320 }, 321 } 322 end, 323 body = function() 324 notify('start') 325 expect_notification('textDocument/didClose') 326 expect_notification('textDocument/didOpen') 327 expect_notification('textDocument/didSave') 328 notify('shutdown') 329 end, 330 } 331 end 332 333 function tests.text_document_sync_save_bool() 334 skeleton { 335 on_init = function() 336 return { 337 capabilities = { 338 textDocumentSync = { 339 save = true, 340 }, 341 }, 342 } 343 end, 344 body = function() 345 notify('start') 346 expect_notification('textDocument/didSave', { textDocument = { uri = 'file://' } }) 347 notify('shutdown') 348 end, 349 } 350 end 351 352 function tests.text_document_sync_save_includeText() 353 skeleton { 354 on_init = function() 355 return { 356 capabilities = { 357 textDocumentSync = { 358 save = { 359 includeText = true, 360 }, 361 }, 362 }, 363 } 364 end, 365 body = function() 366 notify('start') 367 expect_notification('textDocument/didSave', { 368 textDocument = { 369 uri = 'file://', 370 }, 371 text = 'help me\n', 372 }) 373 notify('shutdown') 374 end, 375 } 376 end 377 378 function tests.capabilities_for_client_supports_method() 379 skeleton { 380 on_init = function(params) 381 local expected_capabilities = protocol.make_client_capabilities() 382 assert_eq(params.capabilities, expected_capabilities) 383 return { 384 capabilities = { 385 textDocumentSync = protocol.TextDocumentSyncKind.Full, 386 completionProvider = true, 387 hoverProvider = true, 388 renameProvider = false, 389 definitionProvider = false, 390 referencesProvider = false, 391 codeLensProvider = { resolveProvider = true }, 392 }, 393 } 394 end, 395 body = function() end, 396 } 397 end 398 399 function tests.check_forward_request_cancelled() 400 skeleton { 401 on_init = function(_) 402 return { capabilities = {} } 403 end, 404 body = function() 405 expect_request('error_code_test', function() 406 return { code = -32800 }, nil, { method = 'error_code_test', client_id = 1 } 407 end) 408 notify('finish') 409 end, 410 } 411 end 412 413 function tests.check_forward_content_modified() 414 skeleton { 415 on_init = function(_) 416 return { capabilities = {} } 417 end, 418 body = function() 419 expect_request('error_code_test', function() 420 return { code = -32801 }, nil, { method = 'error_code_test', client_id = 1 } 421 end) 422 expect_notification('finish') 423 notify('finish') 424 end, 425 } 426 end 427 428 function tests.check_forward_server_cancelled() 429 skeleton { 430 on_init = function() 431 return { capabilities = {} } 432 end, 433 body = function() 434 expect_request('error_code_test', function() 435 return { code = -32802 }, nil, { method = 'error_code_test', client_id = 1 } 436 end) 437 expect_notification('finish') 438 notify('finish') 439 end, 440 } 441 end 442 443 function tests.check_pending_request_tracked() 444 skeleton { 445 on_init = function(_) 446 return { capabilities = {} } 447 end, 448 body = function() 449 local msg = read_message() 450 assert_eq('slow_request', msg.method) 451 expect_notification('release') 452 respond(msg.id, nil, {}) 453 expect_notification('finish') 454 notify('finish') 455 end, 456 } 457 end 458 459 function tests.check_cancel_request_tracked() 460 skeleton { 461 on_init = function(_) 462 return { capabilities = {} } 463 end, 464 body = function() 465 local msg = read_message() 466 assert_eq('slow_request', msg.method) 467 expect_notification('$/cancelRequest', { id = msg.id }) 468 expect_notification('release') 469 respond(msg.id, { code = -32800 }, nil) 470 notify('finish') 471 end, 472 } 473 end 474 475 function tests.check_tracked_requests_cleared() 476 skeleton { 477 on_init = function(_) 478 return { capabilities = {} } 479 end, 480 body = function() 481 local msg = read_message() 482 assert_eq('slow_request', msg.method) 483 expect_notification('$/cancelRequest', { id = msg.id }) 484 expect_notification('release') 485 respond(msg.id, nil, {}) 486 expect_notification('finish') 487 notify('finish') 488 end, 489 } 490 end 491 492 function tests.basic_finish() 493 skeleton { 494 on_init = function(params) 495 local expected_capabilities = protocol.make_client_capabilities() 496 assert_eq(params.capabilities, expected_capabilities) 497 return { 498 capabilities = { 499 textDocumentSync = protocol.TextDocumentSyncKind.Full, 500 }, 501 } 502 end, 503 body = function() 504 expect_notification('finish') 505 notify('finish') 506 end, 507 } 508 end 509 510 function tests.basic_check_buffer_open() 511 skeleton { 512 on_init = function(params) 513 local expected_capabilities = protocol.make_client_capabilities() 514 assert_eq(params.capabilities, expected_capabilities) 515 return { 516 capabilities = { 517 textDocumentSync = protocol.TextDocumentSyncKind.Full, 518 }, 519 } 520 end, 521 body = function() 522 notify('start') 523 expect_notification('textDocument/didOpen', { 524 textDocument = { 525 languageId = '', 526 text = table.concat({ 'testing', '123' }, '\n') .. '\n', 527 uri = 'file://', 528 version = 0, 529 }, 530 }) 531 expect_notification('finish') 532 notify('finish') 533 end, 534 } 535 end 536 537 function tests.basic_check_buffer_open_and_change() 538 skeleton { 539 on_init = function(params) 540 local expected_capabilities = protocol.make_client_capabilities() 541 assert_eq(params.capabilities, expected_capabilities) 542 return { 543 capabilities = { 544 textDocumentSync = protocol.TextDocumentSyncKind.Full, 545 }, 546 } 547 end, 548 body = function() 549 notify('start') 550 expect_notification('textDocument/didOpen', { 551 textDocument = { 552 languageId = '', 553 text = table.concat({ 'testing', '123' }, '\n') .. '\n', 554 uri = 'file://', 555 version = 0, 556 }, 557 }) 558 expect_notification('textDocument/didChange', { 559 textDocument = { 560 uri = 'file://', 561 version = 3, 562 }, 563 contentChanges = { 564 { text = table.concat({ 'testing', 'boop' }, '\n') .. '\n' }, 565 }, 566 }) 567 expect_notification('finish') 568 notify('finish') 569 end, 570 } 571 end 572 573 function tests.basic_check_buffer_open_and_change_noeol() 574 skeleton { 575 on_init = function(params) 576 local expected_capabilities = protocol.make_client_capabilities() 577 assert_eq(params.capabilities, expected_capabilities) 578 return { 579 capabilities = { 580 textDocumentSync = protocol.TextDocumentSyncKind.Full, 581 }, 582 } 583 end, 584 body = function() 585 notify('start') 586 expect_notification('textDocument/didOpen', { 587 textDocument = { 588 languageId = '', 589 text = table.concat({ 'testing', '123' }, '\n'), 590 uri = 'file://', 591 version = 0, 592 }, 593 }) 594 expect_notification('textDocument/didChange', { 595 textDocument = { 596 uri = 'file://', 597 version = 3, 598 }, 599 contentChanges = { 600 { text = table.concat({ 'testing', 'boop' }, '\n') }, 601 }, 602 }) 603 expect_notification('finish') 604 notify('finish') 605 end, 606 } 607 end 608 function tests.basic_check_buffer_open_and_change_multi() 609 skeleton { 610 on_init = function(params) 611 local expected_capabilities = protocol.make_client_capabilities() 612 assert_eq(params.capabilities, expected_capabilities) 613 return { 614 capabilities = { 615 textDocumentSync = protocol.TextDocumentSyncKind.Full, 616 }, 617 } 618 end, 619 body = function() 620 notify('start') 621 expect_notification('textDocument/didOpen', { 622 textDocument = { 623 languageId = '', 624 text = table.concat({ 'testing', '123' }, '\n') .. '\n', 625 uri = 'file://', 626 version = 0, 627 }, 628 }) 629 expect_notification('textDocument/didChange', { 630 textDocument = { 631 uri = 'file://', 632 version = 3, 633 }, 634 contentChanges = { 635 { text = table.concat({ 'testing', '321' }, '\n') .. '\n' }, 636 }, 637 }) 638 expect_notification('textDocument/didChange', { 639 textDocument = { 640 uri = 'file://', 641 version = 4, 642 }, 643 contentChanges = { 644 { text = table.concat({ 'testing', 'boop' }, '\n') .. '\n' }, 645 }, 646 }) 647 expect_notification('finish') 648 notify('finish') 649 end, 650 } 651 end 652 653 function tests.basic_check_buffer_open_and_change_multi_and_close() 654 skeleton { 655 on_init = function(params) 656 local expected_capabilities = protocol.make_client_capabilities() 657 assert_eq(params.capabilities, expected_capabilities) 658 return { 659 capabilities = { 660 textDocumentSync = protocol.TextDocumentSyncKind.Full, 661 }, 662 } 663 end, 664 body = function() 665 notify('start') 666 expect_notification('textDocument/didOpen', { 667 textDocument = { 668 languageId = '', 669 text = table.concat({ 'testing', '123' }, '\n') .. '\n', 670 uri = 'file://', 671 version = 0, 672 }, 673 }) 674 expect_notification('textDocument/didChange', { 675 textDocument = { 676 uri = 'file://', 677 version = 3, 678 }, 679 contentChanges = { 680 { text = table.concat({ 'testing', '321' }, '\n') .. '\n' }, 681 }, 682 }) 683 expect_notification('textDocument/didChange', { 684 textDocument = { 685 uri = 'file://', 686 version = 4, 687 }, 688 contentChanges = { 689 { text = table.concat({ 'testing', 'boop' }, '\n') .. '\n' }, 690 }, 691 }) 692 expect_notification('textDocument/didClose', { 693 textDocument = { 694 uri = 'file://', 695 }, 696 }) 697 expect_notification('finish') 698 notify('finish') 699 end, 700 } 701 end 702 703 function tests.basic_check_buffer_open_and_change_incremental() 704 skeleton { 705 on_init = function(params) 706 local expected_capabilities = protocol.make_client_capabilities() 707 assert_eq(params.capabilities, expected_capabilities) 708 return { 709 capabilities = { 710 textDocumentSync = { 711 openClose = true, 712 change = protocol.TextDocumentSyncKind.Incremental, 713 willSave = true, 714 willSaveWaitUntil = true, 715 save = { 716 includeText = true, 717 }, 718 }, 719 }, 720 } 721 end, 722 body = function() 723 notify('start') 724 expect_notification('textDocument/didOpen', { 725 textDocument = { 726 languageId = '', 727 text = table.concat({ 'testing', '123' }, '\n') .. '\n', 728 uri = 'file://', 729 version = 0, 730 }, 731 }) 732 expect_notification('textDocument/didChange', { 733 textDocument = { 734 uri = 'file://', 735 version = 3, 736 }, 737 contentChanges = { 738 { 739 range = { 740 start = { line = 1, character = 3 }, 741 ['end'] = { line = 1, character = 3 }, 742 }, 743 rangeLength = 0, 744 text = 'boop', 745 }, 746 }, 747 }) 748 expect_notification('finish') 749 notify('finish') 750 end, 751 } 752 end 753 754 function tests.basic_check_buffer_open_and_change_incremental_editing() 755 skeleton { 756 on_init = function(params) 757 local expected_capabilities = protocol.make_client_capabilities() 758 assert_eq(params.capabilities, expected_capabilities) 759 return { 760 capabilities = { 761 textDocumentSync = protocol.TextDocumentSyncKind.Incremental, 762 }, 763 } 764 end, 765 body = function() 766 notify('start') 767 expect_notification('textDocument/didOpen', { 768 textDocument = { 769 languageId = '', 770 text = table.concat({ 'testing', '123' }, '\n'), 771 uri = 'file://', 772 version = 0, 773 }, 774 }) 775 expect_notification('textDocument/didChange', { 776 textDocument = { 777 uri = 'file://', 778 version = 3, 779 }, 780 contentChanges = { 781 { 782 range = { 783 start = { line = 0, character = 0 }, 784 ['end'] = { line = 1, character = 0 }, 785 }, 786 rangeLength = 4, 787 text = 'testing\n\n', 788 }, 789 }, 790 }) 791 expect_notification('finish') 792 notify('finish') 793 end, 794 } 795 end 796 797 function tests.decode_nil() 798 skeleton { 799 on_init = function(_) 800 return { capabilities = {} } 801 end, 802 body = function() 803 notify('start') 804 notify('workspace/executeCommand', { 805 arguments = { 'EXTRACT_METHOD', { metadata = { field = vim.NIL } }, 3, 0, 6123, vim.NIL }, 806 command = 'refactor.perform', 807 title = 'EXTRACT_METHOD', 808 }) 809 notify('finish') 810 end, 811 } 812 end 813 814 function tests.code_action_with_resolve() 815 skeleton { 816 on_init = function() 817 return { 818 capabilities = { 819 codeActionProvider = { 820 resolveProvider = true, 821 }, 822 }, 823 } 824 end, 825 body = function() 826 notify('start') 827 local cmd = { title = 'Action 1' } 828 expect_request('textDocument/codeAction', function() 829 return nil, { cmd } 830 end) 831 expect_request('codeAction/resolve', function() 832 return nil, 833 { 834 title = 'Action 1', 835 command = { 836 title = 'Command 1', 837 command = 'dummy1', 838 }, 839 } 840 end) 841 notify('shutdown') 842 end, 843 } 844 end 845 846 function tests.code_action_server_side_command() 847 skeleton({ 848 on_init = function() 849 return { 850 capabilities = { 851 codeActionProvider = { 852 resolveProvider = false, 853 }, 854 executeCommandProvider = { 855 commands = { 'dummy1' }, 856 }, 857 }, 858 } 859 end, 860 body = function() 861 notify('start') 862 local cmd = { 863 title = 'Command 1', 864 command = 'dummy1', 865 } 866 expect_request('textDocument/codeAction', function() 867 return nil, { cmd } 868 end) 869 expect_request('workspace/executeCommand', function() 870 return nil, cmd 871 end) 872 notify('shutdown') 873 end, 874 }) 875 end 876 877 function tests.code_action_filter() 878 skeleton { 879 on_init = function() 880 return { 881 capabilities = { 882 codeActionProvider = { 883 resolveProvider = false, 884 }, 885 }, 886 } 887 end, 888 body = function() 889 notify('start') 890 local action = { 891 title = 'Action 1', 892 command = 'command', 893 } 894 local preferred_action = { 895 title = 'Action 2', 896 isPreferred = true, 897 command = 'preferred_command', 898 } 899 local type_annotate_action = { 900 title = 'Action 3', 901 kind = 'type-annotate', 902 command = 'type_annotate_command', 903 } 904 local type_annotate_foo_action = { 905 title = 'Action 4', 906 kind = 'type-annotate.foo', 907 command = 'type_annotate_foo_command', 908 } 909 expect_request('textDocument/codeAction', function() 910 return nil, { action, preferred_action, type_annotate_action, type_annotate_foo_action } 911 end) 912 expect_request('textDocument/codeAction', function() 913 return nil, { action, preferred_action, type_annotate_action, type_annotate_foo_action } 914 end) 915 notify('shutdown') 916 end, 917 } 918 end 919 920 function tests.clientside_commands() 921 skeleton { 922 on_init = function() 923 return { 924 capabilities = {}, 925 } 926 end, 927 body = function() 928 notify('start') 929 notify('shutdown') 930 end, 931 } 932 end 933 934 function tests.codelens_refresh_lock() 935 skeleton { 936 on_init = function() 937 return { 938 capabilities = { 939 codeLensProvider = { resolveProvider = true }, 940 }, 941 } 942 end, 943 body = function() 944 notify('start') 945 expect_request('textDocument/codeLens', function() 946 return { code = -32002, message = 'ServerNotInitialized' }, nil 947 end) 948 expect_request('textDocument/codeLens', function() 949 local lenses = { 950 { 951 range = { 952 start = { line = 0, character = 0 }, 953 ['end'] = { line = 0, character = 3 }, 954 }, 955 command = { title = 'Lens1', command = 'Dummy' }, 956 }, 957 } 958 return nil, lenses 959 end) 960 expect_request('textDocument/codeLens', function() 961 local lenses = { 962 { 963 range = { 964 start = { line = 0, character = 0 }, 965 ['end'] = { line = 0, character = 3 }, 966 }, 967 command = { title = 'Lens2', command = 'Dummy' }, 968 }, 969 } 970 return nil, lenses 971 end) 972 notify('shutdown') 973 end, 974 } 975 end 976 977 function tests.basic_formatting() 978 skeleton { 979 on_init = function() 980 return { 981 capabilities = { 982 documentFormattingProvider = true, 983 }, 984 } 985 end, 986 body = function() 987 notify('start') 988 expect_request('textDocument/formatting', function() 989 return nil, {} 990 end) 991 notify('shutdown') 992 end, 993 } 994 end 995 996 function tests.range_formatting() 997 skeleton { 998 on_init = function() 999 return { 1000 capabilities = { 1001 documentFormattingProvider = true, 1002 documentRangeFormattingProvider = true, 1003 }, 1004 } 1005 end, 1006 body = function() 1007 notify('start') 1008 expect_request('textDocument/rangeFormatting', function() 1009 return nil, {} 1010 end) 1011 notify('shutdown') 1012 end, 1013 } 1014 end 1015 1016 function tests.ranges_formatting() 1017 skeleton { 1018 on_init = function() 1019 return { 1020 capabilities = { 1021 documentFormattingProvider = true, 1022 documentRangeFormattingProvider = { 1023 rangesSupport = true, 1024 }, 1025 }, 1026 } 1027 end, 1028 body = function() 1029 notify('start') 1030 expect_request('textDocument/rangesFormatting', function() 1031 return nil, {} 1032 end) 1033 notify('shutdown') 1034 end, 1035 } 1036 end 1037 1038 function tests.set_defaults_all_capabilities() 1039 skeleton { 1040 on_init = function(_) 1041 return { 1042 capabilities = { 1043 definitionProvider = true, 1044 completionProvider = true, 1045 documentRangeFormattingProvider = true, 1046 hoverProvider = true, 1047 }, 1048 } 1049 end, 1050 body = function() 1051 notify('test') 1052 end, 1053 } 1054 end 1055 1056 function tests.inlay_hint() 1057 skeleton { 1058 on_init = function(params) 1059 local expected_capabilities = protocol.make_client_capabilities() 1060 assert_eq(params.capabilities, expected_capabilities) 1061 return { 1062 capabilities = { 1063 inlayHintProvider = true, 1064 }, 1065 } 1066 end, 1067 body = function() 1068 notify('start') 1069 expect_request('textDocument/inlayHint', function() 1070 return nil, {} 1071 end) 1072 expect_notification('finish') 1073 notify('finish') 1074 end, 1075 } 1076 end 1077 1078 -- Tests will be indexed by test_name 1079 local test_name = arg[1] 1080 local timeout = tonumber(arg[2]) 1081 assert(type(test_name) == 'string', 'test_name must be specified as first arg.') 1082 1083 local kill_timer = vim.defer_fn(function() 1084 log('ERROR', 'LSP', 'TIMEOUT') 1085 io.stderr:write('TIMEOUT') 1086 os.exit(100) 1087 end, timeout or 1e3) 1088 1089 -- Close the timer on exit (deadly signal or :cquit) to avoid delay with ASAN/TSAN. 1090 vim.api.nvim_create_autocmd('VimLeave', { 1091 callback = function() 1092 kill_timer:stop() 1093 kill_timer:close() 1094 end, 1095 }) 1096 1097 local status, err = pcall(assert(tests[test_name], 'Test not found')) 1098 if not status then 1099 log('ERROR', 'LSP', tostring(err)) 1100 io.stderr:write(err) 1101 vim.cmd [[101cquit]] 1102 end