Skip to content

Commit 3c3816f

Browse files
committed
fix(fvm/monorepo): better project root detection and monorepo support for fvm
1 parent c3d14f6 commit 3c3816f

File tree

10 files changed

+178
-83
lines changed

10 files changed

+178
-83
lines changed

ftplugin/dart/init.lua

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
if vim.b.flutter_tools_did_ftplugin then return end
33
vim.b.flutter_tools_did_ftplugin = 1
44

5-
require("flutter-tools.lsp").attach()
6-
75
vim.opt_local.comments = [[sO:*\ -,mO:*\ \ ,exO:*/,s1:/*,mb:*,ex:*/,:///,://]]
86
vim.opt_local.commentstring = [[//%s]]
97
vim.opt.includeexpr = "v:lua.require('flutter-tools.resolve_url').resolve_url(v:fname)"

lua/flutter-tools.lua

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@ local function setup_autocommands()
116116
pattern = { "*" },
117117
callback = function() dev_tools.stop() end,
118118
})
119+
autocmd({ "BufReadPost", "BufFilePost", "BufEnter" }, {
120+
group = AUGROUP,
121+
pattern = { "*.dart" },
122+
callback = function()
123+
lsp.attach()
124+
end
125+
})
119126
end
120127

121128
---@param opts flutter.ProjectConfig

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)
@@ -322,7 +295,7 @@ local function attach(opts)
322295
local args = opts.cli_args or opts.args or {}
323296
if not use_debugger_runner() then table.insert(args, 1, "attach") end
324297

325-
local cwd = get_cwd()
298+
local cwd = config_utils.get_cwd()
326299
ui.notify("Attaching flutter project...")
327300
runner = use_debugger_runner() and debugger_runner or job_runner
328301
runner:attach(paths, args, cwd, on_run_data, on_run_exit)
@@ -435,7 +408,7 @@ function M.pub_get()
435408
command = cmd,
436409
args = { "pub", "get" },
437410
-- stylua: ignore
438-
cwd = lsp.get_lsp_root_dir() --[[@as string]],
411+
cwd = lsp.get_project_root_dir() --[[@as string]],
439412
})
440413
pub_get_job:after_success(vim.schedule_wrap(function(j)
441414
on_pub_get(j:result())
@@ -471,7 +444,7 @@ function M.pub_upgrade(cmd_args)
471444
command = cmd,
472445
args = args,
473446
-- stylua: ignore
474-
cwd = lsp.get_lsp_root_dir() --[[@as string]],
447+
cwd = lsp.get_project_root_dir() --[[@as string]],
475448
})
476449
pub_upgrade_job:after_success(vim.schedule_wrap(function(j)
477450
ui.notify(utils.join(j:result()), nil, { timeout = notify_timeout })
@@ -579,7 +552,7 @@ function M.install()
579552
command = cmd,
580553
args = args,
581554
-- stylua: ignore
582-
cwd = lsp.get_lsp_root_dir() --[[@as string]],
555+
cwd = lsp.get_project_root_dir() --[[@as string]],
583556
})
584557
install_job:after_success(vim.schedule_wrap(function(j)
585558
ui.notify(utils.join(j:result()), nil, { timeout = notify_timeout })
@@ -610,7 +583,7 @@ function M.uninstall()
610583
command = cmd,
611584
args = args,
612585
-- stylua: ignore
613-
cwd = lsp.get_lsp_root_dir() --[[@as string]],
586+
cwd = lsp.get_project_root_dir() --[[@as string]],
614587
})
615588
uninstall_job:after_success(vim.schedule_wrap(function(j)
616589
ui.notify(utils.join(j:result()), nil, { timeout = notify_timeout })

lua/flutter-tools/executable.lua

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ local utils = lazy.require("flutter-tools.utils") ---@module "flutter-tools.util
33
local path = lazy.require("flutter-tools.utils.path") ---@module "flutter-tools.utils.path"
44
local ui = lazy.require("flutter-tools.ui") ---@module "flutter-tools.ui"
55
local config = lazy.require("flutter-tools.config") ---@module "flutter-tools.config"
6+
local fvm_utils = lazy.require("flutter-tools.lsp.fvm_utils") ---@module "flutter-tools.lsp.fvm_utils"
67
local Job = require("plenary.job")
78

89
local fn = vim.fn
9-
local fs = vim.fs
10-
local luv = vim.loop
1110

1211
local M = {}
1312

@@ -49,9 +48,10 @@ local function _flutter_sdk_dart_bin(flutter_sdk)
4948
end
5049

5150
---Get paths for flutter and dart based on the binary locations
52-
---@return table<string, string>
51+
---@return table<string, string>?
5352
local function get_default_binaries()
5453
local flutter_bin = fn.resolve(fn.exepath("flutter"))
54+
if #flutter_bin <= 0 then return nil end
5555
return {
5656
flutter_bin = flutter_bin,
5757
dart_bin = fn.resolve(fn.exepath("dart")),
@@ -66,7 +66,7 @@ function M.reset_paths() _paths = nil end
6666

6767
---Execute user's lookup command and pass it to the job callback
6868
---@param lookup_cmd string
69-
---@param callback fun(p: string, t: table<string, string>?)
69+
---@param callback fun(t: table<string, string>?)
7070
---@return table<string, string>?
7171
local function path_from_lookup_cmd(lookup_cmd, callback)
7272
local paths = {}
@@ -99,28 +99,21 @@ local function path_from_lookup_cmd(lookup_cmd, callback)
9999
job:start()
100100
end
101101

102-
local function _flutter_bin_from_fvm()
103-
local fvm_root =
104-
fs.dirname(fs.find(".fvm", { path = luv.cwd(), upward = true, type = "directory" })[1])
105-
local binary_name = path.is_windows and "flutter.bat" or "flutter"
106-
local flutter_bin_symlink = path.join(fvm_root, ".fvm", "flutter_sdk", "bin", binary_name)
107-
flutter_bin_symlink = fn.exepath(flutter_bin_symlink)
108-
local flutter_bin = luv.fs_realpath(flutter_bin_symlink)
109-
if path.exists(flutter_bin_symlink) and path.exists(flutter_bin) then return flutter_bin end
110-
end
102+
111103

112104
---Fetch the paths to the users binaries.
113105
---@param callback fun(paths: table<string, string>)
114106
---@return nil
115107
function M.get(callback)
116108
if _paths then return callback(_paths) end
117109
if config.fvm then
118-
local flutter_bin = _flutter_bin_from_fvm()
119-
if flutter_bin then
110+
local fvm_root = fvm_utils.find_fvm_root()
111+
local flutter_bin = fvm_utils.flutter_bin_from_fvm(fvm_root)
112+
if fvm_root and flutter_bin then
120113
_paths = {
121114
flutter_bin = flutter_bin,
122115
flutter_sdk = _flutter_sdk_root(flutter_bin),
123-
fvm = true,
116+
fvm_dir = fvm_root,
124117
}
125118
_paths.dart_sdk = _dart_sdk_root(_paths)
126119
_paths.dart_bin = _flutter_sdk_dart_bin(_paths.flutter_sdk)
@@ -144,8 +137,9 @@ function M.get(callback)
144137
end)
145138
end
146139

147-
if not _paths then
148-
_paths = get_default_binaries()
140+
local paths = get_default_binaries()
141+
if not _paths and paths then
142+
_paths = paths
149143
_paths.dart_sdk = _dart_sdk_root(_paths)
150144
if _paths.flutter_sdk then _paths.dart_bin = _flutter_sdk_dart_bin(_paths.flutter_sdk) end
151145
end
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
local M = {}
2+
3+
local lazy = require("flutter-tools.lazy")
4+
5+
local fn = vim.fn
6+
local luv = vim.loop
7+
8+
local lsp_utils = lazy.require("flutter-tools.lsp.utils") ---@module "flutter-tools.lsp.utils"
9+
local path = lazy.require("flutter-tools.utils.path") ---@module "flutter-tools.utils.path"
10+
local config_utils = lazy.require("flutter-tools.utils.config_utils") ---@module "flutter-tools.utils.config_utils"
11+
12+
13+
--- Gets the FVM root directory by traversing upwards
14+
--- @returns string?
15+
function M.find_fvm_root()
16+
local current_path = path.current_buffer_path();
17+
local search_path = lsp_utils.is_valid_path(current_path) and current_path or config_utils.get_cwd()
18+
return search_path and path.find_root({ ".fvm" }, search_path)
19+
end
20+
21+
--- Gets the flutter binary from fvm root folder
22+
--- @param fvm_root string fvm root folder
23+
--- @return string?
24+
function M.flutter_bin_from_fvm(fvm_root)
25+
local binary_name = path.is_windows and "flutter.bat" or "flutter"
26+
local flutter_bin_symlink = path.join(fvm_root, ".fvm", "flutter_sdk", "bin", binary_name)
27+
flutter_bin_symlink = fn.exepath(flutter_bin_symlink)
28+
local flutter_bin = luv.fs_realpath(flutter_bin_symlink)
29+
if path.exists(flutter_bin_symlink) and path.exists(flutter_bin) then return flutter_bin end
30+
end
31+
32+
return M

lua/flutter-tools/lsp/init.lua

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ local utils = lazy.require("flutter-tools.utils") ---@module "flutter-tools.util
33
local path = lazy.require("flutter-tools.utils.path") ---@module "flutter-tools.utils.path"
44
local color = lazy.require("flutter-tools.lsp.color") ---@module "flutter-tools.lsp.color"
55
local lsp_utils = lazy.require("flutter-tools.lsp.utils") ---@module "flutter-tools.lsp.utils"
6+
local fvm_utils = lazy.require("flutter-tools.lsp.fvm_utils") ---@module "flutter-tools.lsp.fvm_utils"
67

78
local api = vim.api
89
local lsp = vim.lsp
910
local fmt = string.format
10-
local fs = vim.fs
1111

1212
local FILETYPE = "dart"
1313

@@ -166,9 +166,16 @@ function M.restart()
166166
end
167167

168168
---@return string?
169-
function M.get_lsp_root_dir()
170-
local client = lsp_utils.get_dartls_client()
171-
return client and client.config.root_dir or nil
169+
function M.get_project_root_dir()
170+
local conf = require("flutter-tools.config")
171+
local current_buffer_path = lsp_utils.current_buffer_path_if_valid()
172+
173+
if (current_buffer_path == nil) then
174+
local client = lsp_utils.get_dartls_client()
175+
return client and client.config.root_dir or nil
176+
end
177+
178+
return path.find_root(conf.root_patterns, current_buffer_path) or current_buffer_path
172179
end
173180

174181
-- FIXME: I'm not sure how to correctly wait till a server is ready before
@@ -200,7 +207,7 @@ function M.dart_lsp_super()
200207
uri = vim.uri_from_bufnr(0), -- gets URI of current buffer
201208
},
202209
position = {
203-
line = lsp_line, -- 0-based line number
210+
line = lsp_line, -- 0-based line number
204211
character = lsp_col, -- 0-based character position
205212
},
206213
}
@@ -210,12 +217,14 @@ end
210217
function M.dart_reanalyze() lsp.buf_request(0, "dart/reanalyze") end
211218

