Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ require("flutter-tools").setup {} -- use defaults
- `FlutterDebug` - Force run current project in debug mode.
- `FlutterDevices` - Brings up a list of connected devices to select from.
- `FlutterEmulators` - Similar to devices but shows a list of emulators to choose from.
Set `emulators.launcher` to start the selected emulator with your own command instead of
`flutter emulator --launch`, see the [full configuration](#full-configuration).
- `FlutterReload` - Reload the running project.
- `FlutterRestart` - Restart the current project.
- `FlutterQuit` - Ends a running session.
Expand Down Expand Up @@ -284,6 +286,16 @@ require("flutter-tools").setup {
widget_preview = {
web_server = true, -- open the widget previewer with vim.ui.open; false lets Flutter launch its own Chrome
},
emulators = {
-- optional function to launch the emulator picked in `FlutterEmulators` with your own command,
-- e.g. the Android SDK `emulator` binary with extra flags; return nil to use `flutter emulator --launch`
-- launcher = function(emulator, paths)
-- if emulator.system ~= "android" then return nil end
-- local args = { "@" .. emulator.id, "-gpu", "host", "-accel", "on" }
-- if emulator.cold_boot then table.insert(args, "-no-snapshot-load") end
-- return { command = "emulator", args = args }
-- end,
},
outline = {
open_cmd = "30vnew", -- command to use to open the outline buffer
auto_open = false -- if true this will open the outline automatically when it is first populated
Expand Down
11 changes: 11 additions & 0 deletions lua/flutter-tools/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ local utils = lazy.require("flutter-tools.utils") ---@module "flutter-tools.util
---@field flutter? table|string -- options applied to `flutter run` command
---@field dart? table|string -- options appliert to `dart run` command
---
---@class flutter.EmulatorLaunchSpec
---@field command string
---@field args? string[]
---
---@class flutter.EmulatorsOpts
---@field launcher? fun(emulator: Device, paths: flutter.Paths): flutter.EmulatorLaunchSpec?
---
---@class flutter.Config
---@field flutter_path? string Path to the Flutter SDK
---@field flutter_lookup_cmd? string Command to find Flutter SDK
Expand All @@ -44,6 +51,7 @@ local utils = lazy.require("flutter-tools.utils") ---@module "flutter-tools.util
---@field dev_log? flutter.DevLogOpts
---@field dev_tools? {autostart: boolean, auto_open_browser: boolean}
---@field widget_preview? {web_server: boolean}
---@field emulators? flutter.EmulatorsOpts
---@field analyzer_web_port? number

local M = {}
Expand Down Expand Up @@ -152,6 +160,9 @@ local config = {
widget_preview = {
web_server = true,
},
emulators = {
launcher = nil,
},
}

local deprecations = {
Expand Down
40 changes: 27 additions & 13 deletions lua/flutter-tools/devices.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ local ui = lazy.require("flutter-tools.ui") ---@module "flutter-tools.ui"
local utils = lazy.require("flutter-tools.utils") ---@module "flutter-tools.utils"
local commands = lazy.require("flutter-tools.commands") ---@module "flutter-tools.commands"
local executable = lazy.require("flutter-tools.executable") ---@module "flutter-tools.executable"
local config = lazy.require("flutter-tools.config") ---@module "flutter-tools.config"
local fmt = string.format

---@alias Device {name: string, id: string, platform: string, system: string, type: integer, cold_boot: boolean}

local M = {
---@type Job
emulator_job = nil,
}
local M = {}

local EMULATOR = 1
local DEVICE = 2
Expand Down Expand Up @@ -180,19 +178,35 @@ end
---@param job Job
local function handle_launch(job) ui.notify(utils.join(job:result())) end

function M.close_emulator()
if M.emulator_job then M.emulator_job:shutdown() end
---@param emulator Device
---@param paths flutter.Paths
---@return flutter.EmulatorLaunchSpec?
local function custom_launch_spec(emulator, paths)
local launcher = config.emulators.launcher
if not launcher then return end
return launcher(emulator, paths)
end

---@param emulator table
---@param emulator Device
function M.launch_emulator(emulator)
if not emulator then return end
executable.flutter(function(cmd)
args = { "emulator", "--launch", emulator.id }
if emulator.cold_boot then table.insert(args, "--cold") end
M.emulator_job = Job:new({ command = cmd, args = args })
M.emulator_job:after_success(vim.schedule_wrap(handle_launch))
M.emulator_job:start()
executable.get(function(paths)
local spec = custom_launch_spec(emulator, paths)
local job
if spec then
job = Job:new({ command = spec.command, args = spec.args })
job:after_failure(
vim.schedule_wrap(
function(j) ui.notify(utils.join(j:stderr_result()), ui.ERROR, { timeout = 5000 }) end
)
)
else
local args = { "emulator", "--launch", emulator.id }
if emulator.cold_boot then table.insert(args, "--cold") end
job = Job:new({ command = paths.flutter_bin, args = args })
job:after_success(vim.schedule_wrap(handle_launch))
end
job:start()
end)
end

Expand Down
80 changes: 80 additions & 0 deletions tests/devices_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,84 @@ INFO | Storing crashdata in: /tmp/android-ts/emu-crash-34.2.14.db, detection
assert.is_nil(devices.resolve_default_device({ "not json" }))
end)
end)

describe("emulator launch - ", function()
local devices
local config
local jobs
local modules = {
"flutter-tools.devices",
"flutter-tools.config",
"flutter-tools.executable",
"plenary.job",
}
local paths = { flutter_bin = "/sdk/bin/flutter" }
local emulator = { id = "Pixel_8", name = "Pixel 8", system = "android", type = 1 }

before_each(function()
jobs = {}
for _, name in ipairs(modules) do
package.loaded[name] = nil
end
package.loaded["plenary.job"] = {
new = function(_, opts)
local job = { opts = opts, started = false }
function job:after_success() end
function job:after_failure() end
function job:start() self.started = true end
table.insert(jobs, job)
return job
end,
}
package.loaded["flutter-tools.executable"] = {
get = function(callback) callback(paths) end,
}
config = require("flutter-tools.config")
devices = require("flutter-tools.devices")
end)

after_each(function()
for _, name in ipairs(modules) do
package.loaded[name] = nil
end
end)

it("should launch through flutter by default", function()
devices.launch_emulator(vim.tbl_extend("force", emulator, { cold_boot = true }))

assert.equal(1, #jobs)
assert.equal("/sdk/bin/flutter", jobs[1].opts.command)
assert.same({ "emulator", "--launch", "Pixel_8", "--cold" }, jobs[1].opts.args)
assert.is_true(jobs[1].started)
end)

it("should use the command returned by a custom launcher", function()
local received
config.set({
emulators = {
launcher = function(e, p)
received = { emulator = e, paths = p }
return { command = "emulator", args = { "@" .. e.id, "-gpu", "host" } }
end,
},
})

devices.launch_emulator(emulator)

assert.equal(emulator, received.emulator)
assert.equal(paths, received.paths)
assert.equal("emulator", jobs[1].opts.command)
assert.same({ "@Pixel_8", "-gpu", "host" }, jobs[1].opts.args)
assert.is_true(jobs[1].started)
end)

it("should fall back to flutter when the launcher returns nil", function()
config.set({ emulators = { launcher = function() return nil end } })

devices.launch_emulator(emulator)

assert.equal("/sdk/bin/flutter", jobs[1].opts.command)
assert.same({ "emulator", "--launch", "Pixel_8" }, jobs[1].opts.args)
end)
end)
end)
Loading