Skip to content
Closed
2 changes: 0 additions & 2 deletions ftplugin/dart/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand Down
5 changes: 5 additions & 0 deletions lua/flutter-tools.lua
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ 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 | flutter.ProjectConfig[] Project-specific configuration
Expand Down
42 changes: 7 additions & 35 deletions lua/flutter-tools/commands.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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 })
Expand Down Expand Up @@ -551,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
Expand Down Expand Up @@ -590,7 +562,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 })
Expand Down Expand Up @@ -621,7 +593,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 })
Expand Down
1 change: 1 addition & 0 deletions lua/flutter-tools/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ local config = {
virtual_text_str = "■",
background_color = nil,
},
web_port = nil
},
outline = setmetatable({
auto_open = false,
Expand Down
47 changes: 20 additions & 27 deletions lua/flutter-tools/executable.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}

Expand Down Expand Up @@ -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<string, string>
---@return table<string, string>?
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")),
Expand All @@ -62,11 +62,9 @@ end
---@type table<string, string>
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(p: string, t: table<string, string>?)
---@param callback fun(t: table<string, string>?)
---@return table<string, string>?
local function path_from_lookup_cmd(lookup_cmd, callback)
local paths = {}
Expand Down Expand Up @@ -99,35 +97,29 @@ 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<string, string>)
---@return nil
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
_paths = {
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
-- 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 = true,
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) }
Expand All @@ -144,8 +136,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
Expand Down
32 changes: 32 additions & 0 deletions lua/flutter-tools/lsp/fvm_utils.lua
Original file line number Diff line number Diff line change
@@ -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
70 changes: 45 additions & 25 deletions lua/flutter-tools/lsp/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ local lsp_utils = lazy.require("flutter-tools.lsp.utils") ---@module "flutter-to
local api = vim.api
local lsp = vim.lsp
local fmt = string.format
local fs = vim.fs

local FILETYPE = "dart"

Expand Down Expand Up @@ -166,7 +165,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
Expand Down Expand Up @@ -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
},
}
Expand All @@ -210,18 +217,25 @@ 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")
--- 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)
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)
Expand All @@ -231,22 +245,12 @@ 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)
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")
Expand All @@ -255,17 +259,33 @@ 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)

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])
vim.lsp.start(c)
get_server_config(user_config, function(c, paths)
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

Expand Down
Loading
Loading