212219
---@param user_config table
213-
---@param callback fun(table)
220+
---@param callback fun(table, table)
214221
local function get_server_config(user_config, callback)
215222
local config = utils.merge({ name = lsp_utils.SERVER_NAME }, user_config, { "color" })
216223
local executable = require("flutter-tools.executable")
224+
executable.reset_paths()
217225
--- TODO: if a user specifies a command we do not need to call executable.get
218226
executable.get(function(paths)
227+
if paths == nil then return end
219228
local defaults = get_defaults({ flutter_sdk = paths.flutter_sdk })
220229
local root_path = paths.dart_sdk
221230
local debug_log = create_debug_log(user_config.debug)
@@ -233,20 +242,22 @@ local function get_server_config(user_config, callback)
233242
config.on_init = function(client, _)
234243
return client.notify("workspace/didChangeConfiguration", { settings = config.settings })
235244
end
236-
callback(config)
245+
-- TODO: flag something such that we only call attach on exit that has been flagged to
246+
-- re attach.
247+
config.on_exit = function()
248+
if not M.pending_reattach then
249+
return
250+
end
251+
M.pending_reattach = false
252+
-- vim.schedule does not work, it executes attach too soon and
253+
-- instead of creating a new client, the lsp implementation tries
254+
-- to use the old, stopped client.
255+
vim.defer_fn(M.attach, 0)
256+
end
257+
callback(config, paths)
237258
end)
238259
end
239260

