Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(config): add project configuration #232

Merged
merged 9 commits into from
Apr 11, 2023
42 changes: 41 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,45 @@ You cannot/should not edit the files in the sdk directly so diagnostic analysis
To ignore packages installed with pub, consider adding `vim.fn.expand("$HOME/AppData/Local/Pub/Cache")` to
`analysisExcludedFolders` if you are using PowerShell.

#### Project Configuration

It is possible to configure how each project is run using neovim's `exrc` functionality (see `:help exrc`).
This allows you to create an exrc file e.g. `.nvim.lua` and put the project configurations inside it.
This is similar _conceptually_ to vscode's `launch.json` file.

```lua
-- .nvim.lua
-- If you have more than one setup configured you will be prompted when you run
-- your app to select which one you want to use
require('flutter-tools').setup_project({
{
name = 'Development', -- an arbitrary name that you provide so you can recognise this config
flavor = 'DevFlavor', -- your flavour
device = 'pixel6pro', -- the device ID, which you can get by running `flutter devices`
dart_defines = {
API_URL = 'https://dev.example.com/api',
IS_DEV = true,
}
},
{
name = 'Web',
device = 'chrome',
flavor = 'WebApp'
}
})
```

you can also specify the configuration as an object if there is only one

```lua
require('flutter-tools').setup_project({
name = 'Development',
flavor = 'DevFlavor',
device = 'pixel6pro',
dart_defines = { ... }
})
```

#### Flutter binary

