From fe0d323073f536fe670ce3f10c6b293b583ac232 Mon Sep 17 00:00:00 2001 From: johny salas Date: Thu, 14 Dec 2023 09:39:52 -0500 Subject: [PATCH 1/2] feat: LSP rename converts next word if located in a non word character --- lua/textcase/plugin/conversion.lua | 4 +- lua/textcase/shared/utils.lua | 15 +++++ tests/test_helpers.lua | 17 ++++++ .../fixtures/destructuring-bar-as-constant.ts | 1 + .../fixtures/destructuring-foo-as-constant.ts | 1 + tests/textcase/lsp/fixtures/destructuring.ts | 1 + tests/textcase/lsp/lsp_spec.lua | 60 ++++++++++++++++++- 7 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 tests/textcase/lsp/fixtures/destructuring-bar-as-constant.ts create mode 100644 tests/textcase/lsp/fixtures/destructuring-foo-as-constant.ts create mode 100644 tests/textcase/lsp/fixtures/destructuring.ts diff --git a/lua/textcase/plugin/conversion.lua b/lua/textcase/plugin/conversion.lua index 0cbf6c5..f46e122 100644 --- a/lua/textcase/plugin/conversion.lua +++ b/lua/textcase/plugin/conversion.lua @@ -88,8 +88,10 @@ function M.do_lsp_rename(method) "method textDocument/rename is not supported by any of the servers registered for the current buffer" ) else - local current_word = vim.fn.expand("") + local current_word_info = utils.get_current_word_info() + local current_word = current_word_info.word local params = lsp.util.make_position_params() + params.position = current_word_info.position params.newName = method(current_word) lsp.buf_request_all(0, "textDocument/rename", params, function(results) diff --git a/lua/textcase/shared/utils.lua b/lua/textcase/shared/utils.lua index 50e01e3..2f089f8 100644 --- a/lua/textcase/shared/utils.lua +++ b/lua/textcase/shared/utils.lua @@ -243,6 +243,21 @@ function utils.trim_str(str, _trimmable_chars) return trim_info, trimmed_str end +function utils.get_current_word_info() + local cursor_pos = vim.fn.getpos(".") + local start_the_search_at_cursor_position = "W" + local word = "\\w" + local current_word_pos = vim.fn.searchpos(word, start_the_search_at_cursor_position) + vim.fn.setpos(".", cursor_pos) + + local line = current_word_pos[1] - 1 + local character = current_word_pos[2] + + local position = { line = line, character = character } + + return { position = position, word = vim.fn.expand("") } +end + function utils.get_list(str, mode) local limit = 0 local initial = nil diff --git a/tests/test_helpers.lua b/tests/test_helpers.lua index 39fd420..e640091 100644 --- a/tests/test_helpers.lua +++ b/tests/test_helpers.lua @@ -48,4 +48,21 @@ M.wait_for_language_server_to_start = function() end) end +-- This method duplicates wait_for_language_server_to_start for +-- the destructuring file. Using Write Everything Twice and +-- avoiding to come up with a wrong abstraction too early +M.wait_for_language_server_to_start_on_destructuring_file = function() + M.execute_keys("ww") -- Move to `foovar` + local hover = "" + M.wait_for(30 * 1000, function() + -- This prints one "Error detected while processing command line:" but this can be ignored + vim.lsp.buf_request_all(0, "textDocument/hover", vim.lsp.util.make_position_params(), function(results) + -- Hover will print the type definition of the variable under the cursor. Hence, + -- it should contain "fooVar". + hover = results[1].result.contents.value + end) + return string.find(hover, "fooVar") + end) +end + return M diff --git a/tests/textcase/lsp/fixtures/destructuring-bar-as-constant.ts b/tests/textcase/lsp/fixtures/destructuring-bar-as-constant.ts new file mode 100644 index 0000000..5e329d1 --- /dev/null +++ b/tests/textcase/lsp/fixtures/destructuring-bar-as-constant.ts @@ -0,0 +1 @@ +const [fooVar, BAR_VAR] = [0, 1] diff --git a/tests/textcase/lsp/fixtures/destructuring-foo-as-constant.ts b/tests/textcase/lsp/fixtures/destructuring-foo-as-constant.ts new file mode 100644 index 0000000..583e8a6 --- /dev/null +++ b/tests/textcase/lsp/fixtures/destructuring-foo-as-constant.ts @@ -0,0 +1 @@ +const [FOO_VAR, barVar] = [0, 1] diff --git a/tests/textcase/lsp/fixtures/destructuring.ts b/tests/textcase/lsp/fixtures/destructuring.ts new file mode 100644 index 0000000..e127716 --- /dev/null +++ b/tests/textcase/lsp/fixtures/destructuring.ts @@ -0,0 +1 @@ +const [fooVar, barVar] = [0, 1] diff --git a/tests/textcase/lsp/lsp_spec.lua b/tests/textcase/lsp/lsp_spec.lua index 4923cda..8dceab5 100644 --- a/tests/textcase/lsp/lsp_spec.lua +++ b/tests/textcase/lsp/lsp_spec.lua @@ -2,7 +2,7 @@ local test_helpers = require("tests.test_helpers") local textcase = require("textcase") describe("LSP", function() - describe("Rename", function() + describe("Rename case conversion", function() before_each(function() textcase.setup({}) @@ -45,4 +45,62 @@ describe("LSP", function() assert.are.same(table.concat(content, "\n"), expected_code) end) end) + + describe("Renaming according to cursor position", function() + before_each(function() + textcase.setup({}) + + local path = "./tests/textcase/lsp/fixtures/destructuring.ts" + local cmd = " silent exe 'e " .. path .. "'" + vim.cmd(cmd) + vim.bo.filetype = "typescript" + + test_helpers.wait_for_language_server_to_start_on_destructuring_file() + test_helpers.execute_keys("gg0") + end) + + after_each(function() + -- Close the buffer so the next test can open it again. + vim.cmd("silent exe 'bd! %'") + end) + + it("Should convert word when cursor is on its last character", function() + test_helpers.execute_keys("12lgaN") + local content = nil + test_helpers.wait_for(5 * 1000, function() + content = test_helpers.get_buf_lines() + local found_modified_variable = not not string.find(content[1], "FOO_VAR") + return found_modified_variable + end) + + local expected_code = test_helpers.read_file("./tests/textcase/lsp/fixtures/destructuring-foo-as-constant.ts") + assert.are.same(table.concat(content, "\n") .. "\n", expected_code) + end) + + it("Should convert the next word when the cursor isn't on a word", function() + test_helpers.execute_keys("13lgaN") + local content = nil + test_helpers.wait_for(5 * 1000, function() + content = test_helpers.get_buf_lines() + local found_modified_variable = not not string.find(content[1], "BAR_VAR") + return found_modified_variable + end) + + local expected_code = test_helpers.read_file("./tests/textcase/lsp/fixtures/destructuring-bar-as-constant.ts") + assert.are.same(table.concat(content, "\n") .. "\n", expected_code) + end) + + it("Shouldn't modify variables when the cursor isn't on a word and no word is found next", function() + test_helpers.execute_keys("$gaN") + local content = nil + test_helpers.wait_for(5 * 1000, function() + content = test_helpers.get_buf_lines() + local found_modified_variable = not not string.find(content[1], "BAR_VAR") + return found_modified_variable + end) + + local expected_code = test_helpers.read_file("./tests/textcase/lsp/fixtures/destructuring.ts") + assert.are.same(table.concat(content, "\n") .. "\n", expected_code) + end) + end) end) From 97b8fc077080d109114f409d25bc0291b4375817 Mon Sep 17 00:00:00 2001 From: johny salas Date: Fri, 15 Dec 2023 15:38:14 -0500 Subject: [PATCH 2/2] Addressed comments --- lua/textcase/shared/utils.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lua/textcase/shared/utils.lua b/lua/textcase/shared/utils.lua index 2f089f8..677c46b 100644 --- a/lua/textcase/shared/utils.lua +++ b/lua/textcase/shared/utils.lua @@ -243,8 +243,19 @@ function utils.trim_str(str, _trimmable_chars) return trim_info, trimmed_str end +-- Gets the position and text of the current word under the cursor +-- If the cursor is located on a word it returns information about that word +-- If the cursor is not on a word, it returns information about the next word +-- The method could have some validation when there is no word to be returned +-- not required at the moment +-- One of the cases where this method is useful is on LSP rename, +-- If there cursor is not on a word, TextDocument/rename acts on the previous +-- word, while vim.fn.expand returns information about the following word +-- By using this method the plugin has a way of referring to the same word function utils.get_current_word_info() local cursor_pos = vim.fn.getpos(".") + -- This could be customized to read exactly the word under the cursor, ignoring + -- close words, or even considering words before the cursor. Consult values like Wn and Wb local start_the_search_at_cursor_position = "W" local word = "\\w" local current_word_pos = vim.fn.searchpos(word, start_the_search_at_cursor_position)