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 pickers = require("telescope.pickers") local global_state = require("telescope.state") local utils = require("nvim-flutter-companion.utils") local M = {} 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 utils.create_path(vim.fn.getcwd(), ".flutter-companion") end local get_flutter_command = function() local cmd = "flutter" .. utils.iff(utils.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 vim.api.nvim_command("FlutterRun -d " .. id) 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 = require("notify").notify( "Detecting " .. name .. "...", "info", { title = "Flutter Action", 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() require("notify").dismiss() 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 = "Flutter Action"}) 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.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() local config_path = get_config_path() local config_data = load_table(config_path) if config_data.device ~= nil then config_data.device = nil save_table(config_data, config_path) end end _G.Flutter_Companion_Run_Selected_Device = function() local config_path = get_config_path() local config_data = load_table(config_path) if config_data.device == nil then if callbacks[1] == nil then callbacks[1] = _G.Flutter_Companion_Run_Selected_Device M.flutter_list(false) end else flutter_run_device(config_data.device.id) end end _G.Flutter_Companion_Clear_Selected_Emulator = function() local config_path = get_config_path() local config_data = load_table(config_path) if config_data.emulator ~= nil then config_data.emulator = nil save_table(config_data, config_path) end end _G.Flutter_Companion_Run_Selected_Emulator = function() local config_path = get_config_path() local config_data = load_table(config_path) if config_data.emulator == nil then if callbacks[2] == nil then callbacks[2] = _G.Flutter_Companion_Run_Selected_Emulator M.flutter_list(true) end else local job = Job:new( {command = get_flutter_command(), args = {"emulators", "--launch", config_data.emulator.id}} ) job:start() end end return M