From 1b915139e43385a89ed5a5d2fedade0aca790921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tautvydas=20S=CC=8Cidlauskas?= Date: Thu, 6 Apr 2023 15:30:39 +0300 Subject: [PATCH 1/7] feat: add FlutterRename command This command adds support for `updateImportsOnRename` setting (https://github.com/dart-lang/sdk/blob/master/pkg/analysis_server/tool/lsp_spec/README.md#client-workspace-configuration). It is useful when file is renamed after class rename (when enabled `renameFilesWithClasses` setting). It will fallback to `vim.lsp.buf.rename()` if no `dartls` client found. If client is found, file is the same as class name, and class is being renamed, it will request for changes via `workspace/willRenameFiles`. Results of `workspace/willRenameFiles` will be applied after class rename. To test it try to rename class which is in the file with same file name. Before: class renamed, file renamed, usage of class renamed, but imports are not updated. After: class renamed, file renamed, usage and imports are updated. --- README.md | 2 + lua/flutter-tools.lua | 1 + lua/flutter-tools/lsp/init.lua | 15 ++-- lua/flutter-tools/lsp/rename.lua | 149 +++++++++++++++++++++++++++++++ 4 files changed, 160 insertions(+), 7 deletions(-) create mode 100644 lua/flutter-tools/lsp/rename.lua diff --git a/README.md b/README.md index 20f7bd9e..42f2138d 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,7 @@ require("flutter-tools").setup {} -- use defaults - `FlutterLspRestart` - This command restarts the dart language server, and is intended for situations where it begins to work incorrectly. - `FlutterSuper` - Go to super class, method using custom LSP method `dart/textDocument/super`. - `FlutterReanalyze` - Forces LSP server reanalyze using custom LSP method `dart/reanalyze`. +- `FlutterRename` - Renames and updates imports if needed.
@@ -273,6 +274,7 @@ require("flutter-tools").setup { analysisExcludedFolders = {""}, renameFilesWithClasses = "prompt", -- "always" enableSnippets = true, + updateImportsOnRename = true, -- Whether to update imports and other directives when files are renamed. Required for `FlutterRename` command. } } } diff --git a/lua/flutter-tools.lua b/lua/flutter-tools.lua index 98a429ee..91fe9c13 100644 --- a/lua/flutter-tools.lua +++ b/lua/flutter-tools.lua @@ -48,6 +48,7 @@ local function setup_commands() --- LSP command("FlutterSuper", lsp.dart_lsp_super) command("FlutterReanalyze", lsp.dart_reanalyze) + command("FlutterRename", function() require("flutter-tools.lsp.rename").rename() end) end ---Initialise various plugin modules diff --git a/lua/flutter-tools/lsp/init.lua b/lua/flutter-tools/lsp/init.lua index df073240..a55a3543 100644 --- a/lua/flutter-tools/lsp/init.lua +++ b/lua/flutter-tools/lsp/init.lua @@ -75,6 +75,7 @@ local function get_defaults(opts) path.join(flutter_sdk_path, "packages"), path.join(flutter_sdk_path, ".pub-cache"), }, + updateImportsOnRename = true, }, }, handlers = { @@ -149,9 +150,9 @@ M.document_color = function() local active_clients = vim.tbl_map(function(c) return c.id end, vim.lsp.get_active_clients()) local dartls = get_dartls_client() if - dartls - and vim.tbl_contains(active_clients, dartls.id) - and dartls.server_capabilities.colorProvider + dartls + and vim.tbl_contains(active_clients, dartls.id) + and dartls.server_capabilities.colorProvider then color.document_color() end @@ -222,10 +223,10 @@ function M.attach() get_server_config(user_config, function(c) c.root_dir = M.get_lsp_root_dir() - or fs.dirname(fs.find(ROOT_PATTERNS, { - path = buffer_path, - upward = true, - })[1]) + or fs.dirname(fs.find(ROOT_PATTERNS, { + path = buffer_path, + upward = true, + })[1]) vim.lsp.start(c) end) end diff --git a/lua/flutter-tools/lsp/rename.lua b/lua/flutter-tools/lsp/rename.lua new file mode 100644 index 00000000..525d9b46 --- /dev/null +++ b/lua/flutter-tools/lsp/rename.lua @@ -0,0 +1,149 @@ +local M = {} + +local api = vim.api +local util = vim.lsp.util +local lazy = require("flutter-tools.lazy") +local path = lazy.require("flutter-tools.utils.path") ---@module "flutter-tools.utils.path" + +--- Computes a filename for a given class name (convert from PascalCase to snake_case). +local function file_name_for_class_name(class_name) + local starts_uppercase = class_name:find("^%u") + if not starts_uppercase then return nil end + local file_name = class_name:gsub("(%u)", "_%1"):lower() + -- Removes first underscore + file_name = file_name:sub(2) + return file_name .. ".dart" +end + +local function will_rename_files(old_name, new_name, callback) + local params = vim.lsp.util.make_position_params() + if not new_name then return end + local file_change = { + newUri = vim.uri_from_fname(new_name), + oldUri = vim.uri_from_fname(old_name), + } + params.files = { file_change } + vim.lsp.buf_request(0, "workspace/willRenameFiles", params, function(err, result) + if err then + vim.notify(err.message or "Error on getting lsp rename results!") + return + end + callback(result) + end) +end + +--- Call this function when you want rename class or anything else. +--- If file will be renamed too, this function will update imports. +--- Function has same signature as `vim.lsp.buf.rename()` function and can be used instead of it. +function M.rename(new_name, options) + options = options or {} + local bufnr = options.bufnr or api.nvim_get_current_buf() + local clients = vim.lsp.get_active_clients({ + bufnr = bufnr, + name = "dartls", + }) + local client = clients[1] + if not client then + -- Fallback to default rename function if language server is not dartls + vim.lsp.buf.rename(new_name, options) + return + end + + local win = api.nvim_get_current_win() + + -- Compute early to account for cursor movements after going async + local cword = vim.fn.expand("") + local actual_file_name = vim.fn.expand("%:t") + local old_computed_filename = file_name_for_class_name(cword) + local is_file_rename = old_computed_filename == actual_file_name + + local function get_text_at_range(range, offset_encoding) + return api.nvim_buf_get_text( + bufnr, + range.start.line, + util._get_line_byte_from_position(bufnr, range.start, offset_encoding), + range["end"].line, + util._get_line_byte_from_position(bufnr, range["end"], offset_encoding), + {} + )[1] + end + + local function rename(name, will_rename_files_result) + local params = util.make_position_params(win, client.offset_encoding) + params.newName = name + local handler = client.handlers["textDocument/rename"] + or vim.lsp.handlers["textDocument/rename"] + client.request("textDocument/rename", params, function(...) + handler(...) + if will_rename_files_result then + -- the `will_rename_files_result` contains all the places we need to update imports + -- so we apply those edits. + vim.lsp.util.apply_workspace_edit(will_rename_files_result, client.offset_encoding) + end + end, bufnr) + end + + local function rename_fix_imports(name) + if is_file_rename then + local old_file_path = vim.fn.expand("%:p") + local new_filename = file_name_for_class_name(name) + local actual_file_head = vim.fn.expand("%:p:h") + local new_file_path = path.join(actual_file_head, new_filename) + will_rename_files(old_file_path, new_file_path, function(result) rename(name, result) end) + else + rename(name) + end + end + + if client.supports_method("textDocument/prepareRename") then + local params = util.make_position_params(win, client.offset_encoding) + client.request("textDocument/prepareRename", params, function(err, result) + if err or result == nil then + local msg = err and ("Error on prepareRename: " .. (err.message or "")) + or "Nothing to rename" + vim.notify(msg, vim.log.levels.INFO) + return + end + + if new_name then + rename_fix_imports(new_name) + return + end + + local prompt_opts = { + prompt = "New Name: ", + } + -- result: Range | { range: Range, placeholder: string } + if result.placeholder then + prompt_opts.default = result.placeholder + elseif result.start then + prompt_opts.default = get_text_at_range(result, client.offset_encoding) + elseif result.range then + prompt_opts.default = get_text_at_range(result.range, client.offset_encoding) + else + prompt_opts.default = cword + end + vim.ui.input(prompt_opts, function(input) + if not input or #input == 0 then return end + rename_fix_imports(input) + end) + end, bufnr) + else + assert(client.supports_method("textDocument/rename"), "Client must support textDocument/rename") + if new_name then + rename_fix_imports(new_name) + return + end + + local prompt_opts = { + prompt = "New Name: ", + default = cword, + } + vim.ui.input(prompt_opts, function(input) + if not input or #input == 0 then return end + rename_fix_imports(input) + end) + end +end + +return M From 2c3e5b839ca89c952e4a70240da384444db38976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tautvydas=20S=CC=8Cidlauskas?= Date: Thu, 6 Apr 2023 15:49:57 +0300 Subject: [PATCH 2/7] fixup! feat: add FlutterRename command --- lua/flutter-tools/lsp/init.lua | 14 +++++++------- lua/flutter-tools/lsp/rename.lua | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lua/flutter-tools/lsp/init.lua b/lua/flutter-tools/lsp/init.lua index a55a3543..5a3506eb 100644 --- a/lua/flutter-tools/lsp/init.lua +++ b/lua/flutter-tools/lsp/init.lua @@ -150,9 +150,9 @@ M.document_color = function() local active_clients = vim.tbl_map(function(c) return c.id end, vim.lsp.get_active_clients()) local dartls = get_dartls_client() if - dartls - and vim.tbl_contains(active_clients, dartls.id) - and dartls.server_capabilities.colorProvider + dartls + and vim.tbl_contains(active_clients, dartls.id) + and dartls.server_capabilities.colorProvider then color.document_color() end @@ -223,10 +223,10 @@ function M.attach() get_server_config(user_config, function(c) c.root_dir = M.get_lsp_root_dir() - or fs.dirname(fs.find(ROOT_PATTERNS, { - path = buffer_path, - upward = true, - })[1]) + or fs.dirname(fs.find(ROOT_PATTERNS, { + path = buffer_path, + upward = true, + })[1]) vim.lsp.start(c) end) end diff --git a/lua/flutter-tools/lsp/rename.lua b/lua/flutter-tools/lsp/rename.lua index 525d9b46..a54c554d 100644 --- a/lua/flutter-tools/lsp/rename.lua +++ b/lua/flutter-tools/lsp/rename.lua @@ -72,7 +72,7 @@ function M.rename(new_name, options) local params = util.make_position_params(win, client.offset_encoding) params.newName = name local handler = client.handlers["textDocument/rename"] - or vim.lsp.handlers["textDocument/rename"] + or vim.lsp.handlers["textDocument/rename"] client.request("textDocument/rename", params, function(...) handler(...) if will_rename_files_result then @@ -100,7 +100,7 @@ function M.rename(new_name, options) client.request("textDocument/prepareRename", params, function(err, result) if err or result == nil then local msg = err and ("Error on prepareRename: " .. (err.message or "")) - or "Nothing to rename" + or "Nothing to rename" vim.notify(msg, vim.log.levels.INFO) return end From 0c131f8f56abacfe32d44ab6c09fbb965eb09863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tautvydas=20S=CC=8Cidlauskas?= Date: Thu, 6 Apr 2023 19:43:57 +0300 Subject: [PATCH 3/7] refact: extract dartls retrieval logic --- lua/flutter-tools/lsp/color/init.lua | 11 +++++----- lua/flutter-tools/lsp/init.lua | 31 +++++++--------------------- lua/flutter-tools/lsp/rename.lua | 12 +++++------ lua/flutter-tools/lsp/utils.lua | 13 ++++++++++++ 4 files changed, 31 insertions(+), 36 deletions(-) create mode 100644 lua/flutter-tools/lsp/utils.lua diff --git a/lua/flutter-tools/lsp/color/init.lua b/lua/flutter-tools/lsp/color/init.lua index 988bfbe8..746dbbdd 100644 --- a/lua/flutter-tools/lsp/color/init.lua +++ b/lua/flutter-tools/lsp/color/init.lua @@ -1,15 +1,16 @@ local M = {} +local lazy = require("flutter-tools.lazy") +local lsp_utils = lazy.require("flutter-tools.lsp.utils") ---@module "flutter-tools.lsp.utils" + function M.document_color() local params = { textDocument = vim.lsp.util.make_text_document_params(), } - local clients = vim.lsp.get_active_clients({ name = "dartls" }) - for _, client in ipairs(clients) do - if client.server_capabilities.colorProvider then - client.request("textDocument/documentColor", params, nil, 0) - end + local client = lsp_utils.get_dartls_client() + if client and client.server_capabilities.colorProvider then + client.request("textDocument/documentColor", params, nil, 0) end end diff --git a/lua/flutter-tools/lsp/init.lua b/lua/flutter-tools/lsp/init.lua index 5a3506eb..6ae15ffa 100644 --- a/lua/flutter-tools/lsp/init.lua +++ b/lua/flutter-tools/lsp/init.lua @@ -2,6 +2,7 @@ local lazy = require("flutter-tools.lazy") local utils = lazy.require("flutter-tools.utils") ---@module "flutter-tools.utils" local path = lazy.require("flutter-tools.utils.path") ---@module "flutter-tools.utils.path" local color = lazy.require("flutter-tools.lsp.color") ---@module "flutter-tools.lsp.color" +local lsp_utils = lazy.require("flutter-tools.lsp.utils") ---@module "flutter-tools.lsp.utils" local api = vim.api local lsp = vim.lsp @@ -9,7 +10,6 @@ local fmt = string.format local fs = vim.fs local FILETYPE = "dart" -local SERVER_NAME = "dartls" local ROOT_PATTERNS = { ".git", "pubspec.yaml" } local M = { @@ -117,10 +117,7 @@ local function get_defaults(opts) end function M.restart() - local client = utils.find( - vim.lsp.get_active_clients(), - function(client) return client.name == SERVER_NAME end - ) + local client = lsp_utils.get_dartls_client() if client then local bufs = lsp.get_buffers_by_client_id(client.id) client.stop() @@ -131,31 +128,17 @@ function M.restart() end end ----@param server_name string? ----@return lsp.Client? -local function get_dartls_client(server_name) - server_name = server_name or SERVER_NAME - return lsp.get_active_clients({ name = server_name })[1] -end - ---@return string? function M.get_lsp_root_dir() - local client = get_dartls_client() + local client = lsp_utils.get_dartls_client() return client and client.config.root_dir or nil end -- FIXME: I'm not sure how to correctly wait till a server is ready before -- sending this request. Ideally we would wait till the server is ready. M.document_color = function() - local active_clients = vim.tbl_map(function(c) return c.id end, vim.lsp.get_active_clients()) - local dartls = get_dartls_client() - if - dartls - and vim.tbl_contains(active_clients, dartls.id) - and dartls.server_capabilities.colorProvider - then - color.document_color() - end + local client = lsp_utils.get_dartls_client() + if client and client.server_capabilities.colorProvider then color.document_color() end end M.on_document_color = color.on_document_color @@ -163,7 +146,7 @@ function M.dart_lsp_super() local conf = require("flutter-tools.config") local user_config = conf.lsp local debug_log = create_debug_log(user_config.debug) - local client = get_dartls_client() + local client = lsp_utils.get_dartls_client() if client == nil then debug_log("No active dartls server found") return @@ -176,7 +159,7 @@ function M.dart_reanalyze() lsp.buf_request(0, "dart/reanalyze") end ---@param user_config table ---@param callback fun(table) local function get_server_config(user_config, callback) - local config = utils.merge({ name = SERVER_NAME }, user_config, { "color" }) + local config = utils.merge({ name = lsp_utils.SERVER_NAME }, user_config, { "color" }) local executable = require("flutter-tools.executable") --- TODO: if a user specifies a command we do not need to call executable.get executable.get(function(paths) diff --git a/lua/flutter-tools/lsp/rename.lua b/lua/flutter-tools/lsp/rename.lua index a54c554d..24e13769 100644 --- a/lua/flutter-tools/lsp/rename.lua +++ b/lua/flutter-tools/lsp/rename.lua @@ -1,9 +1,11 @@ local M = {} -local api = vim.api -local util = vim.lsp.util local lazy = require("flutter-tools.lazy") local path = lazy.require("flutter-tools.utils.path") ---@module "flutter-tools.utils.path" +local lsp_utils = lazy.require("flutter-tools.lsp.utils") ---@module "flutter-tools.lsp.utils" + +local api = vim.api +local util = vim.lsp.util --- Computes a filename for a given class name (convert from PascalCase to snake_case). local function file_name_for_class_name(class_name) @@ -38,11 +40,7 @@ end function M.rename(new_name, options) options = options or {} local bufnr = options.bufnr or api.nvim_get_current_buf() - local clients = vim.lsp.get_active_clients({ - bufnr = bufnr, - name = "dartls", - }) - local client = clients[1] + local client = lsp_utils.get_dartls_client(bufnr) if not client then -- Fallback to default rename function if language server is not dartls vim.lsp.buf.rename(new_name, options) diff --git a/lua/flutter-tools/lsp/utils.lua b/lua/flutter-tools/lsp/utils.lua new file mode 100644 index 00000000..3933174a --- /dev/null +++ b/lua/flutter-tools/lsp/utils.lua @@ -0,0 +1,13 @@ +local M = {} + +local lsp = vim.lsp + +M.SERVER_NAME = "dartls" + +---@param bufnr number? +---@return lsp.Client? +function M.get_dartls_client(bufnr) + return lsp.get_active_clients({ name = M.SERVER_NAME, bufnr = bufnr })[1] +end + +return M From 52c90fae772481aeac4521e06af47583957646de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tautvydas=20S=CC=8Cidlauskas?= Date: Thu, 6 Apr 2023 19:59:10 +0300 Subject: [PATCH 4/7] refact: use plugin notify, code style --- lua/flutter-tools/lsp/rename.lua | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/lua/flutter-tools/lsp/rename.lua b/lua/flutter-tools/lsp/rename.lua index 24e13769..5ee74155 100644 --- a/lua/flutter-tools/lsp/rename.lua +++ b/lua/flutter-tools/lsp/rename.lua @@ -1,11 +1,14 @@ local M = {} local lazy = require("flutter-tools.lazy") -local path = lazy.require("flutter-tools.utils.path") ---@module "flutter-tools.utils.path" local lsp_utils = lazy.require("flutter-tools.lsp.utils") ---@module "flutter-tools.lsp.utils" +local path = lazy.require("flutter-tools.utils.path") ---@module "flutter-tools.utils.path" +local ui = lazy.require("flutter-tools.ui") ---@module "flutter-tools.ui" local api = vim.api local util = vim.lsp.util +local lsp = vim.lsp +local fn = vim.fn --- Computes a filename for a given class name (convert from PascalCase to snake_case). local function file_name_for_class_name(class_name) @@ -18,16 +21,16 @@ local function file_name_for_class_name(class_name) end local function will_rename_files(old_name, new_name, callback) - local params = vim.lsp.util.make_position_params() + local params = lsp.util.make_position_params() if not new_name then return end local file_change = { newUri = vim.uri_from_fname(new_name), oldUri = vim.uri_from_fname(old_name), } params.files = { file_change } - vim.lsp.buf_request(0, "workspace/willRenameFiles", params, function(err, result) + lsp.buf_request(0, "workspace/willRenameFiles", params, function(err, result) if err then - vim.notify(err.message or "Error on getting lsp rename results!") + ui.notify(err.message or "Error on getting lsp rename results!", ui.ERROR) return end callback(result) @@ -43,15 +46,14 @@ function M.rename(new_name, options) local client = lsp_utils.get_dartls_client(bufnr) if not client then -- Fallback to default rename function if language server is not dartls - vim.lsp.buf.rename(new_name, options) - return + return lsp.buf.rename(new_name, options) end local win = api.nvim_get_current_win() -- Compute early to account for cursor movements after going async - local cword = vim.fn.expand("") - local actual_file_name = vim.fn.expand("%:t") + local cword = fn.expand("") + local actual_file_name = fn.expand("%:t") local old_computed_filename = file_name_for_class_name(cword) local is_file_rename = old_computed_filename == actual_file_name @@ -69,23 +71,22 @@ function M.rename(new_name, options) local function rename(name, will_rename_files_result) local params = util.make_position_params(win, client.offset_encoding) params.newName = name - local handler = client.handlers["textDocument/rename"] - or vim.lsp.handlers["textDocument/rename"] + local handler = client.handlers["textDocument/rename"] or lsp.handlers["textDocument/rename"] client.request("textDocument/rename", params, function(...) handler(...) if will_rename_files_result then -- the `will_rename_files_result` contains all the places we need to update imports -- so we apply those edits. - vim.lsp.util.apply_workspace_edit(will_rename_files_result, client.offset_encoding) + lsp.util.apply_workspace_edit(will_rename_files_result, client.offset_encoding) end end, bufnr) end local function rename_fix_imports(name) if is_file_rename then - local old_file_path = vim.fn.expand("%:p") + local old_file_path = fn.expand("%:p") local new_filename = file_name_for_class_name(name) - local actual_file_head = vim.fn.expand("%:p:h") + local actual_file_head = fn.expand("%:p:h") local new_file_path = path.join(actual_file_head, new_filename) will_rename_files(old_file_path, new_file_path, function(result) rename(name, result) end) else @@ -99,7 +100,7 @@ function M.rename(new_name, options) if err or result == nil then local msg = err and ("Error on prepareRename: " .. (err.message or "")) or "Nothing to rename" - vim.notify(msg, vim.log.levels.INFO) + ui.notify(msg, ui.INFO) return end From 50110403d5992e45d7fd446cc03934724f7964ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tautvydas=20S=CC=8Cidlauskas?= Date: Thu, 6 Apr 2023 20:09:59 +0300 Subject: [PATCH 5/7] refact: extract ui.input function --- lua/flutter-tools/lsp/commands.lua | 14 +++++--------- lua/flutter-tools/lsp/rename.lua | 16 +++++----------- lua/flutter-tools/ui.lua | 4 ++++ 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/lua/flutter-tools/lsp/commands.lua b/lua/flutter-tools/lsp/commands.lua index 687c4118..22a7c977 100644 --- a/lua/flutter-tools/lsp/commands.lua +++ b/lua/flutter-tools/lsp/commands.lua @@ -1,5 +1,8 @@ local M = {} +local lazy = require("flutter-tools.lazy") +local ui = lazy.require("flutter-tools.ui") ---@module "flutter-tools.ui" + function M.refactor_perform(command, ctx) local client = vim.lsp.get_client_by_id(ctx.client_id) @@ -24,8 +27,7 @@ function M.refactor_perform(command, ctx) prompt = prompt, default = default, } - - local on_confirm = function(name) + ui.input(opts, function(name) if not name then return end -- The 6th argument is the additional options of the refactor command. -- For the extract method/local variable/widget commands, we can specify an optional `name` option. @@ -33,13 +35,7 @@ function M.refactor_perform(command, ctx) local optionsIndex = 6 command.arguments[optionsIndex] = { name = name } client.request("workspace/executeCommand", command) - end - if vim.ui and vim.ui.input then - vim.ui.input(opts, on_confirm) - else - local input = vim.fn.input(opts) - if #input > 0 then on_confirm(input) end - end + end) end return M diff --git a/lua/flutter-tools/lsp/rename.lua b/lua/flutter-tools/lsp/rename.lua index 5ee74155..b05a7e36 100644 --- a/lua/flutter-tools/lsp/rename.lua +++ b/lua/flutter-tools/lsp/rename.lua @@ -75,8 +75,7 @@ function M.rename(new_name, options) client.request("textDocument/rename", params, function(...) handler(...) if will_rename_files_result then - -- the `will_rename_files_result` contains all the places we need to update imports - -- so we apply those edits. + -- `will_rename_files_result` contains all the places we need to update imports, so we apply those edits. lsp.util.apply_workspace_edit(will_rename_files_result, client.offset_encoding) end end, bufnr) @@ -104,14 +103,9 @@ function M.rename(new_name, options) return end - if new_name then - rename_fix_imports(new_name) - return - end + if new_name then return rename_fix_imports(new_name) end - local prompt_opts = { - prompt = "New Name: ", - } + local prompt_opts = { prompt = "New Name: " } -- result: Range | { range: Range, placeholder: string } if result.placeholder then prompt_opts.default = result.placeholder @@ -122,7 +116,7 @@ function M.rename(new_name, options) else prompt_opts.default = cword end - vim.ui.input(prompt_opts, function(input) + ui.input(prompt_opts, function(input) if not input or #input == 0 then return end rename_fix_imports(input) end) @@ -138,7 +132,7 @@ function M.rename(new_name, options) prompt = "New Name: ", default = cword, } - vim.ui.input(prompt_opts, function(input) + ui.input(prompt_opts, function(input) if not input or #input == 0 then return end rename_fix_imports(input) end) diff --git a/lua/flutter-tools/ui.lua b/lua/flutter-tools/ui.lua index 5111503d..4cac3918 100644 --- a/lua/flutter-tools/ui.lua +++ b/lua/flutter-tools/ui.lua @@ -74,6 +74,10 @@ M.notify = function(msg, level, opts) }) end +---@param opts table +---@param on_confirm function +M.input = function(opts, on_confirm) vim.ui.input(opts, on_confirm) end + --- @param items SelectionEntry[] --- @param title string --- @param on_select fun(item: SelectionEntry) From 8228df5ca88f609b44d886bde0317ed778847d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tautvydas=20S=CC=8Cidlauskas?= Date: Fri, 7 Apr 2023 09:11:46 +0300 Subject: [PATCH 6/7] refact: add comments, rename, simplify code --- lua/flutter-tools/lsp/rename.lua | 64 +++++++++++++++++--------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/lua/flutter-tools/lsp/rename.lua b/lua/flutter-tools/lsp/rename.lua index b05a7e36..95fd9dd7 100644 --- a/lua/flutter-tools/lsp/rename.lua +++ b/lua/flutter-tools/lsp/rename.lua @@ -9,17 +9,23 @@ local api = vim.api local util = vim.lsp.util local lsp = vim.lsp local fn = vim.fn +local fs = vim.fs --- Computes a filename for a given class name (convert from PascalCase to snake_case). -local function file_name_for_class_name(class_name) +---@param class_name string +---@return string? +local function convert_to_file_name(class_name) local starts_uppercase = class_name:find("^%u") - if not starts_uppercase then return nil end + if not starts_uppercase then return end local file_name = class_name:gsub("(%u)", "_%1"):lower() -- Removes first underscore file_name = file_name:sub(2) return file_name .. ".dart" end +---@param old_name string +---@param new_name string +---@param callback function local function will_rename_files(old_name, new_name, callback) local params = lsp.util.make_position_params() if not new_name then return end @@ -30,8 +36,7 @@ local function will_rename_files(old_name, new_name, callback) params.files = { file_change } lsp.buf_request(0, "workspace/willRenameFiles", params, function(err, result) if err then - ui.notify(err.message or "Error on getting lsp rename results!", ui.ERROR) - return + return ui.notify(err.message or "Error on getting lsp rename results!", ui.ERROR) end callback(result) end) @@ -39,7 +44,8 @@ end --- Call this function when you want rename class or anything else. --- If file will be renamed too, this function will update imports. ---- Function has same signature as `vim.lsp.buf.rename()` function and can be used instead of it. +--- This is a modificated version of `vim.lsp.buf.rename()` function and can be used instead of it. +--- Original version: https://github.com/neovim/neovim/blob/0bc323850410df4c3c1dd8fabded9d2000189270/runtime/lua/vim/lsp/buf.lua#L271 function M.rename(new_name, options) options = options or {} local bufnr = options.bufnr or api.nvim_get_current_buf() @@ -52,15 +58,20 @@ function M.rename(new_name, options) local win = api.nvim_get_current_win() -- Compute early to account for cursor movements after going async - local cword = fn.expand("") - local actual_file_name = fn.expand("%:t") - local old_computed_filename = file_name_for_class_name(cword) - local is_file_rename = old_computed_filename == actual_file_name + local word_under_cursor = fn.expand("") + local current_file_path = api.nvim_buf_get_name(bufnr) + local current_file_name = fs.basename(current_file_path) + local filename_from_class_name = convert_to_file_name(word_under_cursor) + local is_file_rename = filename_from_class_name == current_file_name + ---@param range table + ---@param offset_encoding string local function get_text_at_range(range, offset_encoding) return api.nvim_buf_get_text( bufnr, range.start.line, + -- Private method that may be not stable. + -- Source in case of changes: https://github.com/neovim/neovim/blob/0bc323850410df4c3c1dd8fabded9d2000189270/runtime/lua/vim/lsp/util.lua#L2152 util._get_line_byte_from_position(bufnr, range.start, offset_encoding), range["end"].line, util._get_line_byte_from_position(bufnr, range["end"], offset_encoding), @@ -68,26 +79,25 @@ function M.rename(new_name, options) )[1] end - local function rename(name, will_rename_files_result) + ---@param name string the name of the thing + ---@param result table | nil the result from the call to will rename + local function rename(name, result) local params = util.make_position_params(win, client.offset_encoding) params.newName = name local handler = client.handlers["textDocument/rename"] or lsp.handlers["textDocument/rename"] client.request("textDocument/rename", params, function(...) handler(...) - if will_rename_files_result then - -- `will_rename_files_result` contains all the places we need to update imports, so we apply those edits. - lsp.util.apply_workspace_edit(will_rename_files_result, client.offset_encoding) - end + if result then lsp.util.apply_workspace_edit(result, client.offset_encoding) end end, bufnr) end + ---@param name string local function rename_fix_imports(name) if is_file_rename then - local old_file_path = fn.expand("%:p") - local new_filename = file_name_for_class_name(name) - local actual_file_head = fn.expand("%:p:h") - local new_file_path = path.join(actual_file_head, new_filename) - will_rename_files(old_file_path, new_file_path, function(result) rename(name, result) end) + local new_filename = convert_to_file_name(name) + local new_file_path = path.join(fs.dirname(current_file_path), new_filename) + + will_rename_files(current_file_path, new_file_path, function(result) rename(name, result) end) else rename(name) end @@ -96,11 +106,10 @@ function M.rename(new_name, options) if client.supports_method("textDocument/prepareRename") then local params = util.make_position_params(win, client.offset_encoding) client.request("textDocument/prepareRename", params, function(err, result) - if err or result == nil then - local msg = err and ("Error on prepareRename: " .. (err.message or "")) + if err or not result then + local msg = err and ("Error on prepareRename: %s"):format(err.message) or "Nothing to rename" - ui.notify(msg, ui.INFO) - return + return ui.notify(msg, ui.INFO) end if new_name then return rename_fix_imports(new_name) end @@ -114,7 +123,7 @@ function M.rename(new_name, options) elseif result.range then prompt_opts.default = get_text_at_range(result.range, client.offset_encoding) else - prompt_opts.default = cword + prompt_opts.default = word_under_cursor end ui.input(prompt_opts, function(input) if not input or #input == 0 then return end @@ -123,14 +132,11 @@ function M.rename(new_name, options) end, bufnr) else assert(client.supports_method("textDocument/rename"), "Client must support textDocument/rename") - if new_name then - rename_fix_imports(new_name) - return - end + if new_name then return rename_fix_imports(new_name) end local prompt_opts = { prompt = "New Name: ", - default = cword, + default = word_under_cursor, } ui.input(prompt_opts, function(input) if not input or #input == 0 then return end From b2dbaefd4755522b928ca27724757c3124f0c4ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tautvydas=20S=CC=8Cidlauskas?= Date: Fri, 7 Apr 2023 10:06:47 +0300 Subject: [PATCH 7/7] fix: enable custom rename only if `renameFilesWithClasses="always"` --- README.md | 2 +- lua/flutter-tools/lsp/rename.lua | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 42f2138d..b3fe665d 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ require("flutter-tools").setup {} -- use defaults - `FlutterLspRestart` - This command restarts the dart language server, and is intended for situations where it begins to work incorrectly. - `FlutterSuper` - Go to super class, method using custom LSP method `dart/textDocument/super`. - `FlutterReanalyze` - Forces LSP server reanalyze using custom LSP method `dart/reanalyze`. -- `FlutterRename` - Renames and updates imports if needed. +- `FlutterRename` - Renames and updates imports if `lsp.settings.renameFilesWithClasses == "always"`
diff --git a/lua/flutter-tools/lsp/rename.lua b/lua/flutter-tools/lsp/rename.lua index 95fd9dd7..2198bfc4 100644 --- a/lua/flutter-tools/lsp/rename.lua +++ b/lua/flutter-tools/lsp/rename.lua @@ -1,6 +1,7 @@ local M = {} local lazy = require("flutter-tools.lazy") +local config = lazy.require("flutter-tools.config") ---@module "flutter-tools.config" local lsp_utils = lazy.require("flutter-tools.lsp.utils") ---@module "flutter-tools.lsp.utils" local path = lazy.require("flutter-tools.utils.path") ---@module "flutter-tools.utils.path" local ui = lazy.require("flutter-tools.ui") ---@module "flutter-tools.ui" @@ -50,8 +51,9 @@ function M.rename(new_name, options) options = options or {} local bufnr = options.bufnr or api.nvim_get_current_buf() local client = lsp_utils.get_dartls_client(bufnr) - if not client then + if not client or config.lsp.settings.renameFilesWithClasses ~= "always" then -- Fallback to default rename function if language server is not dartls + -- or if user doesn't want to rename files on class rename. return lsp.buf.rename(new_name, options) end