Skip to content

Commit cf92ff1

Browse files
authored
feat: add FlutterAttach command (#425)
1 parent a526c30 commit cf92ff1

File tree

5 files changed

+130
-23
lines changed

5 files changed

+130
-23
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ require("flutter-tools").setup {} -- use defaults
155155
- `FlutterReload` - Reload the running project.
156156
- `FlutterRestart` - Restart the current project.
157157
- `FlutterQuit` - Ends a running session.
158+
- `FlutterAttach` - Attach to a running app.
158159
- `FlutterDetach` - Ends a running session locally but keeps the process running on the device.
159160
- `FlutterOutlineToggle` - Toggle the outline window showing the widget tree for the given file.
160161
- `FlutterOutlineOpen` - Opens an outline window showing the widget tree for the given file.

lua/flutter-tools.lua

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ local function setup_commands()
2525
command("FlutterRun", function(data) commands.run_command(data.args) end, { nargs = "*" })
2626
command("FlutterDebug", function(data) commands.run_command(data.args) end, { nargs = "*" })
2727
command("FlutterLspRestart", lsp.restart)
28+
command("FlutterAttach", commands.attach)
2829
command("FlutterDetach", commands.detach)
2930
command("FlutterReload", commands.reload)
3031
command("FlutterRestart", commands.restart)

lua/flutter-tools/commands.lua

+23
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ local parser = lazy.require("flutter-tools.utils.yaml_parser")
1616
local M = {}
1717

1818
---@alias RunOpts {cli_args: string[]?, args: string[]?, device: Device?, force_debug: boolean?}
19+
---@alias AttachOpts {cli_args: string[]?, args: string[]?, device: Device?}
1920

2021
---@type table?
2122
local current_device = nil
@@ -25,6 +26,7 @@ local current_device = nil
2526
---@field run fun(runner: flutter.Runner, paths:table, args:table, cwd:string, on_run_data:fun(is_err:boolean, data:string), on_run_exit:fun(data:string[], args: table, project_conf: flutter.ProjectConfig?,launch_config: dap.Configuration?), is_flutter_project: boolean, project_conf: flutter.ProjectConfig?, launch_config: dap.Configuration?)
2627
---@field cleanup fun(funner: flutter.Runner)
2728
---@field send fun(runner: flutter.Runner, cmd:string, quiet: boolean?)
29+
---@field attach fun(runner: flutter.Runner, paths:table, args:table, cwd:string, on_run_data:fun(is_err:boolean, data:string), on_run_exit:fun(data:string[], args: table, project_conf: flutter.ProjectConfig?,launch_config: dap.Configuration?))
2830

2931
---@type flutter.Runner?
3032
local runner = nil
@@ -306,6 +308,27 @@ function M.run(opts, project_conf, launch_config)
306308
end
307309
end
308310

311+
---@param opts AttachOpts
312+
local function attach(opts)
313+
opts = opts or {}
314+
executable.get(function(paths)
315+
local args = opts.cli_args or {}
316+
if not use_debugger_runner() then vim.list_extend(args, { "attach" }) end
317+
318+
local cwd = get_cwd()
319+
ui.notify("Attaching flutter project...")
320+
runner = use_debugger_runner() and debugger_runner or job_runner
321+
runner:attach(paths, args, cwd, on_run_data, on_run_exit)
322+
end)
323+
end
324+
325+
--- Attach to a running app
326+
---@param opts AttachOpts
327+
function M.attach(opts)
328+
if M.is_running() then return ui.notify("Flutter is already running!") end
329+
attach(opts)
330+
end
331+
309332
---@param cmd string
310333
---@param quiet boolean?
311334
---@param on_send function|nil

lua/flutter-tools/runners/debugger_runner.lua

+86-23
Original file line numberDiff line numberDiff line change
@@ -102,35 +102,15 @@ local function register_default_configurations(paths, is_flutter_project, projec
102102
end
103103
end
104104

105-
function DebuggerRunner:run(
106-
paths,
107-
args,
108-
cwd,
109-
on_run_data,
110-
on_run_exit,
111-
is_flutter_project,
112-
project_config,
113-
last_launch_config
114-
)
115-
---@type dap.Configuration
116-
local selected_launch_config = nil
117-
105+
local function register_dap_listeners(on_run_data, on_run_exit)
118106
local started = false
119107
local before_start_logs = {}
120-
vm_service_extensions.reset()
121108
dap.listeners.after["event_output"][plugin_identifier] = function(_, body)
122-
if body and body.output then
123-
for line in body.output:gmatch("[^\r\n]+") do
124-
if not started then table.insert(before_start_logs, line) end
125-
on_run_data(body.category == "sterr", line)
126-
end
127-
end
109+
on_run_data(started, before_start_logs, body)
128110
end
129111

130112
local handle_termination = function()
131-
if next(before_start_logs) ~= nil then
132-
on_run_exit(before_start_logs, args, project_config, selected_launch_config)
133-
end
113+
if next(before_start_logs) ~= nil then on_run_exit(before_start_logs) end
134114
end
135115

136116
dap.listeners.before["event_exited"][plugin_identifier] = function(_, _) handle_termination() end
@@ -160,6 +140,35 @@ function DebuggerRunner:run(
160140
vm_service_extensions.set_service_extensions_state(body.extension, body.value)
161141
end
162142
end
143+
end
144+
145+
function DebuggerRunner:run(
146+
paths,
147+
args,
148+
cwd,
149+
on_run_data,
150+
on_run_exit,
151+
is_flutter_project,
152+
project_config,
153+
last_launch_config
154+
)
155+
vm_service_extensions.reset()
156+
---@type dap.Configuration
157+
local selected_launch_config = nil
158+
159+
register_dap_listeners(
160+
function(started, before_start_logs, body)
161+
if body and body.output then
162+
for line in body.output:gmatch("[^\r\n]+") do
163+
if not started then table.insert(before_start_logs, line) end
164+
on_run_data(body.category == "sterr", line)
165+
end
166+
end
167+
end,
168+
function(before_start_logs)
169+
on_run_exit(before_start_logs, args, project_config, selected_launch_config)
170+
end
171+
)
163172

164173
register_debug_adapter(paths, is_flutter_project)
165174
local launch_configurations = {}
@@ -212,6 +221,60 @@ function DebuggerRunner:run(
212221
end
213222
end
214223

224+
function DebuggerRunner:attach(paths, args, cwd, on_run_data, on_run_exit)
225+
vm_service_extensions.reset()
226+
register_dap_listeners(function(started, before_start_logs, body)
227+
if body and body.output then
228+
for line in body.output:gmatch("[^\r\n]+") do
229+
if not started then table.insert(before_start_logs, line) end
230+
on_run_data(body.category == "sterr", line)
231+
end
232+
end
233+
end, function(before_start_logs) on_run_exit(before_start_logs, args) end)
234+
235+
register_debug_adapter(paths, true)
236+
local launch_configurations = {}
237+
local launch_configuration_count = 0
238+
register_default_configurations(paths, true)
239+
if config.debugger.register_configurations then config.debugger.register_configurations(paths) end
240+
local all_configurations = require("dap").configurations.dart
241+
if not all_configurations then
242+
ui.notify("No launch configuration for DAP found", ui.ERROR)
243+
return
244+
end
245+
for _, c in ipairs(all_configurations) do
246+
if c.request == "attach" then
247+
table.insert(launch_configurations, c)
248+
launch_configuration_count = launch_configuration_count + 1
249+
end
250+
end
251+
252+
if launch_configuration_count == 0 then
253+
ui.notify("No launch configuration for DAP found", ui.ERROR)
254+
return
255+
else
256+
require("dap.ui").pick_if_many(
257+
launch_configurations,
258+
"Select launch configuration",
259+
function(item)
260+
return fmt("%s : %s | %s", item.name, item.program or item.cwd, vim.inspect(item.args))
261+
end,
262+
function(launch_config)
263+
if not launch_config then return end
264+
launch_config = vim.deepcopy(launch_config)
265+
if not launch_config.cwd then launch_config.cwd = cwd end
266+
launch_config.args = vim.list_extend(launch_config.args or {}, args or {})
267+
launch_config.dartSdkPath = paths.dart_sdk
268+
launch_config.flutterSdkPath = paths.flutter_sdk
269+
if config.debugger.evaluate_to_string_in_debug_views then
270+
launch_config.evaluateToStringInDebugViews = true
271+
end
272+
dap.run(launch_config)
273+
end
274+
)
275+
end
276+
end
277+
215278
function DebuggerRunner:send(cmd, quiet)
216279
if cmd == "open_dev_tools" then
217280
dev_tools.open_dev_tools()

lua/flutter-tools/runners/job_runner.lua

+19
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,23 @@ end
6969

7070
function JobRunner:cleanup() run_job = nil end
7171

72+
function JobRunner:attach(paths, args, cwd, on_run_data, on_run_exit)
73+
local command = paths.flutter_bin
74+
local command_args = args
75+
76+
run_job = Job:new({
77+
command = command,
78+
args = command_args,
79+
cwd = cwd,
80+
on_start = function() utils.emit_event(utils.events.APP_STARTED) end,
81+
on_stdout = vim.schedule_wrap(function(_, data, _)
82+
on_run_data(false, data)
83+
dev_tools.handle_log(data)
84+
end),
85+
on_stderr = vim.schedule_wrap(function(_, data, _) on_run_data(true, data) end),
86+
on_exit = vim.schedule_wrap(function(j, _) on_run_exit(j:result(), args) end),
87+
})
88+
run_job:start()
89+
end
90+
7291
return JobRunner

0 commit comments

Comments
 (0)