mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	feat(lsp): support textDocument/prepareRename (#15514)
This commit is contained in:
		| @@ -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() | ||||
|   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. | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Zi How Poh
					Zi How Poh