Skip to content

Commit

Permalink
feat: Introduce dap-based runner (#108)
Browse files Browse the repository at this point in the history
  • Loading branch information
clement-buchart committed Dec 1, 2021
1 parent ad2f4bb commit 6acf7d2
Show file tree
Hide file tree
Showing 8 changed files with 332 additions and 83 deletions.
29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,12 @@ require("flutter-tools").setup {
},
debugger = { -- integrate with nvim dap + install dart code debugger
enabled = false,
run_via_dap = false, -- use dap instead of a plenary job to run flutter apps
register_configurations = function(paths)
require("dap").configurations.dart = {
<put here config that you would find in .vscode/launch.json>
}
end,
},
flutter_path = "<full/path/if/needed>", -- <-- this takes priority over the lookup
flutter_lookup_cmd = nil, -- example "dirname $(which flutter)" or "asdf where flutter"
Expand All @@ -202,6 +208,7 @@ require("flutter-tools").setup {
enabled = true -- set to false to disable
},
dev_log = {
enabled = true,
open_cmd = "tabedit", -- command to use to open the log buffer
},
dev_tools = {
Expand Down Expand Up @@ -323,12 +330,30 @@ use 'mfussenegger/nvim-dap'
```

This plugin integrates with [nvim-dap](https://github.com/mfussenegger/nvim-dap) to provide debug capabilities.
Currently if `debugger` is set to `true` in the user's config **it will expect `nvim-dap` to be installed**.
Currently if `debugger.enabled` is set to `true` in the user's config **it will expect `nvim-dap` to be installed**.
If `dap` is installed the plugin will attempt to install the debugger (Dart-Code's debugger).

To use the debugger you need to run `:lua require('dap').continue()<CR>`. This will start your app. You should then be able
to use `dap` commands to begin to debug it. For more information on how to use `nvim-dap` please read the project's README
or see `:h dap`.
or see `:h dap`. Note that running the app this way will prevent commands such as `:FlutterRestart`, `:FlutterReload` from working.

Alternatively, if you prefer always running your app via dap, you can set `debugger.run_via_dap = true` in your config.
This way you benefit from the debugging abilities of DAP, AND you can still use `:FlutterRestart`, `:FlutterReload`, etc.

You can use the `debugger.register_configurations` to register custom runner configuration (for example for different targets or flavor).
If your flutter repo contains launch configurations in `.vscode/launch.json` you can use them via this config :
```lua
debugger = {
enabled = true,
register_configurations = function(_)
require("dap").configurations.dart = {}
require("dap.ext.vscode").load_launchjs()
end,
},
```

Since there is an overlap between this plugin's log buffer and the repl buffer when running via dap, you may use the `dev_log.enabled` configuration option if you want.


Also see:

Expand Down
113 changes: 60 additions & 53 deletions lua/flutter-tools/commands.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,46 @@ local utils = require("flutter-tools.utils")
local devices = require("flutter-tools.devices")
local config = require("flutter-tools.config")
local executable = require("flutter-tools.executable")
local dev_log = require("flutter-tools.log")
local dev_tools = require("flutter-tools.dev_tools")
local lsp = require("flutter-tools.lsp")
local job_runner = require("flutter-tools.runners.job_runner")
local dap_runner = require("flutter-tools.runners.dap_runner")
local dev_log = require("flutter-tools.log")
local dap_ok, dap = pcall(require, "dap")

local M = {}

---@type Job
local run_job = nil
---@type table
local current_device = nil

---@class FlutterRunner
---@field is_running fun():boolean
---@field run fun(paths:table, args:table, cwd:string, on_run_data:fun(is_err:boolean, data:string), on_run_exit:fun(data:string[]))
---@field cleanup fun()
---@field send fun(cmd:string)

---@type FlutterRunner
local runner = nil

function M.use_dap_runner()
local dap_requested = config.get("debugger").run_via_dap
if dap_requested then
if not dap_ok then
utils.notify("dap runner was request but nvim-dap is not installed!\n" .. dap, utils.L.ERROR)
return false
end
return true
else
return false
end
end

function M.current_device()
return current_device
end

function M.is_running()
return run_job ~= nil
return runner ~= nil and runner:is_running()
end

local function match_error_string(line)
Expand Down Expand Up @@ -50,18 +73,21 @@ end

---Handle output from flutter run command
---@param is_err boolean if this is stdout or stderr
---@param opts table config options for the dev log window
---@return fun(err: string, data: string, job: Job): nil
local function on_run_data(is_err, opts)
return vim.schedule_wrap(function(_, data, _)
if is_err then
ui.notify({ data })
end
if not match_error_string(data) then
dev_tools.handle_log(data)
dev_log.log(data, opts)
end
end)
local function on_run_data(is_err, data)
local dev_log_conf = config.get("dev_log")
if is_err then
ui.notify({ data })
end
dev_log.log(data, dev_log_conf)
end

local function shutdown()
if runner ~= nil then
runner:cleanup()
end
runner = nil
current_device = nil
dev_tools.on_flutter_shutdown()
end

---Handle a finished flutter run command
Expand All @@ -80,12 +106,7 @@ local function on_run_exit(result)
end,
})
end
end

local function shutdown()
run_job = nil
current_device = nil
dev_tools.on_flutter_shutdown()
shutdown()
end

--- Take arguments from the commandline and pass
Expand All @@ -99,16 +120,18 @@ end
---Run the flutter application
---@param opts table
function M.run(opts)
if M.is_running() then
return utils.notify("Flutter is already running!")
end
opts = opts or {}
local device = opts.device
local cmd_args = opts.args
if run_job then
return utils.notify("Flutter is already running!")
end
executable.flutter(function(cmd)
local args = { "run" }
executable.get(function(paths)
local args = {}
if not M.use_dap_runner() then
vim.list_extend(args, { "run" })
end
if not cmd_args and device and device.id then
current_device = device
vim.list_extend(args, { "-d", device.id })
end

Expand All @@ -120,34 +143,18 @@ function M.run(opts)
if dev_url then
vim.list_extend(args, { "--devtools-server-address", dev_url })
end

ui.notify({ "Starting flutter project..." })
local conf = config.get("dev_log")
run_job = Job:new({
command = cmd,
args = args,
cwd = lsp.get_lsp_root_dir(),
on_start = function()
vim.cmd("doautocmd User FlutterToolsAppStarted")
end,
on_stdout = on_run_data(false, conf),
on_stderr = on_run_data(true, conf),
on_exit = vim.schedule_wrap(function(j, _)
on_run_exit(j:result())
shutdown()
end),
})

run_job:start()
runner = M.use_dap_runner() and dap_runner or job_runner
runner:run(paths, args, lsp.get_lsp_root_dir(), on_run_data, on_run_exit)
end)
end

---@param cmd string
---@param quiet boolean
---@param on_send function|nil
local function send(cmd, quiet, on_send)
if run_job then
run_job:send(cmd)
if M.is_running() then
runner:send(cmd, quiet)
if on_send then
on_send()
end
Expand All @@ -158,12 +165,12 @@ end

---@param quiet boolean
function M.reload(quiet)
send("r", quiet)
send("reload", quiet)
end

---@param quiet boolean
function M.restart(quiet)
send("R", quiet, function()
send("restart", quiet, function()
if not quiet then
ui.notify({ "Restarting..." }, 1500)
end
Expand All @@ -172,7 +179,7 @@ end

---@param quiet boolean
function M.quit(quiet)
send("q", quiet, function()
send("quit", quiet, function()
if not quiet then
ui.notify({ "Closing flutter application..." }, 1500)
shutdown()
Expand All @@ -182,11 +189,11 @@ end

---@param quiet boolean
function M.visual_debug(quiet)
send("p", quiet)
send("visual_debug", quiet)
end

function M.copy_profiler_url()
if not run_job then
if not M.is_running() then
ui.notify({ "You must run the app first!" })
return
end
Expand Down
27 changes: 26 additions & 1 deletion lua/flutter-tools/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,29 @@ local defaults = {
},
debugger = {
enabled = false,
run_via_dap = false,
register_configurations = function(paths)
require("dap").configurations.dart = {
{
type = "dart",
request = "launch",
name = "Launch flutter",
dartSdkPath = paths.dart_sdk,
flutterSdkPath = paths.flutter_sdk,
program = "${workspaceFolder}/lib/main.dart",
cwd = "${workspaceFolder}",
},
{
type = "dart",
request = "attach",
name = "Connect flutter",
dartSdkPath = paths.dart_sdk,
flutterSdkPath = paths.flutter_sdk,
program = "${workspaceFolder}/lib/main.dart",
cwd = "${workspaceFolder}",
},
}
end,
},
closing_tags = {
highlight = "Comment",
Expand All @@ -75,7 +98,9 @@ local defaults = {
return k == "open_cmd" and get_split_cmd(0.3, 40) or nil
end,
}),
dev_log = setmetatable({}, {
dev_log = setmetatable({
enabled = true,
}, {
__index = function(_, k)
return k == "open_cmd" and get_split_cmd(0.4, 50) or nil
end,
Expand Down
23 changes: 2 additions & 21 deletions lua/flutter-tools/dap.lua
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function M.install_debugger(silent)
fn.termopen(fmt("%s && %s", clone, build))
end

function M.setup(_)
function M.setup(config)
M.install_debugger(true)

require("flutter-tools.executable").get(function(paths)
Expand All @@ -51,26 +51,7 @@ function M.setup(_)
command = "node",
args = { debugger_path, "flutter" },
}
dap.configurations.dart = {
{
type = "dart",
request = "launch",
name = "Launch flutter",
dartSdkPath = paths.dart_sdk,
flutterSdkPath = paths.flutter_sdk,
program = "${workspaceFolder}/lib/main.dart",
cwd = "${workspaceFolder}",
},
{
type = "dart",
request = "attach",
name = "Connect flutter",
dartSdkPath = paths.dart_sdk,
flutterSdkPath = paths.flutter_sdk,
program = "${workspaceFolder}/lib/main.dart",
cwd = "${workspaceFolder}",
},
}
config.debugger.register_configurations(paths)
end)
end

Expand Down
16 changes: 14 additions & 2 deletions lua/flutter-tools/dev_tools.lua
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,7 @@ function M.handle_log(data)
devtools_profiler_url = try_get_tools_flutter(data)

if devtools_profiler_url then
start_browser()
ui.notify({ "Detected devtools url", "Execute FlutterCopyProfilerUrl to copy it" })
M.handle_devtools_available()
return
end

Expand All @@ -80,13 +79,26 @@ function M.handle_log(data)
profiler_url = try_get_profiler_url_chrome(data)

if profiler_url then
M.register_profiler_url(profiler_url)
end
end

function M.register_profiler_url(url)
if url then
profiler_url = url
local autostart = require("flutter-tools.config").get("dev_tools").autostart
if autostart then
M.start()
M.handle_devtools_available()
end
end
end

function M.handle_devtools_available()
start_browser()
ui.notify({ "Detected devtools url", "Execute FlutterCopyProfilerUrl to copy it" })
end

--[[ {
event = "server.started",
method = "server.started",
Expand Down
10 changes: 6 additions & 4 deletions lua/flutter-tools/log.lua
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,13 @@ end
---@param data string
---@param opts table
function M.log(data, opts)
if not exists() then
create(opts)
if opts.enabled then
if not exists() then
create(opts)
end
append(M.buf, { data })
autoscroll(M.buf, M.win)
end
append(M.buf, { data })
autoscroll(M.buf, M.win)
end

function M.__resurrect()
Expand Down

0 comments on commit 6acf7d2

Please sign in to comment.