240-
--- Checks if buffer path is valid for attaching LSP
241-
local function is_valid_path(buffer_path)
242-
if buffer_path == "" then return false end
243-
244-
local start_index, _, uri_prefix = buffer_path:find("^(%w+://).*")
245-
-- Do not attach LSP if file URI prefix is not file.
246-
-- For example LSP will not be attached for diffview:// or fugitive:// buffers.
247-
return not start_index or uri_prefix == "file://"
248-
end
249-
250261
---This was heavily inspired by nvim-metals implementation of the attach functionality
251262
function M.attach()
252263
local conf = require("flutter-tools.config")
@@ -255,16 +266,15 @@ function M.attach()
255266
debug_log("attaching LSP")
256267

257268
local buf = api.nvim_get_current_buf()
269+
if lsp_utils.get_dartls_client(buf) ~= nil then return end
270+
258271
local buffer_path = api.nvim_buf_get_name(buf)
259272

260-
if not is_valid_path(buffer_path) then return end
273+
if not lsp_utils.is_valid_path(buffer_path) then return end
261274

262-
get_server_config(user_config, function(c)
263-
c.root_dir = M.get_lsp_root_dir()
264-
or fs.dirname(fs.find(conf.root_patterns, {
265-
path = buffer_path,
266-
upward = true,
267-
})[1])
275+
get_server_config(user_config, function(c, paths)
276+
c.root_dir = paths.fvm_dir
277+
or M.get_project_root_dir()
268278
vim.lsp.start(c)
269279
end)
270280
end

lua/flutter-tools/lsp/utils.lua

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,33 @@
11
local M = {}
22

3+
local lazy = require("flutter-tools.lazy")
4+
35
local lsp = vim.lsp
46

7+
local get_clients = vim.fn.has("nvim-0.10") == 1 and lsp.get_clients or lsp.get_active_clients
8+
local utils = lazy.require("flutter-tools.utils") ---@module "flutter-tools.utils"
9+
510
M.SERVER_NAME = "dartls"
611

712
-- TODO: Remove after compatibility with Neovim=0.9 is dropped
8-
local get_clients = vim.fn.has("nvim-0.10") == 1 and lsp.get_clients or lsp.get_active_clients
913

1014
---@param bufnr number?
1115
---@return vim.lsp.Client?
12-
function M.get_dartls_client(bufnr) return get_clients({ name = M.SERVER_NAME, bufnr = bufnr })[1] end
16+
function M.get_dartls_client(bufnr)
17+
local clients = get_clients({ name = M.SERVER_NAME, bufnr = bufnr })
18+
return utils.find(clients, function(c) return not c:is_stopped() end)
19+
end
20+
21+
--- Checks if buffer path is valid for attaching LSP
22+
--- @param buffer_path string
23+
--- @return boolean
24+
function M.is_valid_path(buffer_path)
25+
if buffer_path == "" then return false end
26+
27+
local start_index, _, uri_prefix = buffer_path:find("^(%w+://).*")
28+
-- Do not attach LSP if file URI prefix is not file.
29+
-- For example LSP will not be attached for diffview:// or fugitive:// buffers.
30+
return not start_index or uri_prefix == "file://"
31+
end
1332

1433
return M

0 commit comments

Comments
 (0)