Initial commit
This commit is contained in:
369
nvim/lua/darcula/flutter/init.lua
Normal file
369
nvim/lua/darcula/flutter/init.lua
Normal file
@@ -0,0 +1,369 @@
|
||||
-- The MIT License (MIT)
|
||||
--
|
||||
-- Copyright © 2026 Scott E. Graves <scott.e.graves@protonmail.com>
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
-- and associated documentation files (the “Software”), to deal in the Software without restriction,
|
||||
-- including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
-- sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
-- furnished to do so, subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all copies or
|
||||
-- substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
-- BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
-- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
-- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
--
|
||||
local Job = require("plenary.job")
|
||||
local action_state = require "telescope.actions.state"
|
||||
local actions = require "telescope.actions"
|
||||
local conf = require("telescope.config").values
|
||||
local finders = require("telescope.finders")
|
||||
local global_state = require("telescope.state")
|
||||
local pickers = require("telescope.pickers")
|
||||
local utils = require("darcula.utils")
|
||||
local utils_os = require("darcula.utils.os")
|
||||
|
||||
local M = {}
|
||||
local companion_title = "Flutter Companion"
|
||||
local config = {
|
||||
use_coc = true
|
||||
}
|
||||
|
||||
local callbacks = {nil, nil}
|
||||
local flutter_busy = false
|
||||
local spinner_frames = {"⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"}
|
||||
|
||||
local EMULATOR = 1
|
||||
local DEVICE = 2
|
||||
|
||||
local get_config_path = function()
|
||||
return vim.fs.joinpath(vim.fn.getcwd(), ".flutter-companion")
|
||||
end
|
||||
|
||||
local get_flutter_command = function()
|
||||
local cmd = "flutter" .. utils.iff(utils_os.is_windows, ".bat", "")
|
||||
return cmd
|
||||
end
|
||||
|
||||
local flutter_run_device = function(id)
|
||||
if config.use_coc then
|
||||
vim.api.nvim_command("CocCommand flutter.run -d " .. id)
|
||||
return
|
||||
end
|
||||
|
||||
if vim.fn.exists(":FlutterRun") ~= 0 then
|
||||
vim.api.nvim_command("FlutterRun -d " .. id)
|
||||
return
|
||||
end
|
||||
vim.notify(
|
||||
{
|
||||
"Run device requires 'coc-flutter/coc-flutter-tools' if using CoC or 'flutter-tools.nvim' if using native LSP."
|
||||
},
|
||||
"error",
|
||||
{
|
||||
title = companion_title
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
local load_table = function(path)
|
||||
local file, err = io.open(path, "r")
|
||||
if err or file == nil then
|
||||
return {}
|
||||
end
|
||||
|
||||
local lines = file:read("a")
|
||||
file:close()
|
||||
|
||||
local ok, json =
|
||||
pcall(
|
||||
function()
|
||||
return vim.json.decode(lines)
|
||||
end
|
||||
)
|
||||
return utils.iff(ok, json, {})
|
||||
end
|
||||
|
||||
local parse = function(line, device_type)
|
||||
local parts = vim.split(line, "•")
|
||||
local is_emulator = device_type == EMULATOR
|
||||
local name_index = not is_emulator and 1 or 2
|
||||
local id_index = not is_emulator and 2 or 1
|
||||
if #parts == 4 then
|
||||
return {
|
||||
name = vim.trim(parts[name_index]),
|
||||
id = vim.trim(parts[id_index]),
|
||||
platform = vim.trim(parts[3]),
|
||||
system = vim.trim(parts[4]),
|
||||
type = device_type
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local save_table = function(data, filename)
|
||||
local file, err = io.open(filename, "wb")
|
||||
if err or file == nil then
|
||||
return err
|
||||
end
|
||||
|
||||
file:write(vim.json.encode(data))
|
||||
file:close()
|
||||
end
|
||||
|
||||
local get_devices = function(result, type)
|
||||
local devices = {}
|
||||
for _, line in pairs(result) do
|
||||
local device = parse(line, type)
|
||||
if device then
|
||||
table.insert(devices, device)
|
||||
end
|
||||
end
|
||||
return devices
|
||||
end
|
||||
|
||||
local extract_device_props = function(result, device_type)
|
||||
device_type = device_type or DEVICE
|
||||
local devices = {}
|
||||
local device_list = get_devices(result, device_type)
|
||||
if #device_list > 0 then
|
||||
for _, device in pairs(device_list) do
|
||||
table.insert(devices, device)
|
||||
end
|
||||
end
|
||||
return devices
|
||||
end
|
||||
|
||||
local parse_devices_output = function(job, callback)
|
||||
local result = job:result()
|
||||
local devices = extract_device_props(result, DEVICE)
|
||||
callback(devices)
|
||||
end
|
||||
|
||||
local list_devices = function(callback)
|
||||
local job = Job:new({command = get_flutter_command(), args = {"devices"}})
|
||||
job:after_success(
|
||||
vim.schedule_wrap(
|
||||
function()
|
||||
parse_devices_output(job, callback)
|
||||
end
|
||||
)
|
||||
)
|
||||
job:start()
|
||||
end
|
||||
|
||||
local function list_emulators(callback)
|
||||
local job = Job:new({command = get_flutter_command(), args = {"emulators"}})
|
||||
job:after_success(
|
||||
vim.schedule_wrap(
|
||||
function()
|
||||
local emulators = extract_device_props(job:result(), EMULATOR)
|
||||
callback(emulators)
|
||||
end
|
||||
)
|
||||
)
|
||||
job:start()
|
||||
end
|
||||
|
||||
local start_spinner = function(name, get_active)
|
||||
local spinner = 0
|
||||
local record
|
||||
|
||||
local function update_spinner()
|
||||
if get_active() then
|
||||
local next_record =
|
||||
vim.notify(
|
||||
"Detecting " .. name .. "...",
|
||||
"info",
|
||||
{
|
||||
title = companion_title,
|
||||
hide_from_history = true,
|
||||
icon = spinner_frames[(spinner % #spinner_frames) + 1],
|
||||
replace = record
|
||||
}
|
||||
)
|
||||
record = next_record
|
||||
spinner = spinner + 1
|
||||
vim.defer_fn(update_spinner, 100)
|
||||
end
|
||||
end
|
||||
update_spinner()
|
||||
end
|
||||
|
||||
local stop_spinner = function(set_inactive)
|
||||
set_inactive()
|
||||
vim.notify.dismiss()
|
||||
end
|
||||
|
||||
function M.flutter_clear(is_emulator)
|
||||
local config_path = get_config_path()
|
||||
local config_data = load_table(config_path)
|
||||
if is_emulator then
|
||||
if config_data.emulator ~= nil then
|
||||
config_data.emulator = nil
|
||||
save_table(config_data, config_path)
|
||||
end
|
||||
else
|
||||
if config_data.device ~= nil then
|
||||
config_data.device = nil
|
||||
save_table(config_data, config_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M.flutter_list(is_emulators)
|
||||
if flutter_busy == true then
|
||||
return
|
||||
end
|
||||
flutter_busy = true
|
||||
|
||||
local config_path = get_config_path()
|
||||
local config_data = load_table(config_path)
|
||||
|
||||
local callback_index = utils.iff(is_emulators, 2, 1)
|
||||
local list_function = utils.iff(is_emulators, list_emulators, list_devices)
|
||||
local name = utils.iff(is_emulators, "Emulators", "Devices")
|
||||
|
||||
local completed_callback
|
||||
if callbacks[callback_index] ~= nil then
|
||||
completed_callback = callbacks[callback_index]
|
||||
callbacks[callback_index] = nil
|
||||
end
|
||||
|
||||
local active = true
|
||||
start_spinner(
|
||||
name,
|
||||
function()
|
||||
return active
|
||||
end
|
||||
)
|
||||
|
||||
list_function(
|
||||
function(items)
|
||||
stop_spinner(
|
||||
function()
|
||||
active = false
|
||||
end
|
||||
)
|
||||
|
||||
if #items == 0 then
|
||||
vim.notify(
|
||||
"No " .. name .. " were found",
|
||||
"error",
|
||||
{title = companion_title}
|
||||
)
|
||||
else
|
||||
global_state.set_global_key("selected_entry", nil)
|
||||
|
||||
local opts = {}
|
||||
pickers.new(
|
||||
opts,
|
||||
{
|
||||
prompt_title = "Flutter " .. name,
|
||||
sorter = conf.generic_sorter(opts),
|
||||
finder = finders.new_table(
|
||||
{
|
||||
results = items,
|
||||
entry_maker = function(item)
|
||||
return {
|
||||
value = item,
|
||||
ordinal = item.name,
|
||||
display = item.name
|
||||
}
|
||||
end
|
||||
}
|
||||
),
|
||||
attach_mappings = function(prompt_bufnr --[[, map ]])
|
||||
actions.select_default:replace(
|
||||
function()
|
||||
actions.close(prompt_bufnr)
|
||||
local selection = action_state.get_selected_entry()
|
||||
if is_emulators then
|
||||
config_data.emulator = selection.value
|
||||
else
|
||||
config_data.device = selection.value
|
||||
end
|
||||
save_table(config_data, config_path)
|
||||
if completed_callback ~= nil then
|
||||
completed_callback()
|
||||
end
|
||||
end
|
||||
)
|
||||
return true
|
||||
end
|
||||
}
|
||||
):find()
|
||||
end
|
||||
flutter_busy = false
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
M.flutter_run = function(is_emulator)
|
||||
local config_path = get_config_path()
|
||||
local config_data = load_table(config_path)
|
||||
if is_emulator then
|
||||
if config_data.emulator == nil then
|
||||
if callbacks[2] == nil then
|
||||
callbacks[2] = function()
|
||||
M.flutter_run(is_emulator)
|
||||
end
|
||||
M.flutter_list(true)
|
||||
end
|
||||
else
|
||||
local job =
|
||||
Job:new(
|
||||
{
|
||||
command = get_flutter_command(),
|
||||
args = {"emulators", "--launch", config_data.emulator.id}
|
||||
}
|
||||
)
|
||||
job:start()
|
||||
end
|
||||
else
|
||||
if config_data.device == nil then
|
||||
if callbacks[1] == nil then
|
||||
callbacks[1] = function()
|
||||
M.flutter_run(is_emulator)
|
||||
end
|
||||
M.flutter_list(false)
|
||||
end
|
||||
else
|
||||
flutter_run_device(config_data.device.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.setup = function(opts)
|
||||
opts = opts or {}
|
||||
config.use_coc = vim.F.if_nil(opts.use_coc, config.use_coc)
|
||||
end
|
||||
|
||||
_G.Flutter_Companion_Clear_Selected_Device = function()
|
||||
M.flutter_clear(false)
|
||||
end
|
||||
|
||||
_G.Flutter_Companion_Run_Selected_Device = function()
|
||||
M.flutter_run(false)
|
||||
end
|
||||
|
||||
_G.Flutter_Companion_Select_Device = function()
|
||||
M.flutter_list(false)
|
||||
end
|
||||
|
||||
_G.Flutter_Companion_Clear_Selected_Emulator = function()
|
||||
M.flutter_clear(true)
|
||||
end
|
||||
|
||||
_G.Flutter_Companion_Run_Selected_Emulator = function()
|
||||
M.flutter_run(true)
|
||||
end
|
||||
|
||||
_G.Flutter_Companion_Select_Emulator = function()
|
||||
M.flutter_list(true)
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user