remove promise

This commit is contained in:
Ray Andrew 2025-11-29 15:59:59 -06:00
parent 572d3bfa80
commit cc55237709
Signed by: rayandrew
SSH key fingerprint: SHA256:XYrYrxF0Z3A72n8P/p6mqPRNQZT22F88XcLsG+kX4xw
4 changed files with 222 additions and 159 deletions

View file

@ -0,0 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
"runtime.version": "Lua 5.4",
"diagnostics.global": [
"sbar"
]
}

View file

@ -1,25 +1,28 @@
local Promise = require 'promise'
local colors = require 'colors' local colors = require 'colors'
local utils = require 'utils' local utils = require 'utils'
local settings = require 'settings' local settings = require 'settings'
local app_icons = require 'app_icons' local app_icons = require 'app_icons'
local async = utils.async
local await = utils.await
local awaitAll = utils.awaitAll
local exec = utils.exec
local aerospace_cmd = '/opt/homebrew/bin/aerospace' local aerospace_cmd = '/opt/homebrew/bin/aerospace'
-- Aerospace commands
local function getAllWorkspaces() local function getAllWorkspaces()
return utils.sbarExecP( return exec(aerospace_cmd .. " list-workspaces --all --format '%{workspace}%{monitor-appkit-nsscreen-screens-id}%{monitor-id}%{monitor-name}' --json")
aerospace_cmd .. " list-workspaces --all --format '%{workspace}%{monitor-appkit-nsscreen-screens-id}%{monitor-id}%{monitor-name}' --json"
)
end end
local function getVisibleWorkspaces() local function getVisibleWorkspaces()
return utils.sbarExecP( return exec(
aerospace_cmd .. " list-workspaces --visible --monitor all --format '%{workspace}%{monitor-appkit-nsscreen-screens-id}%{monitor-id}%{monitor-name}' --json" aerospace_cmd .. " list-workspaces --visible --monitor all --format '%{workspace}%{monitor-appkit-nsscreen-screens-id}%{monitor-id}%{monitor-name}' --json"
) )
end end
local function getAllWindows() local function getAllWindows()
return utils.sbarExecP( return exec(
aerospace_cmd aerospace_cmd
.. " list-windows --all --format '%{app-name}%{window-title}%{workspace}%{monitor-id}%{monitor-appkit-nsscreen-screens-id}%{monitor-name}' --json" .. " list-windows --all --format '%{app-name}%{window-title}%{workspace}%{monitor-id}%{monitor-appkit-nsscreen-screens-id}%{monitor-name}' --json"
) )
@ -37,6 +40,7 @@ local function getMonitorId(obj)
return tonumber(obj['monitor-id']) or 1 return tonumber(obj['monitor-id']) or 1
end end
-- State
local spaces = {} local spaces = {}
local space_paddings = {} local space_paddings = {}
local state = { local state = {
@ -44,10 +48,40 @@ local state = {
updating = false, updating = false,
} }
local function getState() -- Build app icons string from app list
local function buildAppIcons(apps)
local appkeys = {}
for app in pairs(apps) do
table.insert(appkeys, app)
end
table.sort(appkeys)
if #appkeys == 0 then return '' end
local icons = ''
for _, app in ipairs(appkeys) do
local lookup = app_icons[app]
local icon = lookup or app_icons['Default']
icons = icons .. ' ' .. icon
end
return icons
end
-- Fetch and build workspace state
local fetchState = async(function()
if state.updating then return end
state.updating = true
local ok, err = pcall(function()
local all, visible, windows = table.unpack(awaitAll {
getAllWorkspaces(),
getVisibleWorkspaces(),
getAllWindows(),
})
local newstate = { workspaces = {} } local newstate = { workspaces = {} }
-- Pre-initialize all workspaces from the spaces table -- Initialize all workspaces
for workspaceid, _ in pairs(spaces) do for workspaceid, _ in pairs(spaces) do
newstate.workspaces[workspaceid] = { newstate.workspaces[workspaceid] = {
id = workspaceid, id = workspaceid,
@ -55,122 +89,94 @@ local function getState()
active = false, active = false,
empty = true, empty = true,
apps = {}, apps = {},
appicons = '',
} }
end end
return Promise.all({ getAllWorkspaces(), getVisibleWorkspaces(), getAllWindows() }):thenCall(function(values) -- Set monitor IDs
local all, visible, apps = values[1], values[2], values[3] for _, ws in ipairs(all) do
local id = ws['workspace']
for _, workspace in ipairs(all) do if newstate.workspaces[id] then newstate.workspaces[id].monitor = getMonitorId(ws) end
local workspaceid = workspace['workspace']
if newstate.workspaces[workspaceid] then newstate.workspaces[workspaceid]['monitor'] = getMonitorId(workspace) end
end end
for _, workspace in ipairs(visible) do -- Mark visible workspaces as active
local workspaceid = workspace['workspace'] for _, ws in ipairs(visible) do
if newstate.workspaces[workspaceid] then newstate.workspaces[workspaceid]['active'] = true end local id = ws['workspace']
if newstate.workspaces[id] then newstate.workspaces[id].active = true end
end end
for _, window in ipairs(apps) do -- Collect apps per workspace
local workspaceid = window['workspace'] for _, win in ipairs(windows) do
local appname = window['app-name'] local id = win['workspace']
if newstate.workspaces[workspaceid] then local app = win['app-name']
newstate.workspaces[workspaceid]['apps'][appname] = true if newstate.workspaces[id] then
newstate.workspaces[workspaceid]['empty'] = false newstate.workspaces[id].apps[app] = true
newstate.workspaces[id].empty = false
end end
end end
for workspaceid, workspacestate in pairs(newstate.workspaces) do -- Build app icons
local appkeys = {} for _, ws in pairs(newstate.workspaces) do
for app in pairs(workspacestate['apps']) do ws.appicons = buildAppIcons(ws.apps)
table.insert(appkeys, app)
end
table.sort(appkeys)
if #appkeys > 0 then
for _, app in ipairs(appkeys) do
local lookup = app_icons[app]
local icon = ((lookup == nil) and app_icons['Default'] or lookup)
workspacestate['appicons'] = workspacestate['appicons'] .. ' ' .. icon
end
else
workspacestate['appicons'] = ''
end
end end
return newstate
end)
end
local function updateState()
if not state.updating then
state.updating = true
return getState()
:thenCall(function(newstate)
state.workspaces = newstate.workspaces state.workspaces = newstate.workspaces
state.updating = false
end) end)
:catch(function(err)
state.updating = false
print('Error updating state: ' .. tostring(err))
end)
end
return Promise.resolve()
end
state.updating = false
if not ok then print('Error fetching state: ' .. tostring(err)) end
end)
-- Sync UI with state
local function syncState() local function syncState()
sbar.animate('tanh', 10, function() sbar.animate('tanh', 10, function()
for workspaceid, workspacestate in pairs(state.workspaces) do for id, ws in pairs(state.workspaces) do
if not workspacestate['empty'] or workspacestate['active'] then local shouldShow = not ws.empty or ws.active
spaces[workspaceid]:set {
drawing = true, spaces[id]:set {
display = workspacestate['monitor'], drawing = shouldShow,
display = ws.monitor,
label = { label = {
string = workspacestate['appicons'], string = ws.appicons,
highlight = workspacestate['active'], highlight = ws.active,
}, },
icon = { icon = {
highlight = workspacestate['active'], highlight = ws.active,
}, },
} }
space_paddings[workspaceid]:set { drawing = true } space_paddings[id]:set { drawing = shouldShow }
else
spaces[workspaceid]:set {
drawing = false,
display = workspacestate['monitor'],
label = workspacestate['appicons'],
}
space_paddings[workspaceid]:set { drawing = false }
end
end end
end) end)
end end
local function updateStateAndSync() return updateState():thenCall(syncState) end local function updateStateAndSync()
fetchState()
-- Use a small delay to let fetchState complete before syncing
sbar.exec('sleep 0.1', syncState)
end
-- Handle workspace change event
local function onActiveSpaceChange(env) local function onActiveSpaceChange(env)
local focused_workspace = env.FOCUSED_WORKSPACE local focused = env.FOCUSED_WORKSPACE
local prev_workspace = env.PREV_WORKSPACE local prev = env.PREV_WORKSPACE
-- Immediately show the focused workspace with animation -- Immediately update UI for responsiveness
if spaces[focused_workspace] then if spaces[focused] then
sbar.animate('tanh', 10, function() sbar.animate('tanh', 10, function()
spaces[focused_workspace]:set { spaces[focused]:set {
drawing = true, drawing = true,
icon = { highlight = true }, icon = { highlight = true },
label = { highlight = true }, label = { highlight = true },
} }
space_paddings[focused_workspace]:set { drawing = true } space_paddings[focused]:set { drawing = true }
if spaces[prev_workspace] then if spaces[prev] then
spaces[prev_workspace]:set { spaces[prev]:set {
icon = { highlight = false }, icon = { highlight = false },
label = { highlight = false }, label = { highlight = false },
} }
-- Hide previous workspace if it's empty if state.workspaces[prev] and state.workspaces[prev].empty then
if state.workspaces[prev_workspace] and state.workspaces[prev_workspace]['empty'] then spaces[prev]:set { drawing = false }
spaces[prev_workspace]:set { drawing = false } space_paddings[prev]:set { drawing = false }
space_paddings[prev_workspace]:set { drawing = false }
end end
end end
end) end)
@ -179,19 +185,21 @@ local function onActiveSpaceChange(env)
updateStateAndSync() updateStateAndSync()
end end
local function setup() -- Setup
getAllWorkspaces() local setup = async(function()
:thenCall(function(workspaces) local workspaces = await(getAllWorkspaces())
for _, workspace in ipairs(workspaces) do
local workspaceid = workspace['workspace']
local display = getMonitorId(workspace)
local space = sbar.add('item', 'space.' .. workspaceid, { -- Create space items
for _, ws in ipairs(workspaces) do
local id = ws['workspace']
local display = getMonitorId(ws)
local space = sbar.add('item', 'space.' .. id, {
drawing = false, drawing = false,
updates = 'when_shown', updates = 'when_shown',
display = display, display = display,
icon = { icon = {
string = workspaceid, string = id,
color = colors.fg, color = colors.fg,
highlight_color = colors.red, highlight_color = colors.red,
}, },
@ -204,10 +212,9 @@ local function setup()
}, },
padding_left = 1, padding_left = 1,
padding_right = 1, padding_right = 1,
click_script = aerospace_cmd .. ' workspace ' .. workspaceid, click_script = aerospace_cmd .. ' workspace ' .. id,
}) })
spaces[id] = space
spaces[workspaceid] = space
local padding = sbar.add('space', 'space.padding.' .. space.name, { local padding = sbar.add('space', 'space.padding.' .. space.name, {
drawing = false, drawing = false,
@ -216,23 +223,24 @@ local function setup()
script = '', script = '',
width = settings.space_paddings, width = settings.space_paddings,
}) })
space_paddings[workspaceid] = padding space_paddings[id] = padding
end end
end)
:thenCall(function() -- Register events
sbar.add('event', 'aerospace_workspace_change') sbar.add('event', 'aerospace_workspace_change')
local space_window_observer = sbar.add('item', { local observer = sbar.add('item', {
drawing = false, drawing = false,
updates = true, updates = true,
}) })
space_window_observer:subscribe('aerospace_workspace_change', onActiveSpaceChange) observer:subscribe('aerospace_workspace_change', onActiveSpaceChange)
space_window_observer:subscribe('space_windows_change', updateStateAndSync) observer:subscribe('space_windows_change', updateStateAndSync)
space_window_observer:subscribe('system_woke', updateStateAndSync) observer:subscribe('system_woke', updateStateAndSync)
space_window_observer:subscribe('front_app_switched', updateStateAndSync) observer:subscribe('front_app_switched', updateStateAndSync)
end)
:thenCall(updateStateAndSync) -- Initial state sync
end updateStateAndSync()
end)
setup() setup()

