diff --git a/README.md b/README.md index 7f1ec41..4681312 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,7 @@ require("flutter-tools").setup {} -- use defaults - `FlutterReload` - Reload the running project. - `FlutterRestart` - Restart the current project. - `FlutterQuit` - Ends a running session. +- `FlutterAttach` - Attach to a running app. - `FlutterDetach` - Ends a running session locally but keeps the process running on the device. - `FlutterOutlineToggle` - Toggle the outline window showing the widget tree for the given file. - `FlutterOutlineOpen` - Opens an outline window showing the widget tree for the given file. diff --git a/lua/flutter-tools.lua b/lua/flutter-tools.lua index 98ebb07..355733b 100644 --- a/lua/flutter-tools.lua +++ b/lua/flutter-tools.lua @@ -25,6 +25,7 @@ local function setup_commands() command("FlutterRun", function(data) commands.run_command(data.args) end, { nargs = "*" }) command("FlutterDebug", function(data) commands.run_command(data.args) end, { nargs = "*" }) command("FlutterLspRestart", lsp.restart) + command("FlutterAttach", commands.attach) command("FlutterDetach", commands.detach) command("FlutterReload", commands.reload) command("FlutterRestart", commands.restart) diff --git a/lua/flutter-tools/commands.lua b/lua/flutter-tools/commands.lua index 14476fc..1232a22 100644 --- a/lua/flutter-tools/commands.lua +++ b/lua/flutter-tools/commands.lua @@ -16,6 +16,7 @@ local parser = lazy.require("flutter-tools.utils.yaml_parser") local M = {} ---@alias RunOpts {cli_args: string[]?, args: string[]?, device: Device?, force_debug: boolean?} +---@alias AttachOpts {cli_args: string[]?, args: string[]?, device: Device?} ---@type table? local current_device = nil @@ -25,6 +26,7 @@ local current_device = nil ---@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?) ---@field cleanup fun(funner: flutter.Runner) ---@field send fun(runner: flutter.Runner, cmd:string, quiet: boolean?) +---@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?)) ---@type flutter.Runner? local runner = nil @@ -306,6 +308,27 @@ function M.run(opts, project_conf, launch_config) end end +---@param opts AttachOpts +local function attach(opts) + opts = opts or {} + executable.get(function(paths) + local args = opts.cli_args or {} + if not use_debugger_runner() then vim.list_extend(args, { "attach" }) end + + local cwd = 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) + end) +end + +--- Attach to a running app +---@param opts AttachOpts +function M.attach(opts) + if M.is_running() then return ui.notify("Flutter is already running!") end + attach(opts) +end + ---@param cmd string ---@param quiet boolean? ---@param on_send function|nil diff --git a/lua/flutter-tools/runners/debugger_runner.lua b/lua/flutter-tools/runners/debugger_runner.lua index 8f10ccf..aff3518 100644 --- a/lua/flutter-tools/runners/debugger_runner.lua +++ b/lua/flutter-tools/runners/debugger_runner.lua @@ -102,35 +102,15 @@ local function register_default_configurations(paths, is_flutter_project, projec end end -function DebuggerRunner:run( - paths, - args, - cwd, - on_run_data, - on_run_exit, - is_flutter_project, - project_config, - last_launch_config -) - ---@type dap.Configuration - local selected_launch_config = nil - +local function register_dap_listeners(on_run_data, on_run_exit) local started = false local before_start_logs = {} - vm_service_extensions.reset() dap.listeners.after["event_output"][plugin_identifier] = function(_, body) - if body and body.output then - for line in body.output:gmatch("[^\r\n]+") do - if not started then table.insert(before_start_logs, line) end - on_run_data(body.category == "sterr", line) - end - end + on_run_data(started, before_start_logs, body) end local handle_termination = function() - if next(before_start_logs) ~= nil then - on_run_exit(before_start_logs, args, project_config, selected_launch_config) - end + if next(before_start_logs) ~= nil then on_run_exit(before_start_logs) end end dap.listeners.before["event_exited"][plugin_identifier] = function(_, _) handle_termination() end @@ -160,6 +140,35 @@ function DebuggerRunner:run( vm_service_extensions.set_service_extensions_state(body.extension, body.value) end end +end + +function DebuggerRunner:run( + paths, + args, + cwd, + on_run_data, + on_run_exit, + is_flutter_project, + project_config, + last_launch_config +) + vm_service_extensions.reset() + ---@type dap.Configuration + local selected_launch_config = nil + + register_dap_listeners( + function(started, before_start_logs, body) + if body and body.output then + for line in body.output:gmatch("[^\r\n]+") do + if not started then table.insert(before_start_logs, line) end + on_run_data(body.category == "sterr", line) + end + end + end, + function(before_start_logs) + on_run_exit(before_start_logs, args, project_config, selected_launch_config) + end + ) register_debug_adapter(paths, is_flutter_project) local launch_configurations = {} @@ -212,6 +221,60 @@ function DebuggerRunner:run( end end +function DebuggerRunner:attach(paths, args, cwd, on_run_data, on_run_exit) + vm_service_extensions.reset() + register_dap_listeners(function(started, before_start_logs, body) + if body and body.output then + for line in body.output:gmatch("[^\r\n]+") do + if not started then table.insert(before_start_logs, line) end + on_run_data(body.category == "sterr", line) + end + end + end, function(before_start_logs) on_run_exit(before_start_logs, args) end) + + register_debug_adapter(paths, true) + local launch_configurations = {} + local launch_configuration_count = 0 + register_default_configurations(paths, true) + if config.debugger.register_configurations then config.debugger.register_configurations(paths) end + local all_configurations = require("dap").configurations.dart + if not all_configurations then + ui.notify("No launch configuration for DAP found", ui.ERROR) + return + end + for _, c in ipairs(all_configurations) do + if c.request == "attach" then + table.insert(launch_configurations, c) + launch_configuration_count = launch_configuration_count + 1 + end + end + + if launch_configuration_count == 0 then + ui.notify("No launch configuration for DAP found", ui.ERROR) + return + else + require("dap.ui").pick_if_many( + launch_configurations, + "Select launch configuration", + function(item) + return fmt("%s : %s | %s", item.name, item.program or item.cwd, vim.inspect(item.args)) + end, + function(launch_config) + if not launch_config then return end + launch_config = vim.deepcopy(launch_config) + if not launch_config.cwd then launch_config.cwd = cwd end + launch_config.args = vim.list_extend(launch_config.args or {}, args or {}) + launch_config.dartSdkPath = paths.dart_sdk + launch_config.flutterSdkPath = paths.flutter_sdk + if config.debugger.evaluate_to_string_in_debug_views then + launch_config.evaluateToStringInDebugViews = true + end + dap.run(launch_config) + end + ) + end +end + function DebuggerRunner:send(cmd, quiet) if cmd == "open_dev_tools" then dev_tools.open_dev_tools() diff --git a/lua/flutter-tools/runners/job_runner.lua b/lua/flutter-tools/runners/job_runner.lua index b3a5d5a..b87c8c5 100644 --- a/lua/flutter-tools/runners/job_runner.lua +++ b/lua/flutter-tools/runners/job_runner.lua @@ -69,4 +69,23 @@ end function JobRunner:cleanup() run_job = nil end +function JobRunner:attach(paths, args, cwd, on_run_data, on_run_exit) + local command = paths.flutter_bin + local command_args = args + + run_job = Job:new({ + command = command, + args = command_args, + cwd = cwd, + on_start = function() utils.emit_event(utils.events.APP_STARTED) end, + on_stdout = vim.schedule_wrap(function(_, data, _) + on_run_data(false, data) + dev_tools.handle_log(data) + end), + on_stderr = vim.schedule_wrap(function(_, data, _) on_run_data(true, data) end), + on_exit = vim.schedule_wrap(function(j, _) on_run_exit(j:result(), args) end), + }) + run_job:start() +end + return JobRunner