diff options
-rw-r--r-- | runtime/lua/vim/lsp/buf.lua | 33 | ||||
-rw-r--r-- | test/functional/fixtures/fake-lsp-server.lua | 83 | ||||
-rw-r--r-- | test/functional/plugin/lsp_spec.lua | 86 |
3 files changed, 196 insertions, 6 deletions
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 264d7c0247..8bfcd90f12 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -249,13 +249,34 @@ end ---@param new_name (string) If not provided, the user will be prompted for a new ---name using |input()|. function M.rename(new_name) - -- TODO(ashkan) use prepareRename - -- * result: [`Range`](#range) \| `{ range: Range, placeholder: string }` \| `null` describing the range of the string to rename and optionally a placeholder text of the string content to be renamed. If `null` is returned then it is deemed that a 'textDocument/rename' request is not valid at the given position. local params = util.make_position_params() - new_name = new_name or npcall(vfn.input, "New Name: ", vfn.expand('<cword>')) - if not (new_name and #new_name > 0) then return end - params.newName = new_name - request('textDocument/rename', params) + local function prepare_rename(err, result) + if err == nil and result == nil then + vim.notify('nothing to rename', vim.log.levels.INFO) + return + end + if result and result.placeholder then + new_name = new_name or npcall(vfn.input, "New Name: ", result.placeholder) + elseif result and result.start and result['end'] and + result.start.line == result['end'].line then + local line = vfn.getline(result.start.line+1) + local start_char = result.start.character+1 + local end_char = result['end'].character + new_name = new_name or npcall(vfn.input, "New Name: ", string.sub(line, start_char, end_char)) + else + -- fallback to guessing symbol using <cword> + -- + -- this can happen if the language server does not support prepareRename, + -- returns an unexpected response, or requests for "default behavior" + -- + -- see https://microsoft.github.io/language-server-protocol/specification#textDocument_prepareRename + new_name = new_name or npcall(vfn.input, "New Name: ", vfn.expand('<cword>')) + end + if not (new_name and #new_name > 0) then return end + params.newName = new_name + request('textDocument/rename', params) + end + request('textDocument/prepareRename', params, prepare_rename) end --- Lists all the references to the symbol under the cursor in the quickfix window. diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua index b7fddc8f29..9579525502 100644 --- a/test/functional/fixtures/fake-lsp-server.lua +++ b/test/functional/fixtures/fake-lsp-server.lua @@ -126,6 +126,89 @@ function tests.check_workspace_configuration() } end +function tests.prepare_rename_nil() + skeleton { + on_init = function() + return { capabilities = { + renameProvider = true, + } } + end; + body = function() + notify('start') + expect_request('textDocument/prepareRename', function() + return nil, nil + end) + notify('shutdown') + end; + } +end + +function tests.prepare_rename_placeholder() + skeleton { + on_init = function() + return { capabilities = { + renameProvider = true, + } } + end; + body = function() + notify('start') + expect_request('textDocument/prepareRename', function() + return nil, {placeholder = 'placeholder'} + end) + expect_request('textDocument/rename', function(params) + assert_eq(params.newName, 'renameto') + return nil, nil + end) + notify('shutdown') + end; + } +end + +function tests.prepare_rename_range() + skeleton { + on_init = function() + return { capabilities = { + renameProvider = true, + } } + end; + body = function() + notify('start') + expect_request('textDocument/prepareRename', function() + return nil, { + start = { line = 1, character = 8 }, + ['end'] = { line = 1, character = 12 }, + } + end) + expect_request('textDocument/rename', function(params) + assert_eq(params.newName, 'renameto') + return nil, nil + end) + notify('shutdown') + end; + } +end + +function tests.prepare_rename_error() + skeleton { + on_init = function() + return { capabilities = { + renameProvider = true, + } } + end; + body = function() + notify('start') + expect_request('textDocument/prepareRename', function() + return {}, nil + end) + expect_request('textDocument/rename', function(params) + assert_eq(params.newName, 'renameto') + return nil, nil + end) + notify('shutdown') + end; + } +end + function tests.basic_check_capabilities() skeleton { on_init = function(params) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 1d790cd470..a9ea26343d 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -2202,4 +2202,90 @@ describe('LSP', function() eq(expected, qflist) end) end) + + describe('vim.lsp.buf.rename', function() + for _, test in ipairs({ + { + it = "does not attempt to rename on nil response", + name = "prepare_rename_nil", + expected_handlers = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="start", client_id=1}}; + }, + }, + { + it = "handles prepareRename placeholder response", + name = "prepare_rename_placeholder", + expected_handlers = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, NIL, {method="textDocument/rename", client_id=1, bufnr=1}}; + {NIL, {}, {method="start", client_id=1}}; + }, + expected_text = "placeholder", -- see fake lsp response + }, + { + it = "handles range response", + name = "prepare_rename_range", + expected_handlers = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, NIL, {method="textDocument/rename", client_id=1, bufnr=1}}; + {NIL, {}, {method="start", client_id=1}}; + }, + expected_text = "line", -- see test case and fake lsp response + }, + { + it = "handles error", + name = "prepare_rename_error", + expected_handlers = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, NIL, {method="textDocument/rename", client_id=1, bufnr=1}}; + {NIL, {}, {method="start", client_id=1}}; + }, + expected_text = "two", -- see test case + }, + }) do + it(test.it, function() + local client + test_rpc_server { + test_name = test.name; + on_init = function(_client) + client = _client + eq(true, client.resolved_capabilities().rename) + end; + on_setup = function() + exec_lua([=[ + local bufnr = vim.api.nvim_get_current_buf() + lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) + vim.lsp._stubs = {} + vim.fn.input = function(prompt, text) + vim.lsp._stubs.input_prompt = prompt + vim.lsp._stubs.input_text = text + return 'renameto' -- expect this value in fake lsp + end + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {'', 'this is line two'}) + vim.fn.cursor(2, 13) -- the space between "line" and "two" + ]=]) + end; + on_exit = function(code, signal) + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) + end; + on_handler = function(err, result, ctx) + eq(table.remove(test.expected_handlers), {err, result, ctx}, "expected handler") + if ctx.method == 'start' then + exec_lua("vim.lsp.buf.rename()") + end + if ctx.method == 'shutdown' then + if test.expected_text then + eq("New Name: ", exec_lua("return vim.lsp._stubs.input_prompt")) + eq(test.expected_text, exec_lua("return vim.lsp._stubs.input_text")) + end + client.stop() + end + end; + } + end) + end + end) + end) |