Skip to content

Commit 3a3f6f5

Browse files
authored
feat: improves project root detection by always going up the directory tree until we find a project root marker (#482)
1 parent ed9f78b commit 3a3f6f5

File tree

6 files changed

+79
-38
lines changed

6 files changed

+79
-38
lines changed

lua/flutter-tools/commands.lua

Lines changed: 7 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ local debugger_runner = lazy.require("flutter-tools.runners.debugger_runner") --
1212
local path = lazy.require("flutter-tools.utils.path") ---@module "flutter-tools.utils.path"
1313
local dev_log = lazy.require("flutter-tools.log") ---@module "flutter-tools.log"
1414
local parser = lazy.require("flutter-tools.utils.yaml_parser")
15+
local config_utils = lazy.require("flutter-tools.utils.config_utils") ---@module "flutter-tools.utils.config_utils"
1516

1617
local M = {}
1718

@@ -189,34 +190,6 @@ local function get_device_from_args(args)
189190
end
190191
end
191192

192-
local function get_absolute_path(input_path)
193-
-- Check if the provided path is an absolute path
194-
if
195-
vim.fn.isdirectory(input_path) == 1
196-
and not input_path:match("^/")
197-
and not input_path:match("^%a:[/\\]")
198-
then
199-
-- It's a relative path, so expand it to an absolute path
200-
local absolute_path = vim.fn.fnamemodify(input_path, ":p")
201-
return absolute_path
202-
else
203-
-- It's already an absolute path
204-
return input_path
205-
end
206-
end
207-
208-
---@param project_conf flutter.ProjectConfig?
209-
local function get_cwd(project_conf)
210-
if project_conf and project_conf.cwd then
211-
local resolved_path = get_absolute_path(project_conf.cwd)
212-
if not vim.loop.fs_stat(resolved_path) then
213-
return ui.notify("Provided cwd does not exist: " .. resolved_path, ui.ERROR)
214-
end
215-
return resolved_path
216-
end
217-
return lsp.get_lsp_root_dir()
218-
end
219-
220193
--@return table?
221194
local function parse_yaml(str)
222195
local ok, yaml = pcall(parser.parse, str)
@@ -276,7 +249,7 @@ local function run(opts, project_conf, launch_config)
276249
project_conf.pre_run_callback(callback_args)
277250
end
278251
end
279-
local cwd = get_cwd(project_conf)
252+
local cwd = config_utils.get_cwd(project_conf)
280253
-- To determinate if the project is a flutter project we need to check if the pubspec.yaml
281254
-- file has a flutter dependency in it. We need to get cwd first to pick correct pubspec.yaml file.
282255
local is_flutter_project = has_flutter_dependency_in_pubspec(cwd)
@@ -333,7 +306,7 @@ local function attach(opts)
333306
local args = opts.cli_args or opts.args or {}
334307
if not use_debugger_runner() then table.insert(args, 1, "attach") end
335308

336-
local cwd = get_cwd()
309+
local cwd = config_utils.get_cwd()
337310
ui.notify("Attaching flutter project...")
338311
runner = use_debugger_runner() and debugger_runner or job_runner
339312
runner:attach(paths, args, cwd, on_run_data, on_run_exit)
@@ -446,7 +419,7 @@ function M.pub_get()
446419
command = cmd,
447420
args = { "pub", "get" },
448421
-- stylua: ignore
449-
cwd = lsp.get_lsp_root_dir() --[[@as string]],
422+
cwd = lsp.get_project_root_dir() --[[@as string]],
450423
})
451424
pub_get_job:after_success(vim.schedule_wrap(function(j)
452425
on_pub_get(j:result())
@@ -482,7 +455,7 @@ function M.pub_upgrade(cmd_args)
482455
command = cmd,
483456
args = args,
484457
-- stylua: ignore
485-
cwd = lsp.get_lsp_root_dir() --[[@as string]],
458+
cwd = lsp.get_project_root_dir() --[[@as string]],
486459
})
487460
pub_upgrade_job:after_success(vim.schedule_wrap(function(j)
488461
ui.notify(utils.join(j:result()), nil, { timeout = notify_timeout })
@@ -590,7 +563,7 @@ function M.install()
590563
command = cmd,
591564
args = args,
592565
-- stylua: ignore
593-
cwd = lsp.get_lsp_root_dir() --[[@as string]],
566+
cwd = lsp.get_project_root_dir() --[[@as string]],
594567
})
595568
install_job:after_success(vim.schedule_wrap(function(j)
596569
ui.notify(utils.join(j:result()), nil, { timeout = notify_timeout })
@@ -621,7 +594,7 @@ function M.uninstall()
621594
command = cmd,
622595
args = args,
623596
-- stylua: ignore
624-
cwd = lsp.get_lsp_root_dir() --[[@as string]],
597+
cwd = lsp.get_project_root_dir() --[[@as string]],
625598
})
626599
uninstall_job:after_success(vim.schedule_wrap(function(j)
627600
ui.notify(utils.join(j:result()), nil, { timeout = notify_timeout })

lua/flutter-tools/lsp/init.lua

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,15 @@ function M.restart()
167167
end
168168

169169
---@return string?
170-
function M.get_lsp_root_dir()
170+
function M.get_project_root_dir()
171+
local conf = require("flutter-tools.config")
172+
local current_buffer_path = path.current_buffer_path()
173+
local root_path = lsp_utils.is_valid_path(current_buffer_path)
174+
and path.find_root(conf.root_patterns, current_buffer_path)
175+
or nil
176+
177+
if root_path ~= nil then return root_path end
178+
171179
local client = lsp_utils.get_dartls_client()
172180
return client and client.config.root_dir or nil
173181
end
@@ -277,7 +285,7 @@ function M.attach()
277285
if not is_valid_path(buffer_path) then return end
278286

279287
get_server_config(user_config, function(c)
280-
c.root_dir = M.get_lsp_root_dir()
288+
c.root_dir = M.get_project_root_dir()
281289
or fs.dirname(fs.find(conf.root_patterns, {
282290
path = buffer_path,
283291
upward = true,

lua/flutter-tools/lsp/utils.lua

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,16 @@ local get_clients = vim.fn.has("nvim-0.10") == 1 and lsp.get_clients or lsp.get_
1111
---@return vim.lsp.Client?
1212
function M.get_dartls_client(bufnr) return get_clients({ name = M.SERVER_NAME, bufnr = bufnr })[1] end
1313

14+
--- Checks if buffer path is valid for attaching LSP
15+
--- @param buffer_path string
16+
--- @return boolean
17+
function M.is_valid_path(buffer_path)
18+
if buffer_path == "" then return false end
19+
20+
local start_index, _, uri_prefix = buffer_path:find("^(%w+://).*")
21+
-- Do not attach LSP if file URI prefix is not file.
22+
-- For example LSP will not be attached for diffview:// or fugitive:// buffers.
23+
return not start_index or uri_prefix == "file://"
24+
end
25+
1426
return M
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
local M = {}
2+
3+
local lazy = require("flutter-tools.lazy")
4+
local path = lazy.require("flutter-tools.utils.path") ---@module "flutter-tools.utils.path"
5+
local ui = lazy.require("flutter-tools.ui") ---@module "flutter-tools.ui"
6+
local lsp = lazy.require("flutter-tools.lsp") ---@module "flutter-tools.utils"
7+
8+
--- Gets the appropriate cwd
9+
---@param project_conf flutter.ProjectConfig?
10+
---@returns string?
11+
function M.get_cwd(project_conf)
12+
if project_conf and project_conf.cwd then
13+
local resolved_path = path.get_absolute_path(project_conf.cwd)
14+
if not vim.loop.fs_stat(resolved_path) then
15+
return ui.notify("Provided cwd does not exist: " .. resolved_path, ui.ERROR)
16+
end
17+
return resolved_path
18+
end
19+
return lsp.get_project_root_dir()
20+
end
21+
22+
return M

lua/flutter-tools/utils/init.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ end
4444
---Find an item in a list based on a compare function
4545
---@generic T
4646
---@param compare fun(item: T): boolean
47-
---@param list `T`
48-
---@return `T`?
47+
---@param list T[]
48+
---@return T?
4949
function M.find(list, compare)
5050
for _, item in ipairs(list) do
5151
if compare(item) then return item end

lua/flutter-tools/utils/path.lua

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ local lazy = require("flutter-tools.lazy")
33
local utils = lazy.require("flutter-tools.utils") ---@module "flutter-tools.utils"
44

55
local luv = vim.loop
6+
local api = vim.api
67
local M = {}
78

89
function M.exists(filename)
@@ -82,7 +83,10 @@ function M.iterate_parents(path)
8283
return it, path, path
8384
end
8485

86+
---@param root string?
87+
---@param path string?
8588
function M.is_descendant(root, path)
89+
if not root then return false end
8690
if not path then return false end
8791

8892
local function cb(dir, _) return dir == root end
@@ -113,4 +117,26 @@ function M.find_root(patterns, startpath)
113117
return M.search_ancestors(startpath, matcher)
114118
end
115119

120+
function M.current_buffer_path()
121+
local current_buffer = api.nvim_get_current_buf()
122+
local current_buffer_path = api.nvim_buf_get_name(current_buffer)
123+
return current_buffer_path
124+
end
125+
126+
function M.get_absolute_path(input_path)
127+
-- Check if the provided path is an absolute path
128+
if
129+
vim.fn.isdirectory(input_path) == 1
130+
and not input_path:match("^/")
131+
and not input_path:match("^%a:[/\\]")
132+
then
133+
-- It's a relative path, so expand it to an absolute path
134+
local absolute_path = vim.fn.fnamemodify(input_path, ":p")
135+
return absolute_path
136+
else
137+
-- It's already an absolute path
138+
return input_path
139+
end
140+
end
141+
116142
return M

0 commit comments

Comments
 (0)