In order to run flutter commands you _might_ need to pass either a _path_ or a _command_ to the plugin so it can find your
Expand All @@ -308,9 +347,10 @@ was added, you can set your `flutter_path` to `"<INSERT-HOME-DIRECTORY>/snap/flu
which is where this is usually installed by `snap`.

### Highlights

Highlight groups that are user configurable to change the appearance of certain UI elements.

* `FlutterToolsOutlineIndentGuides` - indent guides for the outline window
- `FlutterToolsOutlineIndentGuides` - indent guides for the outline window

#### Widget guides

Expand Down
3 changes: 3 additions & 0 deletions lua/flutter-tools.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ local command = function(name, callback, opts)
api.nvim_create_user_command(name, callback, opts or {})
end

---@param opts flutter.ProjectConfig
function M.setup_project(opts) config.setup_project(opts) end

local function setup_commands()
-- Commands
command("FlutterRun", function(data) commands.run_command(data.args) end, { nargs = "*" })
Expand Down
90 changes: 65 additions & 25 deletions lua/flutter-tools/commands.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,21 @@ local dev_log = lazy.require("flutter-tools.log") ---@module "flutter-tools.log"

local M = {}

---@alias RunOpts {cli_args: string[]?, args: string[]?, device: Device?}

---@type table?
local current_device = nil

---@class FlutterRunner
---@field is_running fun(runner: FlutterRunner):boolean
---@field run fun(runner: FlutterRunner, paths:table, args:table, cwd:string, on_run_data:fun(is_err:boolean, data:string), on_run_exit:fun(data:string[], args: table))
---@field cleanup fun(funner: FlutterRunner)
---@field send fun(runner: FlutterRunner, cmd:string, quiet: boolean?)
---@class flutter.Runner
---@field is_running fun(runner: flutter.Runner):boolean
---@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))
---@field cleanup fun(funner: flutter.Runner)
---@field send fun(runner: flutter.Runner, cmd:string, quiet: boolean?)

---@type FlutterRunner?
---@type flutter.Runner?
local runner = nil

function M.use_debugger_runner()
local function use_debugger_runner()
local dap_ok, dap = pcall(require, "dap")
if not config.debugger.run_via_dap then return false end
if dap_ok then return true end
Expand Down Expand Up @@ -98,31 +100,64 @@ function M.run_command(args)
M.run({ args = args })
end

---Run the flutter application
---@param opts table
function M.run(opts)
if M.is_running() then return ui.notify("Flutter is already running!") end
opts = opts or {}
local device = opts.device
local cmd_args = opts.args
local cli_args = opts.cli_args
executable.get(function(paths)
local args = cli_args or {}
if not cli_args then
if not M.use_debugger_runner() then vim.list_extend(args, { "run" }) end
if not cmd_args and device and device.id then vim.list_extend(args, { "-d", device.id }) end

if cmd_args then vim.list_extend(args, cmd_args) end
---@param callback fun(project_config: flutter.ProjectConfig?)
local function select_project_config(callback)
local project_config = config.project --[=[@as flutter.ProjectConfig[]]=]
if #project_config <= 1 then return callback(project_config[1]) end
sidlatau marked this conversation as resolved.
Show resolved Hide resolved
vim.ui.select(project_config, {
prompt = "Select a project configuration",
format_item = function(item)
if item.name then return item.name end
return vim.inspect(item)
end,
}, function(selected)
if selected then callback(selected) end
end)
end

local dev_url = dev_tools.get_url()
if dev_url then vim.list_extend(args, { "--devtools-server-address", dev_url }) end
---@param opts RunOpts
---@param conf flutter.ProjectConfig?
---@return string[]
local function get_run_args(opts, conf)
local args = {}
local cmd_args = opts.args
local device = conf and conf.device or (opts.device and opts.device.id)
local flavor = conf and conf.flavor
local dart_defines = conf and conf.dart_define
local dev_url = dev_tools.get_url()

if not use_debugger_runner() then vim.list_extend(args, { "run" }) end
if not cmd_args and device then vim.list_extend(args, { "-d", device }) end
if cmd_args then vim.list_extend(args, cmd_args) end
if flavor then vim.list_extend(args, { "--flavor", flavor }) end
if dart_defines then
for key, value in pairs(dart_defines) do
vim.list_extend(args, { "--dart-define", ("%s=%s"):format(key, value) })
end
end
if dev_url then vim.list_extend(args, { "--devtools-server-address", dev_url }) end
return args
end

---@param opts RunOpts
---@param project_conf flutter.ProjectConfig?
local function run(opts, project_conf)
opts = opts or {}
executable.get(function(paths)
local args = opts.cli_args or get_run_args(opts, project_conf)
ui.notify("Starting flutter project...")
runner = M.use_debugger_runner() and debugger_runner or job_runner
runner = use_debugger_runner() and debugger_runner or job_runner
runner:run(paths, args, lsp.get_lsp_root_dir(), on_run_data, on_run_exit)
end)
end

---Run the flutter application
---@param opts RunOpts
function M.run(opts)
if M.is_running() then return ui.notify("Flutter is already running!") end
select_project_config(function(project_conf) run(opts, project_conf) end)
end

---@param cmd string
---@param quiet boolean?
---@param on_send function|nil
Expand Down Expand Up @@ -331,4 +366,9 @@ function M.fvm_use(sdk_name)
end
end

if __TEST then
M.__run = run
M.__get_run_args = get_run_args
end

return M
24 changes: 18 additions & 6 deletions lua/flutter-tools/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,17 @@ local lazy = require("flutter-tools.lazy")
local path = lazy.require("flutter-tools.utils.path") ---@module "flutter-tools.utils.path"
local ui = lazy.require("flutter-tools.ui") ---@module "flutter-tools.ui"

---@class flutter.ProjectConfig
---@field name string?
---@field device string
---@field flavor string
---@field dart_define {[string]: string}

local M = {}

---@type flutter.ProjectConfig[]
local project_config = {}

local fn = vim.fn
local fmt = string.format

Expand Down Expand Up @@ -138,11 +147,10 @@ local function handle_deprecation(key, value, conf)
if deprecation.fallback then conf[deprecation.fallback] = value end
end

---Get the configuration or just a key of the config
---@param key string?
function M.get(key)
if key then return config[key] end
return config
---@param project flutter.ProjectConfig | flutter.ProjectConfig[]
M.setup_project = function(project)
if not vim.tbl_islist(project) then project = { project } end
project_config = project
end

function M.set(user_config)
Expand All @@ -155,6 +163,10 @@ function M.set(user_config)
return config
end

---@module "flutter-tools.config"
return setmetatable(M, {
__index = function(_, k) return M.get(k) end,
__index = function(_, k)
if k == "project" then return project_config end
return config[k]
end,
})
2 changes: 1 addition & 1 deletion lua/flutter-tools/runners/debugger_runner.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ local api = vim.api

local fmt = string.format

---@type FlutterRunner
---@type flutter.Runner
local DebuggerRunner = {}

local service_extensions_isolateid = {}
Expand Down
2 changes: 1 addition & 1 deletion lua/flutter-tools/runners/job_runner.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ local ui = require("flutter-tools.ui")
local dev_tools = require("flutter-tools.dev_tools")
local api = vim.api

---@type FlutterRunner
---@type flutter.Runner
local JobRunner = {}

---@type Job
Expand Down
11 changes: 8 additions & 3 deletions lua/flutter-tools/utils/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,14 @@ function M.highlight(name, opts)
api.nvim_create_autocmd("ColorScheme", { callback = hl, group = colorscheme_group })
end

function M.fold(accumulator, callback, list)
for _, v in ipairs(list) do
accumulator = callback(accumulator, v)
---@generic T, S
---@param accumulator S
---@param callback fun(accumulator: S, item: T, index: number|string): S
---@param list T[]
---@return S
function M.fold(callback, list, accumulator)
for k, v in ipairs(list) do
accumulator = callback(accumulator, v, k)
end
return accumulator
end
Expand Down
49 changes: 49 additions & 0 deletions tests/commands_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
local utils = require("flutter-tools.utils")

describe("commands", function()
local commands
before_each(function() commands = require("flutter-tools.commands") end)
after_each(function()
commands = nil
package.loaded["flutter-tools.commands"] = nil
end)
it(
"should add project config options correctly",
function()
assert.are.same(
{ "run", "--flavor", "Production" },
commands.__get_run_args({}, { flavor = "Production" })
)
end
)

it(
"should add 'dart_defines' options correctly",
function()
assert.are.same(
{ "run", "--flavor", "Production", "--dart-define", "ENV=prod" },
commands.__get_run_args({}, { flavor = "Production", dart_define = { ENV = "prod" } })
)
end
)

it("should add multiple dart_defines", function()
local args = commands.__get_run_args({}, {
flavor = "Production",
dart_define = { ENV = "prod", KEY = "VALUE" },
})
local result = utils.fold(function(acc, v)
acc[v] = acc[v] and acc[v] + 1 or 1
return acc
end, args, {})

assert.are.same(result, {
["run"] = 1,
["--flavor"] = 1,
["Production"] = 1,
["--dart-define"] = 2,
["ENV=prod"] = 1,
["KEY=VALUE"] = 1,
})
end)
end)