From 2b031591f945d3c9c4c5796151aa84c0fcb91a42 Mon Sep 17 00:00:00 2001 From: Gabriel Porto Date: Sat, 21 Jun 2025 17:41:58 -0300 Subject: [PATCH] feat: improves project root detection by always going up the directory tree until we find a project root marker --- lua/flutter-tools/commands.lua | 41 ++++-------------------- lua/flutter-tools/lsp/init.lua | 12 +++++-- lua/flutter-tools/lsp/utils.lua | 12 +++++++ lua/flutter-tools/utils/config_utils.lua | 22 +++++++++++++ lua/flutter-tools/utils/init.lua | 4 +-- lua/flutter-tools/utils/path.lua | 26 +++++++++++++++ 6 files changed, 79 insertions(+), 38 deletions(-) create mode 100644 lua/flutter-tools/utils/config_utils.lua diff --git a/lua/flutter-tools/commands.lua b/lua/flutter-tools/commands.lua index e5d982e4..a723e0a7 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) @@ -333,7 +306,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) @@ -446,7 +419,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()) @@ -482,7 +455,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 }) @@ -590,7 +563,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 }) @@ -621,7 +594,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/lsp/init.lua b/lua/flutter-tools/lsp/init.lua index 9a1e8949..0a9cfe2f 100644 --- a/lua/flutter-tools/lsp/init.lua +++ b/lua/flutter-tools/lsp/init.lua @@ -166,7 +166,15 @@ function M.restart() end ---@return string? -function M.get_lsp_root_dir() +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) + or nil + + 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 end @@ -260,7 +268,7 @@ function M.attach() if not is_valid_path(buffer_path) then return end get_server_config(user_config, function(c) - c.root_dir = M.get_lsp_root_dir() + c.root_dir = M.get_project_root_dir() or fs.dirname(fs.find(conf.root_patterns, { path = buffer_path, upward = true, diff --git a/lua/flutter-tools/lsp/utils.lua b/lua/flutter-tools/lsp/utils.lua index 30c3fd41..2e421dac 100644 --- a/lua/flutter-tools/lsp/utils.lua +++ b/lua/flutter-tools/lsp/utils.lua @@ -11,4 +11,16 @@ local get_clients = vim.fn.has("nvim-0.10") == 1 and lsp.get_clients or lsp.get_ ---@return vim.lsp.Client? function M.get_dartls_client(bufnr) return get_clients({ name = M.SERVER_NAME, bufnr = bufnr })[1] 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..babdf5dd 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 diff --git a/lua/flutter-tools/utils/path.lua b/lua/flutter-tools/utils/path.lua index 6e76890f..42cef251 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) @@ -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