Before: call ale#test#SetDirectory('/testplugin/test') call ale#test#SetFilename('dummy.txt') let g:old_filename = expand('%:p') let g:Callback = '' let g:expr_list = [] let g:message_list = [] let g:handle_code_action_called = 0 let g:code_actions = [] let g:options = {} let g:capability_checked = '' let g:conn_id = v:null let g:InitCallback = v:null runtime autoload/ale/lsp_linter.vim runtime autoload/ale/lsp.vim runtime autoload/ale/util.vim runtime autoload/ale/rename.vim runtime autoload/ale/code_action.vim function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {}) call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) if a:linter.lsp is# 'tsserver' call ale#lsp#MarkConnectionAsTsserver(g:conn_id) endif let l:details = { \ 'command': 'foobar', \ 'buffer': a:buffer, \ 'connection_id': g:conn_id, \ 'project_root': '/foo/bar', \} let g:InitCallback = {-> ale#lsp_linter#OnInit(a:linter, l:details, a:Callback)} endfunction function! ale#lsp#HasCapability(conn_id, capability) abort let g:capability_checked = a:capability return 1 endfunction function! ale#lsp#RegisterCallback(conn_id, callback) abort let g:Callback = a:callback endfunction function! ale#lsp#Send(conn_id, message) abort call add(g:message_list, a:message) return 42 endfunction function! ale#util#Execute(expr) abort call add(g:expr_list, a:expr) endfunction function! ale#code_action#HandleCodeAction(code_action, should_save) abort let g:handle_code_action_called = 1 AssertEqual v:true, a:should_save call add(g:code_actions, a:code_action) endfunction function! ale#util#Input(message, value) abort return 'a-new-name' endfunction call ale#rename#SetMap({ \ 3: { \ 'old_name': 'oldName', \ 'new_name': 'aNewName', \ }, \}) After: if g:conn_id isnot v:null call ale#lsp#RemoveConnectionWithID(g:conn_id) endif call ale#rename#SetMap({}) call ale#test#RestoreDirectory() call ale#linter#Reset() unlet! g:capability_checked unlet! g:InitCallback unlet! g:old_filename unlet! g:conn_id unlet! g:Callback unlet! g:message_list unlet! g:expr_list unlet! b:ale_linters unlet! g:options unlet! g:code_actions unlet! g:handle_code_action_called runtime autoload/ale/lsp_linter.vim runtime autoload/ale/lsp.vim runtime autoload/ale/util.vim runtime autoload/ale/rename.vim runtime autoload/ale/code_action.vim Execute(Other messages for the tsserver handler should be ignored): call ale#rename#HandleTSServerResponse(1, {'command': 'foo'}) AssertEqual g:handle_code_action_called, 0 Execute(Failed rename responses should be handled correctly): call ale#rename#SetMap({3: {'old_name': 'oldName', 'new_name': 'a-test'}}) call ale#rename#HandleTSServerResponse( \ 1, \ {'command': 'rename', 'request_seq': 3} \) AssertEqual g:handle_code_action_called, 0 Given typescript(Some typescript file): foo somelongerline bazxyzxyzxyz Execute(Code actions from tsserver should be handled): call ale#rename#HandleTSServerResponse(1, { \ 'command': 'rename', \ 'request_seq': 3, \ 'success': v:true, \ 'body': { \ 'locs': [ \ { \ 'file': '/foo/bar/file1.ts', \ 'locs': [ \ { \ 'start': { \ 'line': 1, \ 'offset': 2, \ }, \ 'end': { \ 'line': 3, \ 'offset': 4, \ }, \ }, \ ], \ }, \ { \ 'file': '/foo/bar/file2.ts', \ 'locs': [ \ { \ 'start': { \ 'line': 10, \ 'offset': 20, \ }, \ 'end': { \ 'line': 30, \ 'offset': 40, \ }, \ }, \ ], \ }, \ ] \ }, \}) AssertEqual \ [ \ { \ 'description': 'rename', \ 'changes': [ \ { \ 'fileName': '/foo/bar/file1.ts', \ 'textChanges': [ \ { \ 'start': { \ 'line': 1, \ 'offset': 2, \ }, \ 'end': { \ 'line': 3, \ 'offset': 4, \ }, \ 'newText': 'aNewName', \ }, \ ], \ }, \ { \ 'fileName': '/foo/bar/file2.ts', \ 'textChanges': [ \ { \ 'start': { \ 'line': 10, \ 'offset': 20, \ }, \ 'end': { \ 'line': 30, \ 'offset': 40, \ }, \ 'newText': 'aNewName', \ }, \ ], \ }, \ ], \ } \ ], \ g:code_actions Execute(HandleTSServerResponse does nothing when no data in rename_map): call ale#rename#HandleTSServerResponse(1, { \ 'command': 'rename', \ 'request_seq': -9, \ 'success': v:true, \ 'body': {} \}) AssertEqual g:handle_code_action_called, 0 Execute(Prints a tsserver error message when unsuccessful): call ale#rename#HandleTSServerResponse(1, { \ 'command': 'rename', \ 'request_seq': 3, \ 'success': v:false, \ 'message': 'This symbol cannot be renamed', \}) AssertEqual g:handle_code_action_called, 0 AssertEqual ['echom ''Error renaming "oldName" to: "aNewName". ' . \ 'Reason: This symbol cannot be renamed'''], g:expr_list Execute(Does nothing when no changes): call ale#rename#HandleTSServerResponse(1, { \ 'command': 'rename', \ 'request_seq': 3, \ 'success': v:true, \ 'body': { \ 'locs': [] \ } \}) AssertEqual g:handle_code_action_called, 0 AssertEqual ['echom ''Error renaming "oldName" to: "aNewName"'''], g:expr_list Execute(tsserver rename requests should be sent): call ale#rename#SetMap({}) call ale#linter#Reset() runtime ale_linters/typescript/tsserver.vim call setpos('.', [bufnr(''), 2, 5, 0]) ALERename " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) AssertEqual type(function('type')), type(g:InitCallback) call g:InitCallback() AssertEqual 'rename', g:capability_checked AssertEqual \ 'function(''ale#rename#HandleTSServerResponse'')', \ string(g:Callback) AssertEqual \ [ \ ale#lsp#tsserver_message#Change(bufnr('')), \ [0, 'ts@rename', { \ 'file': expand('%:p'), \ 'line': 2, \ 'offset': 5, \ 'arguments': { \ 'findInComments': g:ale_rename_tsserver_find_in_comments, \ 'findInStrings': g:ale_rename_tsserver_find_in_strings, \ }, \ }] \ ], \ g:message_list AssertEqual {'42': {'old_name': 'somelongerline', 'new_name': 'a-new-name'}}, \ ale#rename#GetMap() Given python(Some Python file): foo somelongerline bazxyzxyzxyz Execute(Code actions from LSP should be handled): call ale#rename#HandleLSPResponse(1, { \ 'id': 3, \ 'result': { \ 'changes': { \ 'file:///foo/bar/file1.ts': [ \ { \ 'range': { \ 'start': { \ 'line': 1, \ 'character': 2, \ }, \ 'end': { \ 'line': 3, \ 'character': 4, \ }, \ }, \ 'newText': 'bla123' \ }, \ ], \ }, \ }, \}) AssertEqual \ [ \ { \ 'description': 'rename', \ 'changes': [ \ { \ 'fileName': '/foo/bar/file1.ts', \ 'textChanges': [ \ { \ 'start': { \ 'line': 2, \ 'offset': 3, \ }, \ 'end': { \ 'line': 4, \ 'offset': 5, \ }, \ 'newText': 'bla123', \ }, \ ], \ }, \ ], \ } \ ], \ g:code_actions Execute(LSP should perform no action when no result): call ale#rename#HandleLSPResponse(1, { \ 'id': 3, \}) AssertEqual g:handle_code_action_called, 0 AssertEqual ['echom ''No rename result received from server'''], g:expr_list Execute(LSP should perform no action when no changes): call ale#rename#HandleLSPResponse(1, { \ 'id': 3, \ 'result': {}, \}) AssertEqual g:handle_code_action_called, 0 AssertEqual ['echom ''No changes received from server'''], g:expr_list Execute(LSP should perform no action when changes is empty): call ale#rename#HandleLSPResponse(1, { \ 'id': 3, \ 'result': { \ 'changes': [], \ }, \}) AssertEqual g:handle_code_action_called, 0 AssertEqual ['echom ''No changes received from server'''], g:expr_list Execute(LSP rename requests should be sent): call ale#rename#SetMap({}) runtime ale_linters/python/pyls.vim let b:ale_linters = ['pyls'] call setpos('.', [bufnr(''), 1, 5, 0]) ALERename " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) AssertEqual type(function('type')), type(g:InitCallback) call g:InitCallback() AssertEqual 'rename', g:capability_checked AssertEqual \ 'function(''ale#rename#HandleLSPResponse'')', \ string(g:Callback) AssertEqual \ [ \ [1, 'textDocument/didChange', { \ 'textDocument': { \ 'uri': ale#path#ToURI(expand('%:p')), \ 'version': g:ale_lsp_next_version_id - 1, \ }, \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] \ }], \ [0, 'textDocument/rename', { \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}, \ 'position': {'line': 0, 'character': 2}, \ 'newName': 'a-new-name', \ }], \ ], \ g:message_list AssertEqual {'42': {'old_name': 'foo', 'new_name': 'a-new-name'}}, \ ale#rename#GetMap()