View file

@ -1,5 +1,3 @@
local Promise = require 'promise'
local M = {} local M = {}
function M.dump(o) function M.dump(o)
@ -15,19 +13,68 @@ function M.dump(o)
end end
end end
local function onErrorP(reason) print('Error found: ' .. (reason and M.dump(reason) or 'unknown')) end -- Coroutine-based async/await helpers
function M.await(callback_fn)
local co = coroutine.running()
if not co then error 'await must be called inside async' end
-- https://github.com/Tnixc/nix-config/blob/main/home/programs/aerospace-sketchybar/sbar-config-libs/items/aerospaces.lua local result, err
function M.sbarExecP(cmd) local done = false
return Promise.new(function(resolve, failfunc)
callback_fn(function(res, e)
result = res
err = e
done = true
if coroutine.status(co) == 'suspended' then coroutine.resume(co) end
end)
if not done then coroutine.yield() end
if err then error(err) end
return result
end
function M.awaitAll(callback_fns)
local co = coroutine.running()
if not co then error 'awaitAll must be called inside async' end
local results = {}
local pending = #callback_fns
local err = nil
for i, fn in ipairs(callback_fns) do
fn(function(res, e)
if e then err = e end
results[i] = res
pending = pending - 1
if pending == 0 and coroutine.status(co) == 'suspended' then coroutine.resume(co) end
end)
end
if pending > 0 then coroutine.yield() end
if err then error(err) end
return results
end
function M.async(fn)
return function(...)
local args = { ... }
local co = coroutine.create(function() fn(table.unpack(args)) end)
local ok, err = coroutine.resume(co)
if not ok then print('Async error: ' .. tostring(err)) end
end
end
-- Execute a shell command, returns a callback function for use with await
function M.exec(cmd)
return function(resolve)
sbar.exec(cmd, function(result, exit_code) sbar.exec(cmd, function(result, exit_code)
if exit_code ~= 0 then if exit_code ~= 0 then
if failfunc ~= nil then failfunc(string.format('Exit Code: %s Message: %s', tostring(exit_code), M.dump(result))) end resolve(nil, string.format('Exit Code: %s Message: %s', tostring(exit_code), M.dump(result)))
else else
if resolve ~= nil then resolve(result) end resolve(result, nil)
end end
end) end)
end):catch(onErrorP) end
end end
return M return M

View file

@ -45,6 +45,7 @@ in
hm.home.packages = with pkgs; [ hm.home.packages = with pkgs; [
sketchybar-app-font sketchybar-app-font
custom.sk-utils
]; ];
hm.xdg.configFile = { hm.xdg.configFile = {