From c3d14f62a0901bb10e5140c4eeb68fe5e1a3a55c Mon Sep 17 00:00:00 2001 From: Gabriel Porto Date: Wed, 14 May 2025 09:35:08 -0300 Subject: [PATCH 1/9] fix(fvm/windows): append .bat extension on fvm command --- lua/flutter-tools/commands.lua | 44 ++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/lua/flutter-tools/commands.lua b/lua/flutter-tools/commands.lua index b3a027cc..50c363b4 100644 --- a/lua/flutter-tools/commands.lua +++ b/lua/flutter-tools/commands.lua @@ -31,6 +31,8 @@ local current_device = nil ---@type flutter.Runner? local runner = nil +local fvm_name = path.is_windows and "fvm.bat" or "fvm" + local function use_debugger_runner(force_debug) if force_debug or config.debugger.enabled then local dap_ok, _ = pcall(require, "dap") @@ -190,9 +192,9 @@ end local function get_absolute_path(input_path) -- Check if the provided path is an absolute path if - vim.fn.isdirectory(input_path) == 1 - and not input_path:match("^/") - and not input_path:match("^%a:[/\\]") + vim.fn.isdirectory(input_path) == 1 + and not input_path:match("^/") + and not input_path:match("^%a:[/\\]") then -- It's a relative path, so expand it to an absolute path local absolute_path = vim.fn.fnamemodify(input_path, ":p") @@ -235,23 +237,23 @@ local function has_flutter_dependency_in_pubspec(cwd) if not pubspec then return default_has_flutter_dependency end --https://github.com/Dart-Code/Dart-Code/blob/43914cd2709d77668e19a4edf3500f996d5c307b/src/shared/utils/fs.ts#L183 return ( - pubspec.dependencies - and ( - pubspec.dependencies.flutter - or pubspec.dependencies.flutter_test - or pubspec.dependencies.sky_engine - or pubspec.dependencies.flutter_goldens - ) - ) - or ( - pubspec.devDependencies - and ( - pubspec.devDependencies.flutter - or pubspec.devDependencies.flutter_test - or pubspec.devDependencies.sky_engine - or pubspec.devDependencies.flutter_goldens + pubspec.dependencies + and ( + pubspec.dependencies.flutter + or pubspec.dependencies.flutter_test + or pubspec.dependencies.sky_engine + or pubspec.dependencies.flutter_goldens + ) + ) + or ( + pubspec.devDependencies + and ( + pubspec.devDependencies.flutter + or pubspec.devDependencies.flutter_test + or pubspec.devDependencies.sky_engine + or pubspec.devDependencies.flutter_goldens + ) ) - ) end ---@param opts RunOpts @@ -494,7 +496,7 @@ local fvm_list_job = nil --- Returns table<{name: string, status: active|global|nil}> function M.fvm_list(callback) if not fvm_list_job then - fvm_list_job = Job:new({ command = "fvm", args = { "api", "list" } }) + fvm_list_job = Job:new({ command = fvm_name, args = { "api", "list" } }) fvm_list_job:after_success(vim.schedule_wrap(function(j) local out = j:result() @@ -533,7 +535,7 @@ local fvm_use_job = nil function M.fvm_use(sdk_name) if not fvm_use_job then - fvm_use_job = Job:new({ command = "fvm", args = { "use", sdk_name } }) + fvm_use_job = Job:new({ command = fvm_name, args = { "use", sdk_name } }) fvm_use_job:after_success(vim.schedule_wrap(function(j) ui.notify(utils.join(j:result())) From 3c3816fa8ecb6eb6b3bebfb5db2a3b80bf5357aa Mon Sep 17 00:00:00 2001 From: Gabriel Porto Date: Wed, 14 May 2025 14:41:25 -0300 Subject: [PATCH 2/9] fix(fvm/monorepo): better project root detection and monorepo support for fvm --- ftplugin/dart/init.lua | 2 - lua/flutter-tools.lua | 7 +++ lua/flutter-tools/commands.lua | 41 +++-------------- lua/flutter-tools/executable.lua | 30 +++++------- lua/flutter-tools/lsp/fvm_utils.lua | 32 +++++++++++++ lua/flutter-tools/lsp/init.lua | 58 ++++++++++++++---------- lua/flutter-tools/lsp/utils.lua | 23 +++++++++- lua/flutter-tools/utils/config_utils.lua | 22 +++++++++ lua/flutter-tools/utils/init.lua | 18 +++++++- lua/flutter-tools/utils/path.lua | 28 +++++++++++- 10 files changed, 178 insertions(+), 83 deletions(-) create mode 100644 lua/flutter-tools/lsp/fvm_utils.lua create mode 100644 lua/flutter-tools/utils/config_utils.lua diff --git a/ftplugin/dart/init.lua b/ftplugin/dart/init.lua index a5f64427..fd2c3420 100644 --- a/ftplugin/dart/init.lua +++ b/ftplugin/dart/init.lua @@ -2,8 +2,6 @@ if vim.b.flutter_tools_did_ftplugin then return end vim.b.flutter_tools_did_ftplugin = 1 -require("flutter-tools.lsp").attach() - vim.opt_local.comments = [[sO:*\ -,mO:*\ \ ,exO:*/,s1:/*,mb:*,ex:*/,:///,://]] vim.opt_local.commentstring = [[//%s]] vim.opt.includeexpr = "v:lua.require('flutter-tools.resolve_url').resolve_url(v:fname)" diff --git a/lua/flutter-tools.lua b/lua/flutter-tools.lua index ffd31281..a6c93625 100644 --- a/lua/flutter-tools.lua +++ b/lua/flutter-tools.lua @@ -116,6 +116,13 @@ local function setup_autocommands() pattern = { "*" }, callback = function() dev_tools.stop() end, }) + autocmd({ "BufReadPost", "BufFilePost", "BufEnter" }, { + group = AUGROUP, + pattern = { "*.dart" }, + callback = function() + lsp.attach() + end + }) end ---@param opts flutter.ProjectConfig diff --git a/lua/flutter-tools/commands.lua b/lua/flutter-tools/commands.lua index 50c363b4..9bb14aac 100644 --- a/lua/flutter-tools/commands.lua +++ b/lua/flutter-tools/commands.lua @@ -12,6 +12,7 @@ local debugger_runner = lazy.require("flutter-tools.runners.debugger_runner") -- local path = lazy.require("flutter-tools.utils.path") ---@module "flutter-tools.utils.path" local dev_log = lazy.require("flutter-tools.log") ---@module "flutter-tools.log" local parser = lazy.require("flutter-tools.utils.yaml_parser") +local config_utils = lazy.require("flutter-tools.utils.config_utils") ---@module "flutter-tools.utils.config_utils" local M = {} @@ -189,34 +190,6 @@ local function get_device_from_args(args) end end -local function get_absolute_path(input_path) - -- Check if the provided path is an absolute path - if - vim.fn.isdirectory(input_path) == 1 - and not input_path:match("^/") - and not input_path:match("^%a:[/\\]") - then - -- It's a relative path, so expand it to an absolute path - local absolute_path = vim.fn.fnamemodify(input_path, ":p") - return absolute_path - else - -- It's already an absolute path - return input_path - end -end - ----@param project_conf flutter.ProjectConfig? -local function get_cwd(project_conf) - if project_conf and project_conf.cwd then - local resolved_path = get_absolute_path(project_conf.cwd) - if not vim.loop.fs_stat(resolved_path) then - return ui.notify("Provided cwd does not exist: " .. resolved_path, ui.ERROR) - end - return resolved_path - end - return lsp.get_lsp_root_dir() -end - --@return table? local function parse_yaml(str) local ok, yaml = pcall(parser.parse, str) @@ -276,7 +249,7 @@ local function run(opts, project_conf, launch_config) project_conf.pre_run_callback(callback_args) end end - local cwd = get_cwd(project_conf) + local cwd = config_utils.get_cwd(project_conf) -- To determinate if the project is a flutter project we need to check if the pubspec.yaml -- file has a flutter dependency in it. We need to get cwd first to pick correct pubspec.yaml file. local is_flutter_project = has_flutter_dependency_in_pubspec(cwd) @@ -322,7 +295,7 @@ local function attach(opts) local args = opts.cli_args or opts.args or {} if not use_debugger_runner() then table.insert(args, 1, "attach") end - local cwd = get_cwd() + local cwd = config_utils.get_cwd() ui.notify("Attaching flutter project...") runner = use_debugger_runner() and debugger_runner or job_runner runner:attach(paths, args, cwd, on_run_data, on_run_exit) @@ -435,7 +408,7 @@ function M.pub_get() command = cmd, args = { "pub", "get" }, -- stylua: ignore - cwd = lsp.get_lsp_root_dir() --[[@as string]], + cwd = lsp.get_project_root_dir() --[[@as string]], }) pub_get_job:after_success(vim.schedule_wrap(function(j) on_pub_get(j:result()) @@ -471,7 +444,7 @@ function M.pub_upgrade(cmd_args) command = cmd, args = args, -- stylua: ignore - cwd = lsp.get_lsp_root_dir() --[[@as string]], + cwd = lsp.get_project_root_dir() --[[@as string]], }) pub_upgrade_job:after_success(vim.schedule_wrap(function(j) ui.notify(utils.join(j:result()), nil, { timeout = notify_timeout }) @@ -579,7 +552,7 @@ function M.install() command = cmd, args = args, -- stylua: ignore - cwd = lsp.get_lsp_root_dir() --[[@as string]], + cwd = lsp.get_project_root_dir() --[[@as string]], }) install_job:after_success(vim.schedule_wrap(function(j) ui.notify(utils.join(j:result()), nil, { timeout = notify_timeout }) @@ -610,7 +583,7 @@ function M.uninstall() command = cmd, args = args, -- stylua: ignore - cwd = lsp.get_lsp_root_dir() --[[@as string]], + cwd = lsp.get_project_root_dir() --[[@as string]], }) uninstall_job:after_success(vim.schedule_wrap(function(j) ui.notify(utils.join(j:result()), nil, { timeout = notify_timeout }) diff --git a/lua/flutter-tools/executable.lua b/lua/flutter-tools/executable.lua index 854cca17..a2c5b28e 100644 --- a/lua/flutter-tools/executable.lua +++ b/lua/flutter-tools/executable.lua @@ -3,11 +3,10 @@ local utils = lazy.require("flutter-tools.utils") ---@module "flutter-tools.util 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 config = lazy.require("flutter-tools.config") ---@module "flutter-tools.config" +local fvm_utils = lazy.require("flutter-tools.lsp.fvm_utils") ---@module "flutter-tools.lsp.fvm_utils" local Job = require("plenary.job") local fn = vim.fn -local fs = vim.fs -local luv = vim.loop local M = {} @@ -49,9 +48,10 @@ local function _flutter_sdk_dart_bin(flutter_sdk) end ---Get paths for flutter and dart based on the binary locations ----@return table +---@return table? local function get_default_binaries() local flutter_bin = fn.resolve(fn.exepath("flutter")) + if #flutter_bin <= 0 then return nil end return { flutter_bin = flutter_bin, dart_bin = fn.resolve(fn.exepath("dart")), @@ -66,7 +66,7 @@ function M.reset_paths() _paths = nil end ---Execute user's lookup command and pass it to the job callback ---@param lookup_cmd string ----@param callback fun(p: string, t: table?) +---@param callback fun(t: table?) ---@return table? local function path_from_lookup_cmd(lookup_cmd, callback) local paths = {} @@ -99,15 +99,7 @@ local function path_from_lookup_cmd(lookup_cmd, callback) job:start() end -local function _flutter_bin_from_fvm() - local fvm_root = - fs.dirname(fs.find(".fvm", { path = luv.cwd(), upward = true, type = "directory" })[1]) - local binary_name = path.is_windows and "flutter.bat" or "flutter" - local flutter_bin_symlink = path.join(fvm_root, ".fvm", "flutter_sdk", "bin", binary_name) - flutter_bin_symlink = fn.exepath(flutter_bin_symlink) - local flutter_bin = luv.fs_realpath(flutter_bin_symlink) - if path.exists(flutter_bin_symlink) and path.exists(flutter_bin) then return flutter_bin end -end + ---Fetch the paths to the users binaries. ---@param callback fun(paths: table) @@ -115,12 +107,13 @@ end function M.get(callback) if _paths then return callback(_paths) end if config.fvm then - local flutter_bin = _flutter_bin_from_fvm() - if flutter_bin then + local fvm_root = fvm_utils.find_fvm_root() + local flutter_bin = fvm_utils.flutter_bin_from_fvm(fvm_root) + if fvm_root and flutter_bin then _paths = { flutter_bin = flutter_bin, flutter_sdk = _flutter_sdk_root(flutter_bin), - fvm = true, + fvm_dir = fvm_root, } _paths.dart_sdk = _dart_sdk_root(_paths) _paths.dart_bin = _flutter_sdk_dart_bin(_paths.flutter_sdk) @@ -144,8 +137,9 @@ function M.get(callback) end) end - if not _paths then - _paths = get_default_binaries() + local paths = get_default_binaries() + if not _paths and paths then + _paths = paths _paths.dart_sdk = _dart_sdk_root(_paths) if _paths.flutter_sdk then _paths.dart_bin = _flutter_sdk_dart_bin(_paths.flutter_sdk) end end diff --git a/lua/flutter-tools/lsp/fvm_utils.lua b/lua/flutter-tools/lsp/fvm_utils.lua new file mode 100644 index 00000000..ab47eeef --- /dev/null +++ b/lua/flutter-tools/lsp/fvm_utils.lua @@ -0,0 +1,32 @@ +local M = {} + +local lazy = require("flutter-tools.lazy") + +local fn = vim.fn +local luv = vim.loop + +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 config_utils = lazy.require("flutter-tools.utils.config_utils") ---@module "flutter-tools.utils.config_utils" + + +--- Gets the FVM root directory by traversing upwards +--- @returns string? +function M.find_fvm_root() + local current_path = path.current_buffer_path(); + local search_path = lsp_utils.is_valid_path(current_path) and current_path or config_utils.get_cwd() + return search_path and path.find_root({ ".fvm" }, search_path) +end + +--- Gets the flutter binary from fvm root folder +--- @param fvm_root string fvm root folder +--- @return string? +function M.flutter_bin_from_fvm(fvm_root) + local binary_name = path.is_windows and "flutter.bat" or "flutter" + local flutter_bin_symlink = path.join(fvm_root, ".fvm", "flutter_sdk", "bin", binary_name) + flutter_bin_symlink = fn.exepath(flutter_bin_symlink) + local flutter_bin = luv.fs_realpath(flutter_bin_symlink) + if path.exists(flutter_bin_symlink) and path.exists(flutter_bin) then return flutter_bin end +end + +return M diff --git a/lua/flutter-tools/lsp/init.lua b/lua/flutter-tools/lsp/init.lua index 9a1e8949..58d44b25 100644 --- a/lua/flutter-tools/lsp/init.lua +++ b/lua/flutter-tools/lsp/init.lua @@ -3,11 +3,11 @@ local utils = lazy.require("flutter-tools.utils") ---@module "flutter-tools.util 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 fvm_utils = lazy.require("flutter-tools.lsp.fvm_utils") ---@module "flutter-tools.lsp.fvm_utils" local api = vim.api local lsp = vim.lsp local fmt = string.format -local fs = vim.fs local FILETYPE = "dart" @@ -166,9 +166,16 @@ function M.restart() end ---@return string? -function M.get_lsp_root_dir() - local client = lsp_utils.get_dartls_client() - return client and client.config.root_dir or nil +function M.get_project_root_dir() + local conf = require("flutter-tools.config") + local current_buffer_path = lsp_utils.current_buffer_path_if_valid() + + if (current_buffer_path == nil) then + local client = lsp_utils.get_dartls_client() + return client and client.config.root_dir or nil + end + + return path.find_root(conf.root_patterns, current_buffer_path) or current_buffer_path end -- FIXME: I'm not sure how to correctly wait till a server is ready before @@ -200,7 +207,7 @@ function M.dart_lsp_super() uri = vim.uri_from_bufnr(0), -- gets URI of current buffer }, position = { - line = lsp_line, -- 0-based line number + line = lsp_line, -- 0-based line number character = lsp_col, -- 0-based character position }, } @@ -210,12 +217,14 @@ end function M.dart_reanalyze() lsp.buf_request(0, "dart/reanalyze") end ---@param user_config table ----@param callback fun(table) +---@param callback fun(table, table) local function get_server_config(user_config, callback) local config = utils.merge({ name = lsp_utils.SERVER_NAME }, user_config, { "color" }) local executable = require("flutter-tools.executable") + executable.reset_paths() --- TODO: if a user specifies a command we do not need to call executable.get executable.get(function(paths) + if paths == nil then return end local defaults = get_defaults({ flutter_sdk = paths.flutter_sdk }) local root_path = paths.dart_sdk local debug_log = create_debug_log(user_config.debug) @@ -233,20 +242,22 @@ local function get_server_config(user_config, callback) config.on_init = function(client, _) return client.notify("workspace/didChangeConfiguration", { settings = config.settings }) end - callback(config) + -- TODO: flag something such that we only call attach on exit that has been flagged to + -- re attach. + config.on_exit = function() + if not M.pending_reattach then + return + end + M.pending_reattach = false + -- vim.schedule does not work, it executes attach too soon and + -- instead of creating a new client, the lsp implementation tries + -- to use the old, stopped client. + vim.defer_fn(M.attach, 0) + end + callback(config, paths) end) end ---- Checks if buffer path is valid for attaching LSP -local function is_valid_path(buffer_path) - if buffer_path == "" then return false end - - local start_index, _, uri_prefix = buffer_path:find("^(%w+://).*") - -- Do not attach LSP if file URI prefix is not file. - -- For example LSP will not be attached for diffview:// or fugitive:// buffers. - return not start_index or uri_prefix == "file://" -end - ---This was heavily inspired by nvim-metals implementation of the attach functionality function M.attach() local conf = require("flutter-tools.config") @@ -255,16 +266,15 @@ function M.attach() debug_log("attaching LSP") local buf = api.nvim_get_current_buf() + if lsp_utils.get_dartls_client(buf) ~= nil then return end + local buffer_path = api.nvim_buf_get_name(buf) - if not is_valid_path(buffer_path) then return end + if not lsp_utils.is_valid_path(buffer_path) then return end - get_server_config(user_config, function(c) - c.root_dir = M.get_lsp_root_dir() - or fs.dirname(fs.find(conf.root_patterns, { - path = buffer_path, - upward = true, - })[1]) + get_server_config(user_config, function(c, paths) + c.root_dir = paths.fvm_dir + or M.get_project_root_dir() vim.lsp.start(c) end) end diff --git a/lua/flutter-tools/lsp/utils.lua b/lua/flutter-tools/lsp/utils.lua index 30c3fd41..411d161c 100644 --- a/lua/flutter-tools/lsp/utils.lua +++ b/lua/flutter-tools/lsp/utils.lua @@ -1,14 +1,33 @@ local M = {} +local lazy = require("flutter-tools.lazy") + local lsp = vim.lsp +local get_clients = vim.fn.has("nvim-0.10") == 1 and lsp.get_clients or lsp.get_active_clients +local utils = lazy.require("flutter-tools.utils") ---@module "flutter-tools.utils" + M.SERVER_NAME = "dartls" -- TODO: Remove after compatibility with Neovim=0.9 is dropped -local get_clients = vim.fn.has("nvim-0.10") == 1 and lsp.get_clients or lsp.get_active_clients ---@param bufnr number? ---@return vim.lsp.Client? -function M.get_dartls_client(bufnr) return get_clients({ name = M.SERVER_NAME, bufnr = bufnr })[1] end +function M.get_dartls_client(bufnr) + local clients = get_clients({ name = M.SERVER_NAME, bufnr = bufnr }) + return utils.find(clients, function(c) return not c:is_stopped() end) +end + +--- Checks if buffer path is valid for attaching LSP +--- @param buffer_path string +--- @return boolean +function M.is_valid_path(buffer_path) + if buffer_path == "" then return false end + + local start_index, _, uri_prefix = buffer_path:find("^(%w+://).*") + -- Do not attach LSP if file URI prefix is not file. + -- For example LSP will not be attached for diffview:// or fugitive:// buffers. + return not start_index or uri_prefix == "file://" +end return M diff --git a/lua/flutter-tools/utils/config_utils.lua b/lua/flutter-tools/utils/config_utils.lua new file mode 100644 index 00000000..551b62cf --- /dev/null +++ b/lua/flutter-tools/utils/config_utils.lua @@ -0,0 +1,22 @@ +local M = {} + +local lazy = require("flutter-tools.lazy") +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 lsp = lazy.require("flutter-tools.lsp") ---@module "flutter-tools.utils" + +--- Gets the appropriate cwd +---@param project_conf flutter.ProjectConfig? +---@returns string? +function M.get_cwd(project_conf) + if project_conf and project_conf.cwd then + local resolved_path = path.get_absolute_path(project_conf.cwd) + if not vim.loop.fs_stat(resolved_path) then + return ui.notify("Provided cwd does not exist: " .. resolved_path, ui.ERROR) + end + return resolved_path + end + return lsp.get_project_root_dir() +end + +return M diff --git a/lua/flutter-tools/utils/init.lua b/lua/flutter-tools/utils/init.lua index 167960d9..fbc0a118 100644 --- a/lua/flutter-tools/utils/init.lua +++ b/lua/flutter-tools/utils/init.lua @@ -44,8 +44,8 @@ end ---Find an item in a list based on a compare function ---@generic T ---@param compare fun(item: T): boolean ----@param list `T` ----@return `T`? +---@param list T[] +---@return T? function M.find(list, compare) for _, item in ipairs(list) do if compare(item) then return item end @@ -151,4 +151,18 @@ M.islist = vim.fn.has("nvim-0.10") == 1 and vim.islist or vim.tbl_islist local flatten = function(t) return vim.iter(t):flatten():totable() end M.flatten = vim.fn.has("nvim-0.11") == 1 and flatten or vim.tbl_flatten +---@generic T +---@param t T[] +---@param t2 T[] +---@param compare fun(a: T, b: T): boolean +function M.compare(t, t2, compare) + if #t2 ~= #t then return false end + for index, _ in ipairs(t) do + if not compare(t[index], t2[index]) then + return false + end + end + return true +end + return M diff --git a/lua/flutter-tools/utils/path.lua b/lua/flutter-tools/utils/path.lua index 6e76890f..bbfdaecd 100644 --- a/lua/flutter-tools/utils/path.lua +++ b/lua/flutter-tools/utils/path.lua @@ -3,6 +3,7 @@ local lazy = require("flutter-tools.lazy") local utils = lazy.require("flutter-tools.utils") ---@module "flutter-tools.utils" local luv = vim.loop +local api = vim.api local M = {} function M.exists(filename) @@ -53,7 +54,7 @@ end ---@return string function M.join(...) local result = - table.concat(utils.flatten({ ... }), M.path_sep):gsub(M.path_sep .. "+", M.path_sep) + table.concat(utils.flatten({ ... }), M.path_sep):gsub(M.path_sep .. "+", M.path_sep) return result end @@ -82,7 +83,10 @@ function M.iterate_parents(path) return it, path, path end +---@param root string? +---@param path string? function M.is_descendant(root, path) + if not root then return false end if not path then return false end local function cb(dir, _) return dir == root end @@ -113,4 +117,26 @@ function M.find_root(patterns, startpath) return M.search_ancestors(startpath, matcher) end +function M.current_buffer_path() + local current_buffer = api.nvim_get_current_buf() + local current_buffer_path = api.nvim_buf_get_name(current_buffer) + return current_buffer_path +end + +function M.get_absolute_path(input_path) + -- Check if the provided path is an absolute path + if + vim.fn.isdirectory(input_path) == 1 + and not input_path:match("^/") + and not input_path:match("^%a:[/\\]") + then + -- It's a relative path, so expand it to an absolute path + local absolute_path = vim.fn.fnamemodify(input_path, ":p") + return absolute_path + else + -- It's already an absolute path + return input_path + end +end + return M From ba2ad40dfc820c4e88e7684304b14a79671fee4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tautvydas=20=C5=A0idlauskas?= Date: Wed, 28 May 2025 22:00:28 +0300 Subject: [PATCH 3/9] feat: add possibility to set default run args (#471) --- README.md | 1 + lua/flutter-tools.lua | 4 ++-- lua/flutter-tools/commands.lua | 11 +++++++++++ lua/flutter-tools/config.lua | 30 +++++++++++++++++++++++++++++- lua/flutter-tools/ui.lua | 8 +++++++- 5 files changed, 50 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c4b5bf5f..c60fbe2c 100644 --- a/README.md +++ b/README.md @@ -240,6 +240,7 @@ require("flutter-tools").setup { flutter_lookup_cmd = nil, -- example "dirname $(which flutter)" or "asdf where flutter" root_patterns = { ".git", "pubspec.yaml" }, -- patterns to find the root of your flutter project fvm = false, -- takes priority over path, uses /.fvm/flutter_sdk if enabled + default_run_args= nil, -- Default options for run command (i.e `{ flutter = "--no-version-check" }`). Configured separately for `dart run` and `flutter run`. widget_guides = { enabled = false, }, diff --git a/lua/flutter-tools.lua b/lua/flutter-tools.lua index a6c93625..904eee27 100644 --- a/lua/flutter-tools.lua +++ b/lua/flutter-tools.lua @@ -125,14 +125,14 @@ local function setup_autocommands() }) end ----@param opts flutter.ProjectConfig +---@param opts flutter.ProjectConfig | flutter.ProjectConfig[] Project-specific configuration function M.setup_project(opts) config.setup_project(opts) start() end ---Entry point for this plugin ----@param user_config table +---@param user_config flutter.Config Configuration options for flutter-tools function M.setup(user_config) config.set(user_config) setup_autocommands() diff --git a/lua/flutter-tools/commands.lua b/lua/flutter-tools/commands.lua index 9bb14aac..a56ce616 100644 --- a/lua/flutter-tools/commands.lua +++ b/lua/flutter-tools/commands.lua @@ -253,10 +253,21 @@ local function run(opts, project_conf, launch_config) -- To determinate if the project is a flutter project we need to check if the pubspec.yaml -- file has a flutter dependency in it. We need to get cwd first to pick correct pubspec.yaml file. local is_flutter_project = has_flutter_dependency_in_pubspec(cwd) + local default_run_args = config.default_run_args + local run_args if is_flutter_project then ui.notify("Starting flutter project...") + if default_run_args then run_args = default_run_args.flutter end else ui.notify("Starting dart project...") + if default_run_args then run_args = default_run_args.dart end + end + if run_args then + if type(run_args) == "string" then + vim.list_extend(args, vim.split(run_args, " ")) + elseif type(run_args) == "table" then + vim.list_extend(args, run_args) + end end runner = use_debugger_runner(opts.force_debug) and debugger_runner or job_runner runner:run( diff --git a/lua/flutter-tools/config.lua b/lua/flutter-tools/config.lua index e8676e65..576d9b95 100644 --- a/lua/flutter-tools/config.lua +++ b/lua/flutter-tools/config.lua @@ -15,6 +15,34 @@ local utils = lazy.require("flutter-tools.utils") ---@module "flutter-tools.util ---@field web_port? string ---@field cwd? string full path of current working directory, defaults to LSP root ---@field additional_args? string[] additional arguments to pass to the flutter run command +--- +---@class flutter.DevLogOpts +---@field filter? fun(data: string): boolean +---@field enabled? boolean +---@field notify_errors? boolean +---@field focus_on_open? boolean +---@field open_cmd? string +--- +---@class flutter.RunArgsOpts +---@field flutter? table|string -- options applied to `flutter run` command +---@field dart? table|string -- options appliert to `dart run` command +--- +---@class flutter.Config +---@field flutter_path? string Path to the Flutter SDK +---@field flutter_lookup_cmd? string Command to find Flutter SDK +---@field pre_run_callback? fun(opts: table) Function called before running Flutter +---@field root_patterns? string[] Patterns to find project root +---@field fvm? boolean Whether to use FVM (Flutter Version Manager) +---@field default_run_args? flutter.RunArgsOpts Default options for run command +---@field widget_guides? {enabled: boolean, debug: boolean} +---@field ui? {border: string} +---@field decorations? {statusline: {app_version: boolean, device: boolean, project_config: boolean}} +---@field debugger? {enabled: boolean, exception_breakpoints?: table, evaluate_to_string_in_debug_views?: boolean, register_configurations?: fun(paths: table)} +---@field closing_tags? {highlight: string, prefix: string, priority: number, enabled: boolean} +---@field lsp? {debug?: number, color?: {enabled: boolean, background: boolean, foreground: boolean, virtual_text: boolean, virtual_text_str: string, background_color?: string}, settings?: table} +---@field outline? {auto_open: boolean, open_cmd?: string} +---@field dev_log? flutter.DevLogOpts +---@field dev_tools? {autostart: boolean, auto_open_browser: boolean} local M = {} @@ -67,13 +95,13 @@ M.debug_levels = { DEBUG = 1, WARN = 2, } - local config = { flutter_path = nil, flutter_lookup_cmd = get_default_lookup(), pre_run_callback = nil, root_patterns = { ".git", "pubspec.yaml" }, fvm = false, + default_run_args = nil, widget_guides = { enabled = false, debug = false, diff --git a/lua/flutter-tools/ui.lua b/lua/flutter-tools/ui.lua index 07b3b45c..b4e85798 100644 --- a/lua/flutter-tools/ui.lua +++ b/lua/flutter-tools/ui.lua @@ -10,6 +10,12 @@ local entry_type = { ---@generic T ---@alias SelectionEntry {text: string, type: EntryType, data: T} +---@class flutter.WindowOpts +---@field open_cmd? string Command to open the window +---@field filename? string Name to give the buffer +---@field filetype string Filetype to set for the buffer +---@field focus_on_open? boolean Whether to focus the window after opening + ---@enum local M = { ERROR = vim.log.levels.ERROR, @@ -129,7 +135,7 @@ function M.select(opts) end ---Create a split window ----@param opts table +---@param opts flutter.WindowOpts ---@param on_open fun(buf: integer, win: integer) ---@return nil function M.open_win(opts, on_open) From 139554539daba3406bf26b4eb601fda1cfb45239 Mon Sep 17 00:00:00 2001 From: Gabriel Porto Date: Sun, 1 Jun 2025 19:48:42 -0300 Subject: [PATCH 4/9] fix - fix incorrect method name and displaced comment --- lua/flutter-tools/lsp/init.lua | 25 +++++++------------------ lua/flutter-tools/lsp/utils.lua | 2 +- lua/flutter-tools/utils/init.lua | 14 -------------- 3 files changed, 8 insertions(+), 33 deletions(-) diff --git a/lua/flutter-tools/lsp/init.lua b/lua/flutter-tools/lsp/init.lua index 58d44b25..ed386727 100644 --- a/lua/flutter-tools/lsp/init.lua +++ b/lua/flutter-tools/lsp/init.lua @@ -3,7 +3,6 @@ local utils = lazy.require("flutter-tools.utils") ---@module "flutter-tools.util 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 fvm_utils = lazy.require("flutter-tools.lsp.fvm_utils") ---@module "flutter-tools.lsp.fvm_utils" local api = vim.api local lsp = vim.lsp @@ -168,14 +167,16 @@ end ---@return string? function M.get_project_root_dir() local conf = require("flutter-tools.config") - local current_buffer_path = lsp_utils.current_buffer_path_if_valid() + local current_buffer_path = path.current_buffer_path() + local root_path = lsp_utils.is_valid_path(current_buffer_path) and + path.find_root(conf.root_patterns, current_buffer_path) - if (current_buffer_path == nil) then - local client = lsp_utils.get_dartls_client() - return client and client.config.root_dir or nil + if (root_path ~= nil) then + return root_path end - return path.find_root(conf.root_patterns, current_buffer_path) or current_buffer_path + 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 @@ -242,18 +243,6 @@ local function get_server_config(user_config, callback) config.on_init = function(client, _) return client.notify("workspace/didChangeConfiguration", { settings = config.settings }) end - -- TODO: flag something such that we only call attach on exit that has been flagged to - -- re attach. - config.on_exit = function() - if not M.pending_reattach then - return - end - M.pending_reattach = false - -- vim.schedule does not work, it executes attach too soon and - -- instead of creating a new client, the lsp implementation tries - -- to use the old, stopped client. - vim.defer_fn(M.attach, 0) - end callback(config, paths) end) end diff --git a/lua/flutter-tools/lsp/utils.lua b/lua/flutter-tools/lsp/utils.lua index 411d161c..cf06108c 100644 --- a/lua/flutter-tools/lsp/utils.lua +++ b/lua/flutter-tools/lsp/utils.lua @@ -4,12 +4,12 @@ local lazy = require("flutter-tools.lazy") local lsp = vim.lsp +-- TODO: Remove after compatibility with Neovim=0.9 is dropped local get_clients = vim.fn.has("nvim-0.10") == 1 and lsp.get_clients or lsp.get_active_clients local utils = lazy.require("flutter-tools.utils") ---@module "flutter-tools.utils" M.SERVER_NAME = "dartls" --- TODO: Remove after compatibility with Neovim=0.9 is dropped ---@param bufnr number? ---@return vim.lsp.Client? diff --git a/lua/flutter-tools/utils/init.lua b/lua/flutter-tools/utils/init.lua index fbc0a118..babdf5dd 100644 --- a/lua/flutter-tools/utils/init.lua +++ b/lua/flutter-tools/utils/init.lua @@ -151,18 +151,4 @@ M.islist = vim.fn.has("nvim-0.10") == 1 and vim.islist or vim.tbl_islist local flatten = function(t) return vim.iter(t):flatten():totable() end M.flatten = vim.fn.has("nvim-0.11") == 1 and flatten or vim.tbl_flatten ----@generic T ----@param t T[] ----@param t2 T[] ----@param compare fun(a: T, b: T): boolean -function M.compare(t, t2, compare) - if #t2 ~= #t then return false end - for index, _ in ipairs(t) do - if not compare(t[index], t2[index]) then - return false - end - end - return true -end - return M From da7957108a9bd4004c639b3872dc986e0616a73b Mon Sep 17 00:00:00 2001 From: Gabriel Porto Date: Sun, 1 Jun 2025 20:56:17 -0300 Subject: [PATCH 5/9] fix - removes path caching when using fvm and removes paths reset function is no longer used --- lua/flutter-tools/commands.lua | 1 - lua/flutter-tools/executable.lua | 17 +++++++++-------- lua/flutter-tools/lsp/init.lua | 1 - 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/lua/flutter-tools/commands.lua b/lua/flutter-tools/commands.lua index a56ce616..6cea0adc 100644 --- a/lua/flutter-tools/commands.lua +++ b/lua/flutter-tools/commands.lua @@ -524,7 +524,6 @@ function M.fvm_use(sdk_name) fvm_use_job:after_success(vim.schedule_wrap(function(j) ui.notify(utils.join(j:result())) shutdown() - executable.reset_paths() lsp.restart() fvm_use_job = nil diff --git a/lua/flutter-tools/executable.lua b/lua/flutter-tools/executable.lua index a2c5b28e..3337562e 100644 --- a/lua/flutter-tools/executable.lua +++ b/lua/flutter-tools/executable.lua @@ -62,8 +62,6 @@ end ---@type table local _paths = nil -function M.reset_paths() _paths = nil end - ---Execute user's lookup command and pass it to the job callback ---@param lookup_cmd string ---@param callback fun(t: table?) @@ -105,22 +103,25 @@ end ---@param callback fun(paths: table) ---@return nil function M.get(callback) - if _paths then return callback(_paths) end if config.fvm then local fvm_root = fvm_utils.find_fvm_root() local flutter_bin = fvm_utils.flutter_bin_from_fvm(fvm_root) if fvm_root and flutter_bin then - _paths = { + -- TODO(kaerum): We currently don't cache fvm based paths + -- because we'd need a multiple entry based cache + -- that is somehow better than just traversing up + -- the directory tree looking for the nearest .fvm + local paths = { flutter_bin = flutter_bin, flutter_sdk = _flutter_sdk_root(flutter_bin), fvm_dir = fvm_root, } - _paths.dart_sdk = _dart_sdk_root(_paths) - _paths.dart_bin = _flutter_sdk_dart_bin(_paths.flutter_sdk) - return callback(_paths) + paths.dart_sdk = _dart_sdk_root(paths) + paths.dart_bin = _flutter_sdk_dart_bin(paths.flutter_sdk) + return callback(paths) end end - + if _paths then return callback(_paths) end if config.flutter_path then local flutter_path = fn.resolve(config.flutter_path) _paths = { flutter_bin = flutter_path, flutter_sdk = _flutter_sdk_root(flutter_path) } diff --git a/lua/flutter-tools/lsp/init.lua b/lua/flutter-tools/lsp/init.lua index ed386727..1209d736 100644 --- a/lua/flutter-tools/lsp/init.lua +++ b/lua/flutter-tools/lsp/init.lua @@ -222,7 +222,6 @@ function M.dart_reanalyze() lsp.buf_request(0, "dart/reanalyze") end local function get_server_config(user_config, callback) local config = utils.merge({ name = lsp_utils.SERVER_NAME }, user_config, { "color" }) local executable = require("flutter-tools.executable") - executable.reset_paths() --- TODO: if a user specifies a command we do not need to call executable.get executable.get(function(paths) if paths == nil then return end From 45697b694a93c540c6d305615ccc9549201b1fa2 Mon Sep 17 00:00:00 2001 From: Gabriel Porto Date: Sun, 1 Jun 2025 21:25:41 -0300 Subject: [PATCH 6/9] fix - formatting --- lua/flutter-tools.lua | 4 +--- lua/flutter-tools/commands.lua | 32 ++++++++++++++--------------- lua/flutter-tools/lsp/fvm_utils.lua | 6 +++--- lua/flutter-tools/lsp/init.lua | 13 +++++------- lua/flutter-tools/utils/path.lua | 8 ++++---- 5 files changed, 29 insertions(+), 34 deletions(-) diff --git a/lua/flutter-tools.lua b/lua/flutter-tools.lua index 904eee27..ce7d8980 100644 --- a/lua/flutter-tools.lua +++ b/lua/flutter-tools.lua @@ -119,9 +119,7 @@ local function setup_autocommands() autocmd({ "BufReadPost", "BufFilePost", "BufEnter" }, { group = AUGROUP, pattern = { "*.dart" }, - callback = function() - lsp.attach() - end + callback = function() lsp.attach() end, }) end diff --git a/lua/flutter-tools/commands.lua b/lua/flutter-tools/commands.lua index 6cea0adc..79ee971c 100644 --- a/lua/flutter-tools/commands.lua +++ b/lua/flutter-tools/commands.lua @@ -210,23 +210,23 @@ local function has_flutter_dependency_in_pubspec(cwd) if not pubspec then return default_has_flutter_dependency end --https://github.com/Dart-Code/Dart-Code/blob/43914cd2709d77668e19a4edf3500f996d5c307b/src/shared/utils/fs.ts#L183 return ( - pubspec.dependencies - and ( - pubspec.dependencies.flutter - or pubspec.dependencies.flutter_test - or pubspec.dependencies.sky_engine - or pubspec.dependencies.flutter_goldens - ) - ) - or ( - pubspec.devDependencies - and ( - pubspec.devDependencies.flutter - or pubspec.devDependencies.flutter_test - or pubspec.devDependencies.sky_engine - or pubspec.devDependencies.flutter_goldens - ) + pubspec.dependencies + and ( + pubspec.dependencies.flutter + or pubspec.dependencies.flutter_test + or pubspec.dependencies.sky_engine + or pubspec.dependencies.flutter_goldens + ) + ) + or ( + pubspec.devDependencies + and ( + pubspec.devDependencies.flutter + or pubspec.devDependencies.flutter_test + or pubspec.devDependencies.sky_engine + or pubspec.devDependencies.flutter_goldens ) + ) end ---@param opts RunOpts diff --git a/lua/flutter-tools/lsp/fvm_utils.lua b/lua/flutter-tools/lsp/fvm_utils.lua index ab47eeef..decb33e0 100644 --- a/lua/flutter-tools/lsp/fvm_utils.lua +++ b/lua/flutter-tools/lsp/fvm_utils.lua @@ -9,12 +9,12 @@ local lsp_utils = lazy.require("flutter-tools.lsp.utils") ---@module "flutter-to local path = lazy.require("flutter-tools.utils.path") ---@module "flutter-tools.utils.path" local config_utils = lazy.require("flutter-tools.utils.config_utils") ---@module "flutter-tools.utils.config_utils" - --- Gets the FVM root directory by traversing upwards --- @returns string? function M.find_fvm_root() - local current_path = path.current_buffer_path(); - local search_path = lsp_utils.is_valid_path(current_path) and current_path or config_utils.get_cwd() + local current_path = path.current_buffer_path() + local search_path = lsp_utils.is_valid_path(current_path) and current_path + or config_utils.get_cwd() return search_path and path.find_root({ ".fvm" }, search_path) end diff --git a/lua/flutter-tools/lsp/init.lua b/lua/flutter-tools/lsp/init.lua index 1209d736..4e34801b 100644 --- a/lua/flutter-tools/lsp/init.lua +++ b/lua/flutter-tools/lsp/init.lua @@ -168,12 +168,10 @@ end function M.get_project_root_dir() local conf = require("flutter-tools.config") local current_buffer_path = path.current_buffer_path() - local root_path = lsp_utils.is_valid_path(current_buffer_path) and - path.find_root(conf.root_patterns, current_buffer_path) + local root_path = lsp_utils.is_valid_path(current_buffer_path) + and path.find_root(conf.root_patterns, current_buffer_path) - if (root_path ~= nil) then - return root_path - end + if root_path ~= nil then return root_path end local client = lsp_utils.get_dartls_client() return client and client.config.root_dir or nil @@ -208,7 +206,7 @@ function M.dart_lsp_super() uri = vim.uri_from_bufnr(0), -- gets URI of current buffer }, position = { - line = lsp_line, -- 0-based line number + line = lsp_line, -- 0-based line number character = lsp_col, -- 0-based character position }, } @@ -261,8 +259,7 @@ function M.attach() if not lsp_utils.is_valid_path(buffer_path) then return end get_server_config(user_config, function(c, paths) - c.root_dir = paths.fvm_dir - or M.get_project_root_dir() + c.root_dir = paths.fvm_dir or M.get_project_root_dir() vim.lsp.start(c) end) end diff --git a/lua/flutter-tools/utils/path.lua b/lua/flutter-tools/utils/path.lua index bbfdaecd..42cef251 100644 --- a/lua/flutter-tools/utils/path.lua +++ b/lua/flutter-tools/utils/path.lua @@ -54,7 +54,7 @@ end ---@return string function M.join(...) local result = - table.concat(utils.flatten({ ... }), M.path_sep):gsub(M.path_sep .. "+", M.path_sep) + table.concat(utils.flatten({ ... }), M.path_sep):gsub(M.path_sep .. "+", M.path_sep) return result end @@ -126,9 +126,9 @@ end function M.get_absolute_path(input_path) -- Check if the provided path is an absolute path if - vim.fn.isdirectory(input_path) == 1 - and not input_path:match("^/") - and not input_path:match("^%a:[/\\]") + vim.fn.isdirectory(input_path) == 1 + and not input_path:match("^/") + and not input_path:match("^%a:[/\\]") then -- It's a relative path, so expand it to an absolute path local absolute_path = vim.fn.fnamemodify(input_path, ":p") From 6223759042537ee043d75c747244b1477373c33e Mon Sep 17 00:00:00 2001 From: Gabriel Porto Date: Sun, 1 Jun 2025 21:26:44 -0300 Subject: [PATCH 7/9] fix - more formatting --- lua/flutter-tools/executable.lua | 2 -- lua/flutter-tools/lsp/utils.lua | 1 - 2 files changed, 3 deletions(-) diff --git a/lua/flutter-tools/executable.lua b/lua/flutter-tools/executable.lua index 3337562e..1adc9326 100644 --- a/lua/flutter-tools/executable.lua +++ b/lua/flutter-tools/executable.lua @@ -97,8 +97,6 @@ local function path_from_lookup_cmd(lookup_cmd, callback) job:start() end - - ---Fetch the paths to the users binaries. ---@param callback fun(paths: table) ---@return nil diff --git a/lua/flutter-tools/lsp/utils.lua b/lua/flutter-tools/lsp/utils.lua index cf06108c..d4c82d7f 100644 --- a/lua/flutter-tools/lsp/utils.lua +++ b/lua/flutter-tools/lsp/utils.lua @@ -10,7 +10,6 @@ local utils = lazy.require("flutter-tools.utils") ---@module "flutter-tools.util M.SERVER_NAME = "dartls" - ---@param bufnr number? ---@return vim.lsp.Client? function M.get_dartls_client(bufnr) From 728a96c546bc0899887ca75c6cffbb8a749e5c39 Mon Sep 17 00:00:00 2001 From: Gabriel Porto Date: Tue, 3 Jun 2025 22:57:21 -0300 Subject: [PATCH 8/9] fix - misuse of ternary --- lua/flutter-tools/lsp/init.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/flutter-tools/lsp/init.lua b/lua/flutter-tools/lsp/init.lua index 4e34801b..1930b814 100644 --- a/lua/flutter-tools/lsp/init.lua +++ b/lua/flutter-tools/lsp/init.lua @@ -169,7 +169,8 @@ function M.get_project_root_dir() local conf = require("flutter-tools.config") local current_buffer_path = path.current_buffer_path() local root_path = lsp_utils.is_valid_path(current_buffer_path) - and path.find_root(conf.root_patterns, current_buffer_path) + and path.find_root(conf.root_patterns, current_buffer_path) + or nil if root_path ~= nil then return root_path end From 6f37e83cb457e9903ed4fedecb7cec18c481f793 Mon Sep 17 00:00:00 2001 From: Gabriel Porto Date: Thu, 12 Jun 2025 09:40:03 -0300 Subject: [PATCH 9/9] feat: expose web-port, uses the same client if they use the same version --- lua/flutter-tools/config.lua | 1 + lua/flutter-tools/lsp/init.lua | 36 +++++++++++++++++++++++++++------ lua/flutter-tools/lsp/utils.lua | 17 ++++++++++++++++ 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/lua/flutter-tools/config.lua b/lua/flutter-tools/config.lua index 576d9b95..0fb3dccc 100644 --- a/lua/flutter-tools/config.lua +++ b/lua/flutter-tools/config.lua @@ -137,6 +137,7 @@ local config = { virtual_text_str = "■", background_color = nil, }, + web_port = nil }, outline = setmetatable({ auto_open = false, diff --git a/lua/flutter-tools/lsp/init.lua b/lua/flutter-tools/lsp/init.lua index 1930b814..b30df18a 100644 --- a/lua/flutter-tools/lsp/init.lua +++ b/lua/flutter-tools/lsp/init.lua @@ -170,7 +170,7 @@ function M.get_project_root_dir() local current_buffer_path = path.current_buffer_path() local root_path = lsp_utils.is_valid_path(current_buffer_path) and path.find_root(conf.root_patterns, current_buffer_path) - or nil + or nil if root_path ~= nil then return root_path end @@ -207,7 +207,7 @@ function M.dart_lsp_super() uri = vim.uri_from_bufnr(0), -- gets URI of current buffer }, position = { - line = lsp_line, -- 0-based line number + line = lsp_line, -- 0-based line number character = lsp_col, -- 0-based character position }, } @@ -229,7 +229,13 @@ local function get_server_config(user_config, callback) local debug_log = create_debug_log(user_config.debug) debug_log(fmt("dart_sdk_path: %s", root_path)) - config.cmd = config.cmd or { paths.dart_bin, "language-server", "--protocol=lsp" } + local cmd = { paths.dart_bin, "language-server", "--protocol=lsp" } + + if (config.web_port) then + table.insert(cmd, "--port=" .. config.web_port) + end + + config.cmd = config.cmd or cmd config.filetypes = { FILETYPE } config.capabilities = merge_config(defaults.capabilities, config.capabilities) @@ -239,7 +245,7 @@ local function get_server_config(user_config, callback) config.commands = merge_config(defaults.commands, config.commands) config.on_init = function(client, _) - return client.notify("workspace/didChangeConfiguration", { settings = config.settings }) + return client:notify("workspace/didChangeConfiguration", { settings = config.settings }) end callback(config, paths) end) @@ -253,6 +259,13 @@ function M.attach() debug_log("attaching LSP") local buf = api.nvim_get_current_buf() + + local key = "dart_lsp_attaching" + + local err, attaching = pcall(vim.api.nvim_buf_get_var, buf, key) + if (err or attaching == true) then return end + vim.api.nvim_buf_set_var(buf, key, true) + if lsp_utils.get_dartls_client(buf) ~= nil then return end local buffer_path = api.nvim_buf_get_name(buf) @@ -260,8 +273,19 @@ function M.attach() if not lsp_utils.is_valid_path(buffer_path) then return end get_server_config(user_config, function(c, paths) - c.root_dir = paths.fvm_dir or M.get_project_root_dir() - vim.lsp.start(c) + local project_root = M.get_project_root_dir() + c.root_dir = paths.fvm_dir or project_root + vim.schedule(function() + local client = lsp_utils.get_dartls_client_for_version(c.cmd[1]) + if client == nil then + local id = vim.lsp.start(c) + client = vim.lsp.get_clients({ id = id })[1] + if client == nil then return end + end + client:_add_workspace_folder(project_root) + vim.lsp.buf_attach_client(buf, client.id) + end) + vim.api.nvim_buf_set_var(buf, key, false) end) end diff --git a/lua/flutter-tools/lsp/utils.lua b/lua/flutter-tools/lsp/utils.lua index d4c82d7f..64291e57 100644 --- a/lua/flutter-tools/lsp/utils.lua +++ b/lua/flutter-tools/lsp/utils.lua @@ -17,6 +17,23 @@ function M.get_dartls_client(bufnr) return utils.find(clients, function(c) return not c:is_stopped() end) end +function M.get_dartls_server() + local clients = get_clients({ name = M.SERVER_NAME }) + return utils.find(clients, function(c) return not c:is_stopped() end) +end + +---@param cmd string +---@return vim.lsp.Client? +function M.get_dartls_client_for_version(cmd) + local clients = get_clients({ name = M.SERVER_NAME }) + return utils.find(clients, function(c) + local isStopping = c:is_stopped() + if isStopping then return false end + local client_cmd = (c.config.cmd or {})[1] + return cmd == client_cmd + end) +end + --- Checks if buffer path is valid for attaching LSP --- @param buffer_path string --- @return boolean