Files
herolib/lib/osal/tmux/play.v
2025-10-12 12:30:19 +03:00

752 lines
19 KiB
V

module tmux
import incubaid.herolib.core.playbook { PlayBook }
import incubaid.herolib.core.texttools
import incubaid.herolib.osal.core as osal
import incubaid.herolib.ui.console
pub fn play(mut plbook PlayBook) ! {
if !plbook.exists(filter: 'tmux.') {
return
}
// Create tmux instance
mut tmux_instance := new()!
// Start tmux if not running
if !tmux_instance.is_running()! {
tmux_instance.start()!
}
// Imperative functions (action after action)
play_session_create(mut plbook, mut tmux_instance)!
play_session_delete(mut plbook, mut tmux_instance)!
play_window_create(mut plbook, mut tmux_instance)!
play_window_delete(mut plbook, mut tmux_instance)!
play_pane_execute(mut plbook, mut tmux_instance)!
play_pane_kill(mut plbook, mut tmux_instance)!
play_pane_split(mut plbook, mut tmux_instance)!
play_session_ttyd(mut plbook, mut tmux_instance)!
play_window_ttyd(mut plbook, mut tmux_instance)!
play_session_ttyd_stop(mut plbook, mut tmux_instance)!
play_window_ttyd_stop(mut plbook, mut tmux_instance)!
play_ttyd_stop_all(mut plbook, mut tmux_instance)!
// Declarative functions (desired state)
play_session_ensure(mut plbook, mut tmux_instance)!
play_window_ensure(mut plbook, mut tmux_instance)!
play_pane_ensure(mut plbook, mut tmux_instance)!
}
struct ParsedWindowName {
session string
window string
}
struct ParsedPaneName {
session string
window string
pane string
}
fn parse_window_name(name string) !ParsedWindowName {
parts := name.split('|')
if parts.len != 2 {
return error('Window name must be in format "session|window", got: ${name}')
}
return ParsedWindowName{
session: texttools.name_fix_token(parts[0])
window: texttools.name_fix_token(parts[1])
}
}
fn parse_pane_name(name string) !ParsedPaneName {
parts := name.split('|')
if parts.len != 3 {
return error('Pane name must be in format "session|window|pane", got: ${name}')
}
return ParsedPaneName{
session: texttools.name_fix_token(parts[0])
window: texttools.name_fix_token(parts[1])
pane: texttools.name_fix_token(parts[2])
}
}
fn play_session_create(mut plbook PlayBook, mut tmux_instance Tmux) ! {
mut actions := plbook.find(filter: 'tmux.session_create')!
for mut action in actions {
mut p := action.params
session_name := p.get('name')!
reset := p.get_default_false('reset')
tmux_instance.session_create(
name: session_name
reset: reset
)!
action.done = true
}
}
fn play_session_delete(mut plbook PlayBook, mut tmux_instance Tmux) ! {
mut actions := plbook.find(filter: 'tmux.session_delete')!
for mut action in actions {
mut p := action.params
session_name := p.get('name')!
tmux_instance.session_delete(session_name)!
action.done = true
}
}
fn play_window_create(mut plbook PlayBook, mut tmux_instance Tmux) ! {
mut actions := plbook.find(filter: 'tmux.window_create')!
for mut action in actions {
mut p := action.params
name := p.get('name')!
parsed := parse_window_name(name)!
cmd := p.get_default('cmd', '')!
reset := p.get_default_false('reset')
// Parse environment variables if provided
mut env := map[string]string{}
if env_str := p.get_default('env', '') {
// Parse env as comma-separated key=value pairs
env_pairs := env_str.split(',')
for pair in env_pairs {
kv := pair.split('=')
if kv.len == 2 {
env[kv[0].trim_space()] = kv[1].trim_space()
}
}
}
// Get or create session
mut session := if tmux_instance.session_exist(parsed.session) {
tmux_instance.session_get(parsed.session)!
} else {
tmux_instance.session_create(name: parsed.session)!
}
session.window_new(
name: parsed.window
cmd: cmd
env: env
reset: reset
)!
action.done = true
}
}
fn play_window_delete(mut plbook PlayBook, mut tmux_instance Tmux) ! {
mut actions := plbook.find(filter: 'tmux.window_delete')!
for mut action in actions {
mut p := action.params
name := p.get('name')!
parsed := parse_window_name(name)!
if tmux_instance.session_exist(parsed.session) {
mut session := tmux_instance.session_get(parsed.session)!
session.window_delete(name: parsed.window)!
}
action.done = true
}
}
fn play_pane_execute(mut plbook PlayBook, mut tmux_instance Tmux) ! {
mut actions := plbook.find(filter: 'tmux.pane_execute')!
for mut action in actions {
mut p := action.params
name := p.get('name')!
cmd := p.get('cmd')!
parsed := parse_pane_name(name)!
// Find the session and window
if tmux_instance.session_exist(parsed.session) {
mut session := tmux_instance.session_get(parsed.session)!
if session.window_exist(name: parsed.window) {
mut window := session.window_get(name: parsed.window)!
// Send command to the window (goes to active pane by default)
tmux_cmd := 'tmux send-keys -t ${session.name}:@${window.id} "${cmd}" Enter'
osal.exec(cmd: tmux_cmd, stdout: false, name: 'tmux_pane_execute')!
}
}
action.done = true
}
}
fn play_pane_kill(mut plbook PlayBook, mut tmux_instance Tmux) ! {
mut actions := plbook.find(filter: 'tmux.pane_kill')!
for mut action in actions {
mut p := action.params
name := p.get('name')!
parsed := parse_pane_name(name)!
// Find the session and window, then kill the active pane
if tmux_instance.session_exist(parsed.session) {
mut session := tmux_instance.session_get(parsed.session)!
if session.window_exist(name: parsed.window) {
mut window := session.window_get(name: parsed.window)!
// Kill the active pane in the window
if pane := window.pane_active() {
tmux_cmd := 'tmux kill-pane -t ${session.name}:@${window.id}.%${pane.id}'
osal.exec(
cmd: tmux_cmd
stdout: false
name: 'tmux_pane_kill'
ignore_error: true
)!
}
}
}
action.done = true
}
}
fn play_pane_split(mut plbook PlayBook, mut tmux_instance Tmux) ! {
mut actions := plbook.find(filter: 'tmux.pane_split')!
for mut action in actions {
mut p := action.params
name := p.get('name')!
cmd := p.get_default('cmd', '')!
horizontal := p.get_default_false('horizontal')
parsed := parse_window_name(name)!
// Parse environment variables if provided
mut env := map[string]string{}
if env_str := p.get_default('env', '') {
env_pairs := env_str.split(',')
for pair in env_pairs {
kv := pair.split('=')
if kv.len == 2 {
env[kv[0].trim_space()] = kv[1].trim_space()
}
}
}
// Find the session and window
if tmux_instance.session_exist(parsed.session) {
mut session := tmux_instance.session_get(parsed.session)!
if session.window_exist(name: parsed.window) {
mut window := session.window_get(name: parsed.window)!
// Split the pane
window.pane_split(
cmd: cmd
horizontal: horizontal
env: env
)!
}
}
action.done = true
}
}
fn play_session_ttyd(mut plbook PlayBook, mut tmux_instance Tmux) ! {
mut actions := plbook.find(filter: 'tmux.session_ttyd')!
for mut action in actions {
mut p := action.params
session_name := p.get('name')!
port := p.get_int('port')!
editable := p.get_default_false('editable')
if tmux_instance.session_exist(session_name) {
mut session := tmux_instance.session_get(session_name)!
session.run_ttyd(
port: port
editable: editable
)!
}
action.done = true
}
}
fn play_window_ttyd(mut plbook PlayBook, mut tmux_instance Tmux) ! {
mut actions := plbook.find(filter: 'tmux.window_ttyd')!
for mut action in actions {
mut p := action.params
name := p.get('name')!
port := p.get_int('port')!
editable := p.get_default_false('editable')
parsed := parse_window_name(name)!
if tmux_instance.session_exist(parsed.session) {
mut session := tmux_instance.session_get(parsed.session)!
if session.window_exist(name: parsed.window) {
mut window := session.window_get(name: parsed.window)!
window.run_ttyd(
port: port
editable: editable
)!
}
}
action.done = true
}
}
// Handle tmux.session_ttyd_stop actions
fn play_session_ttyd_stop(mut plbook PlayBook, mut tmux_instance Tmux) ! {
for mut action in plbook.find(filter: 'tmux.session_ttyd_stop')! {
if action.done {
continue
}
mut p := action.params
session_name := p.get('name')!
port := p.get_int('port')!
mut session := tmux_instance.session_get(session_name)!
session.stop_ttyd(port)!
action.done = true
}
}
// Handle tmux.window_ttyd_stop actions
fn play_window_ttyd_stop(mut plbook PlayBook, mut tmux_instance Tmux) ! {
for mut action in plbook.find(filter: 'tmux.window_ttyd_stop')! {
if action.done {
continue
}
mut p := action.params
name := p.get('name')!
port := p.get_int('port')!
parsed := parse_window_name(name)!
mut session := tmux_instance.session_get(parsed.session)!
mut window := session.window_get(name: parsed.window)!
window.stop_ttyd(port)!
action.done = true
}
}
// Handle tmux.ttyd_stop_all actions
fn play_ttyd_stop_all(mut plbook PlayBook, mut tmux_instance Tmux) ! {
for mut action in plbook.find(filter: 'tmux.ttyd_stop_all')! {
if action.done {
continue
}
stop_all_ttyd()!
action.done = true
}
}
// DECLARATIVE FUNCTIONS - Ensure desired state exists
// Ensure session exists (declarative)
fn play_session_ensure(mut plbook PlayBook, mut tmux_instance Tmux) ! {
mut actions := plbook.find(filter: 'tmux.session_ensure')!
for mut action in actions {
mut p := action.params
session_name := p.get('name')!
// Ensure session exists, create if it doesn't
if !tmux_instance.session_exist(session_name) {
tmux_instance.session_create(name: session_name)!
}
action.done = true
}
}
// Pane layout configurations for different categories
struct PaneLayout {
splits []PaneSplit
}
struct PaneSplit {
horizontal bool
target_pane int // which pane to split (0-based index)
}
// Get pane layout configuration based on category
fn get_pane_layout(category string) PaneLayout {
match category {
'1pane' {
return PaneLayout{
splits: []
}
}
'2pane' {
return PaneLayout{
splits: [
PaneSplit{
horizontal: true
target_pane: 0
},
]
}
}
'4pane' {
return PaneLayout{
splits: [
PaneSplit{
horizontal: true
target_pane: 0
}, // Split horizontally first
PaneSplit{
horizontal: false
target_pane: 0
}, // Split left pane vertically
PaneSplit{
horizontal: false
target_pane: 1
}, // Split right pane vertically
]
}
}
'6pane' {
return PaneLayout{
splits: [
PaneSplit{
horizontal: true
target_pane: 0
}, // Split horizontally
PaneSplit{
horizontal: true
target_pane: 1
}, // Split right pane horizontally
PaneSplit{
horizontal: false
target_pane: 0
}, // Split left pane vertically
PaneSplit{
horizontal: false
target_pane: 1
}, // Split middle pane vertically
PaneSplit{
horizontal: false
target_pane: 2
}, // Split right pane vertically
]
}
}
'8pane' {
return PaneLayout{
splits: [
PaneSplit{
horizontal: true
target_pane: 0
}, // Split horizontally
PaneSplit{
horizontal: false
target_pane: 0
}, // Split left vertically
PaneSplit{
horizontal: false
target_pane: 1
}, // Split right vertically
PaneSplit{
horizontal: true
target_pane: 0
}, // Split top-left horizontally
PaneSplit{
horizontal: true
target_pane: 1
}, // Split bottom-left horizontally
PaneSplit{
horizontal: true
target_pane: 2
}, // Split top-right horizontally
PaneSplit{
horizontal: true
target_pane: 3
}, // Split bottom-right horizontally
]
}
}
'12pane' {
return PaneLayout{
splits: [
PaneSplit{
horizontal: true
target_pane: 0
}, // Split horizontally (2 panes)
PaneSplit{
horizontal: true
target_pane: 1
}, // Split right horizontally (3 panes)
PaneSplit{
horizontal: false
target_pane: 0
}, // Split left vertically (4 panes)
PaneSplit{
horizontal: false
target_pane: 1
}, // Split middle vertically (5 panes)
PaneSplit{
horizontal: false
target_pane: 2
}, // Split right vertically (6 panes)
PaneSplit{
horizontal: true
target_pane: 0
}, // Split top-left horizontally (7 panes)
PaneSplit{
horizontal: true
target_pane: 1
}, // Split bottom-left horizontally (8 panes)
PaneSplit{
horizontal: true
target_pane: 2
}, // Split top-middle horizontally (9 panes)
PaneSplit{
horizontal: true
target_pane: 3
}, // Split bottom-middle horizontally (10 panes)
PaneSplit{
horizontal: true
target_pane: 4
}, // Split top-right horizontally (11 panes)
PaneSplit{
horizontal: true
target_pane: 5
}, // Split bottom-right horizontally (12 panes)
]
}
}
'16pane' {
return PaneLayout{
splits: [
PaneSplit{
horizontal: true
target_pane: 0
}, // Split horizontally (2 panes)
PaneSplit{
horizontal: false
target_pane: 0
}, // Split left vertically (3 panes)
PaneSplit{
horizontal: false
target_pane: 1
}, // Split right vertically (4 panes)
PaneSplit{
horizontal: true
target_pane: 0
}, // Split top-left horizontally (5 panes)
PaneSplit{
horizontal: true
target_pane: 1
}, // Split bottom-left horizontally (6 panes)
PaneSplit{
horizontal: true
target_pane: 2
}, // Split top-right horizontally (7 panes)
PaneSplit{
horizontal: true
target_pane: 3
}, // Split bottom-right horizontally (8 panes)
PaneSplit{
horizontal: false
target_pane: 0
}, // Split first quarter vertically (9 panes)
PaneSplit{
horizontal: false
target_pane: 1
}, // Split second quarter vertically (10 panes)
PaneSplit{
horizontal: false
target_pane: 2
}, // Split third quarter vertically (11 panes)
PaneSplit{
horizontal: false
target_pane: 3
}, // Split fourth quarter vertically (12 panes)
PaneSplit{
horizontal: false
target_pane: 4
}, // Split fifth quarter vertically (13 panes)
PaneSplit{
horizontal: false
target_pane: 5
}, // Split sixth quarter vertically (14 panes)
PaneSplit{
horizontal: false
target_pane: 6
}, // Split seventh quarter vertically (15 panes)
PaneSplit{
horizontal: false
target_pane: 7
}, // Split eighth quarter vertically (16 panes)
]
}
}
else {
// Default to 1pane if unknown category
return PaneLayout{
splits: []
}
}
}
}
// Ensure window exists with specified pane layout (declarative)
fn play_window_ensure(mut plbook PlayBook, mut tmux_instance Tmux) ! {
mut actions := plbook.find(filter: 'tmux.window_ensure')!
for mut action in actions {
mut p := action.params
name := p.get('name')!
parsed := parse_window_name(name)!
category := p.get_default('cat', '1pane')!
cmd := p.get_default('cmd', '')!
// Parse environment variables if provided
mut env := map[string]string{}
if env_str := p.get_default('env', '') {
env_pairs := env_str.split(',')
for pair in env_pairs {
kv := pair.split('=')
if kv.len == 2 {
env[kv[0].trim_space()] = kv[1].trim_space()
}
}
}
// Ensure session exists
mut session := if tmux_instance.session_exist(parsed.session) {
tmux_instance.session_get(parsed.session)!
} else {
tmux_instance.session_create(name: parsed.session)!
}
// Check if window already exists with correct pane layout
mut window_exists := session.window_exist(name: parsed.window)
mut window := if window_exists {
session.window_get(name: parsed.window)!
} else {
// Create new window
session.window_new(
name: parsed.window
cmd: cmd
env: env
)!
}
// Ensure correct pane layout
layout := get_pane_layout(category)
current_pane_count := window.panes.len
// If we need more panes, create them according to layout
if layout.splits.len + 1 > current_pane_count {
// We need to create the layout from scratch
// First, ensure we have at least one pane (the window should have one by default)
window.scan()! // Refresh pane information
// Apply splits according to layout
for split in layout.splits {
// For simplicity, we'll split the active pane
// In a more sophisticated implementation, we could track specific panes
window.pane_split(
cmd: cmd
horizontal: split.horizontal
env: env
)!
}
// After creating all panes, resize them to equal dimensions dynamically
window.resize_panes_equal()!
}
action.done = true
}
}
// Ensure specific pane exists with command and label (declarative)
fn play_pane_ensure(mut plbook PlayBook, mut tmux_instance Tmux) ! {
mut actions := plbook.find(filter: 'tmux.pane_ensure')!
for mut action in actions {
mut p := action.params
name := p.get('name')!
parsed := parse_pane_name(name)!
cmd := p.get_default('cmd', '')!
// label := p.get_default('label', '')!
// Parse environment variables if provided
mut env := map[string]string{}
if env_str := p.get_default('env', '') {
env_pairs := env_str.split(',')
for pair in env_pairs {
kv := pair.split('=')
if kv.len == 2 {
env[kv[0].trim_space()] = kv[1].trim_space()
}
}
}
// Ensure session exists
mut session := if tmux_instance.session_exist(parsed.session) {
tmux_instance.session_get(parsed.session)!
} else {
tmux_instance.session_create(name: parsed.session)!
}
// Ensure window exists
mut window := if session.window_exist(name: parsed.window) {
session.window_get(name: parsed.window)!
} else {
session.window_new(name: parsed.window)!
}
// Refresh pane information
window.scan()!
// Check if we need to create more panes or execute command in existing pane
pane_number := parsed.pane.int()
// Ensure we have enough panes (create splits if needed)
for window.panes.len < pane_number {
window.pane_split(
cmd: '/bin/bash'
horizontal: window.panes.len % 2 == 0 // Alternate between horizontal and vertical
env: env
)!
}
// Execute command in the specified pane if provided
if cmd.len > 0 {
// Find the target pane (by index, since tmux pane IDs can vary)
if pane_number > 0 && pane_number <= window.panes.len {
mut target_pane := window.panes[pane_number - 1] // Convert to 0-based index
// Use declarative command logic for intelligent state management
target_pane.send_command_declarative(cmd)!
}
}
// Handle logging parameters - enable logging if requested
log_enabled := p.get_default_false('log')
if log_enabled {
logpath := p.get_default('logpath', '')!
logreset := p.get_default_false('logreset')
// Find the target pane for logging
if pane_number > 0 && pane_number <= window.panes.len {
mut target_pane := window.panes[pane_number - 1] // Convert to 0-based index
// Enable logging with automation (binary compilation, directory creation, etc.)
target_pane.logging_enable(
logpath: logpath
logreset: logreset
) or {
console.print_debug('Warning: Failed to enable logging for pane ${name}: ${err}')
}
}
}
action.done = true
}
}