repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
wezterm/bugabinga/workspace_switcher.lua
Raw--- stolen and adapted from https://github.com/MLFlexer/smart_workspace_switcher.wezterm/blob/main/plugin/init.lua
local wezterm = require 'wezterm'
local act = wezterm.action
local mux = wezterm.mux
local instances = require 'bugabinga.instances'
local zoxide_path = 'zoxide'
local function workspace_formatter( label )
return wezterm.format {
{ Text = ' : ' .. label, },
}
end
local run_child_process = function ( args )
local success, stdout, stderr = wezterm.run_child_process( args )
if not success then
wezterm.log_error( "Child process '" .. table.concat( args, ' ' ) .. "' failed with stderr: '" .. stderr .. "'" )
end
return stdout or ''
end
local function get_workspace_elements( choice_table, include_instances )
local workspace_ids = {}
for _, workspace in ipairs( mux.get_workspace_names() ) do
table.insert( choice_table, {
id = workspace,
label = workspace_formatter( workspace ),
} )
workspace_ids[workspace] = true
end
if include_instances then
local current_pid = instances.current_pid()
local seen = {}
for _, row in ipairs( instances.list() or {} ) do
local key = tostring( row.gui_pid ) .. ':' .. (row.workspace or 'default')
if row.gui_pid ~= current_pid and not seen[key] then
local id = instances.target_id( row.gui_pid, row.pane_id )
local title = wezterm.truncate_right( row.title or 'WezTerm', 40 )
table.insert( choice_table, {
id = id,
label = workspace_formatter( (row.workspace or 'default') .. ' ' .. title ),
} )
workspace_ids[id] = true
seen[key] = true
end
end
end
return choice_table, workspace_ids
end
local function display_path( path )
if path:sub( 1, #wezterm.home_dir ) == wezterm.home_dir then
return '~' .. path:sub( #wezterm.home_dir + 1 )
end
return path
end
local function add_path_element( choice_table, workspace_ids, path, label )
if not path or path == '' then return end
local workspace = display_path( path )
if not workspace_ids[workspace] then
table.insert( choice_table, {
id = path,
label = label or workspace,
} )
workspace_ids[workspace] = true
end
end
local function get_zoxide_elements( choice_table, workspace_ids )
local stdout = run_child_process { zoxide_path, 'query', '-l', }
for _, path in ipairs( wezterm.split_by_newlines( stdout ) ) do
add_path_element( choice_table, workspace_ids, path )
end
return choice_table
end
local function parse_worktrees( output )
local worktrees = {}
local current = nil
local function finish()
if current and current.path then table.insert( worktrees, current ) end
current = nil
end
for raw_line in (output .. '\n'):gmatch '([^\n]*)\n' do
local line = raw_line:gsub( '\r$', '' )
if line == '' then
finish()
else
local path = line:match '^worktree (.+)$'
local branch = line:match '^branch refs/heads/(.+)$'
if path then
finish()
current = { path = path, branch = 'detached', }
elseif branch and current then
current.branch = branch
elseif line == 'bare' and current then
current.branch = 'bare'
end
end
end
finish()
return worktrees
end
local function pane_cwd( pane )
local ok, cwd = pcall( pane.get_current_working_dir, pane )
if not ok or not cwd then return nil end
local ok_path, path = pcall( function () return cwd.file_path end )
if not ok_path or not path then
path = tostring( cwd ):match '^file://[^/]*(/.*)$'
end
if not path then return nil end
if wezterm.target_triple:find( 'windows', 1, true ) and path:match '^/[A-Za-z]:/' then
path = path:sub( 2 )
end
return path
end
local function get_worktree_elements( choice_table, workspace_ids, pane )
local cwd = pane_cwd( pane )
if not cwd then return choice_table end
local success, stdout = wezterm.run_child_process { 'git', '-C', cwd, 'worktree', 'list', '--porcelain', }
if not success then return choice_table end
for _, worktree in ipairs( parse_worktrees( stdout ) ) do
add_path_element(
choice_table,
workspace_ids,
worktree.path,
'git ' .. worktree.branch .. ' ' .. display_path( worktree.path )
)
end
return choice_table
end
local function is_dir( path )
-- wezterm.read_dir returns children only if path itself is a directory.
local ok, result = pcall( wezterm.read_dir, path )
return ok and result ~= nil
end
local function get_workspace_dir_elements( choice_table, workspace_ids )
local workspace_dir = wezterm.home_dir .. '/Workspace'
local ok, entries = pcall( wezterm.read_dir, workspace_dir )
if not ok or not entries then return choice_table end
local paths = {}
for _, entry in ipairs( entries ) do
if is_dir( entry ) then
table.insert( paths, entry )
end
end
table.sort( paths )
for _, path in ipairs( paths ) do
add_path_element( choice_table, workspace_ids, path )
end
return choice_table
end
local function get_choices( pane, include_instances )
local choices = {}
local _, workspace_ids = get_workspace_elements( choices, include_instances )
get_worktree_elements( choices, workspace_ids, pane )
get_zoxide_elements( choices, workspace_ids )
get_workspace_dir_elements( choices, workspace_ids )
return choices
end
local function zoxide_chosen( window, pane, path, label_path )
local default_prog = window:effective_config().default_prog
window:perform_action(
act.SwitchToWorkspace {
name = label_path,
spawn = {
args = default_prog,
label = 'Workspace: ' .. label_path,
cwd = path,
},
},
pane
)
-- increment zoxide path score
run_child_process { zoxide_path, 'add', path, }
end
local function workspace_chosen( window, pane, workspace )
window:perform_action(
act.SwitchToWorkspace {
name = workspace,
},
pane
)
end
local function spawn_workspace_window( _window, workspace, cwd )
-- `mux.spawn_window()` and `wezterm cli spawn --new-window` create mux
-- windows, which can appear in the workspace HUD without necessarily mapping
-- a new OS/WM window. For the workflow-window use-case, force a fresh GUI.
local args = { os.getenv 'WEZTERM_EXECUTABLE' or 'wezterm', 'start', '--always-new-process', '--workspace', workspace, }
if cwd then
table.insert( args, '--cwd' )
table.insert( args, cwd )
end
wezterm.background_child_process( args )
end
local function zoxide_chosen_in_new_window( window, path, label_path )
spawn_workspace_window( window, label_path, path )
-- increment zoxide path score
run_child_process { zoxide_path, 'add', path, }
end
local function workspace_chosen_in_new_window( window, workspace )
spawn_workspace_window( window, workspace )
end
local function workspace_exists( workspace )
for _, workspace_name in ipairs( mux.get_workspace_names() ) do
if workspace == workspace_name then
return true
end
end
return false
end
local function choose_workspace( title, fuzzy_description, include_instances, handler )
return wezterm.action_callback( function ( window, pane )
local choices = get_choices( pane, include_instances )
window:perform_action(
act.InputSelector {
action = wezterm.action_callback( function ( inner_window, inner_pane, id )
if id then handler( inner_window, inner_pane, id ) end
end ),
title = title,
description = 'Select a workspace and press Enter = accept, Esc = cancel, / = filter',
fuzzy_description = fuzzy_description,
choices = choices,
fuzzy = true,
},
pane
)
end )
end
local function switch_workspace()
return choose_workspace( 'Choose Workspace', 'Workspace to switch: ', true, function ( window, pane, id )
local gui_pid, pane_id = instances.parse_target_id( id )
if gui_pid then
instances.activate( gui_pid, pane_id )
elseif workspace_exists( id ) then
workspace_chosen( window, pane, id )
else
zoxide_chosen( window, pane, id, display_path( id ) )
end
end )
end
local function switch_workspace_in_new_window()
return choose_workspace(
'Choose Workspace in New Window',
'Workspace for new window: ',
false,
function ( window, pane, id )
if workspace_exists( id ) then
workspace_chosen_in_new_window( window, id )
else
zoxide_chosen_in_new_window( window, id, display_path( id ) )
end
end
)
end
return {
switch_workspace = switch_workspace,
switch_workspace_in_new_window = switch_workspace_in_new_window,
}