wip: pushing the code to sync in other branch

This commit is contained in:
Mahmoud-Emad
2025-08-24 17:58:09 +03:00
parent 117c9ac67c
commit b26893bf45
9 changed files with 892 additions and 188 deletions

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.osal.tmux
mut t := tmux.new()!
// if !t.is_running()! {
// t.start()!
// }
// if t.session_exist('main') {
// t.session_delete('main')!
// }
// // Create session first, then create window
// mut session := t.session_create(name: 'main')!
// session.window_new(name: 'test', cmd: 'mc', reset: true)!
// // Or use the convenience method
// // t.window_new(session_name: 'main', name: 'test', cmd: 'mc', reset: true)!
println(t)

122
examples/tmux/tmux.vsh Executable file
View File

@@ -0,0 +1,122 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.osal.tmux
import freeflowuniverse.herolib.osal.core as osal
import time
// Constants for display formatting
const (
bytes_to_mb = 1024.0 * 1024.0
cpu_precision = 1
memory_precision = 3
)
println('=== Tmux Pane Example ===')
mut t := tmux.new()!
if !t.is_running()! {
println('Starting tmux server...')
t.start()!
}
if t.session_exist('demo') {
println('Deleting existing demo session...')
t.session_delete('demo')!
}
// Create session and window
println('Creating demo session...')
mut session := t.session_create(name: 'demo')!
println('Creating main window with htop...')
mut window := session.window_new(name: 'main', cmd: 'htop', reset: true)!
// Wait a moment for the window to be created
time.sleep(500 * time.millisecond)
// Refresh to get current state
t.scan()!
println('\n=== Current Tmux State ===')
println(t)
// Get the window and demonstrate pane functionality
mut main_window := session.window_get(name: 'main')!
println('\n=== Window Pane Information ===')
println('Window: ${main_window.name} (ID: ${main_window.id})')
println('Number of panes: ${main_window.panes.len}')
for i, mut pane in main_window.panes {
println('Pane ${i}: ID=%${pane.id}, PID=${pane.pid}, Active=${pane.active}, Cmd="${pane.cmd}"')
// Get pane stats
stats := pane.stats() or {
println(' Could not get stats: ${err}')
continue
}
memory_mb := f64(stats.memory_bytes) / bytes_to_mb
println(' CPU: ${stats.cpu_percent:.1f}%, Memory: ${stats.memory_percent:.3f}% (${memory_mb:.1f} MB)')
}
// Get the active pane
if mut active_pane := main_window.pane_active() {
println('\n=== Active Pane Details ===')
println('Active pane ID: %${active_pane.id}')
println('Process ID: ${active_pane.pid}')
println('Command: ${active_pane.cmd}')
// Get process information
process_info := active_pane.processinfo_main() or {
println('Could not get process info: ${err}')
osal.ProcessInfo{}
}
if process_info.pid > 0 {
println('Process info: PID=${process_info.pid}, Command=${process_info.cmd}')
}
// Get recent logs
println('\n=== Recent Pane Output ===')
logs := active_pane.logs_all() or {
println('Could not get logs: ${err}')
''
}
if logs.len > 0 {
lines := logs.split_into_lines()
// Show last 5 lines
start_idx := if lines.len > 5 { lines.len - 5 } else { 0 }
for i in start_idx .. lines.len {
if lines[i].trim_space().len > 0 {
println(' ${lines[i]}')
}
}
}
} else {
println('No active pane found')
}
println('\n=== Creating Additional Windows ===')
// Create more windows to demonstrate multiple panes
mut monitor_window := session.window_new(name: 'monitor', cmd: 'top', reset: true)!
mut logs_window := session.window_new(name: 'logs', cmd: 'tail -f /var/log/system.log', reset: true)!
time.sleep(500 * time.millisecond)
t.scan()!
println('\n=== Final Tmux State ===')
println(t)
println('\n=== Window Statistics ===')
for mut win in session.windows {
println('Window: ${win.name}')
window_stats := win.stats() or {
println(' Could not get window stats: ${err}')
continue
}
memory_mb := f64(window_stats.memory_bytes) / bytes_to_mb
println(' Total CPU: ${window_stats.cpu_percent:.1f}%')
println(' Total Memory: ${window_stats.memory_percent:.3f}% (${memory_mb:.1f} MB)')
println(' Panes: ${win.panes.len}')
}

View File

@@ -0,0 +1,143 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.osal.tmux
import time
println('=== Tmux Pane Resizing Example ===')
mut t := tmux.new()!
if !t.is_running()! {
println('Starting tmux server...')
t.start()!
}
if t.session_exist('resize_demo') {
println('Deleting existing resize_demo session...')
t.session_delete('resize_demo')!
}
// Create session and window
println('Creating resize_demo session...')
mut session := t.session_create(name: 'resize_demo')!
println('Creating main window...')
mut window := session.window_new(name: 'main', cmd: 'bash', reset: true)!
time.sleep(500 * time.millisecond)
t.scan()!
// Create a 2x2 grid of panes
println('\n=== Creating 2x2 Grid of Panes ===')
// Split horizontally first (left | right)
mut right_pane := window.pane_split_horizontal('htop')!
time.sleep(300 * time.millisecond)
// Split left pane vertically (top-left, bottom-left)
window.scan()!
if window.panes.len > 1 {
mut left_pane := window.panes[1] // The original bash pane
left_pane.select()!
time.sleep(200 * time.millisecond)
}
mut bottom_left_pane := window.pane_split_vertical('top')!
time.sleep(300 * time.millisecond)
// Split right pane vertically (top-right, bottom-right)
window.scan()!
for mut pane in window.panes {
if pane.cmd.contains('htop') {
pane.select()!
break
}
}
time.sleep(200 * time.millisecond)
mut bottom_right_pane := window.pane_split_vertical('tail -f /var/log/system.log')!
time.sleep(500 * time.millisecond)
window.scan()!
println('Created 2x2 grid with ${window.panes.len} panes:')
for i, pane in window.panes {
println(' Pane ${i}: ID=%${pane.id}, Cmd="${pane.cmd}"')
}
// Demonstrate resizing operations
println('\n=== Demonstrating Pane Resizing ===')
// Get references to panes for resizing
window.scan()!
if window.panes.len >= 4 {
mut top_left := window.panes[1] // bash
mut top_right := window.panes[0] // htop
mut bottom_left := window.panes[2] // top
mut bottom_right := window.panes[3] // tail
println('Resizing top-left pane (bash) to be wider...')
top_left.select()!
time.sleep(200 * time.millisecond)
top_left.resize_right(10)!
time.sleep(1000 * time.millisecond)
println('Resizing top-right pane (htop) to be taller...')
top_right.select()!
time.sleep(200 * time.millisecond)
top_right.resize_down(5)!
time.sleep(1000 * time.millisecond)
println('Resizing bottom-left pane (top) to be narrower...')
bottom_left.select()!
time.sleep(200 * time.millisecond)
bottom_left.resize_left(5)!
time.sleep(1000 * time.millisecond)
println('Resizing bottom-right pane (tail) to be shorter...')
bottom_right.select()!
time.sleep(200 * time.millisecond)
bottom_right.resize_up(3)!
time.sleep(1000 * time.millisecond)
// Demonstrate using the generic resize method
println('Using generic resize method to make top-left pane taller...')
top_left.select()!
time.sleep(200 * time.millisecond)
top_left.resize(direction: 'down', cells: 3)!
time.sleep(1000 * time.millisecond)
}
// Send some commands to make the panes more interesting
println('\n=== Adding Content to Panes ===')
window.scan()!
if window.panes.len >= 4 {
// Send commands to bash pane
mut bash_pane := window.panes[1]
bash_pane.send_command('echo "=== Bash Pane ==="')!
bash_pane.send_command('ls -la')!
bash_pane.send_command('pwd')!
time.sleep(500 * time.millisecond)
// Send command to top pane
mut top_pane := window.panes[2]
top_pane.send_command('echo "=== Top Pane ==="')!
time.sleep(500 * time.millisecond)
}
println('\n=== Final Layout ===')
t.scan()!
println('Session: ${session.name}')
println('Window: ${window.name} (${window.panes.len} panes)')
for i, pane in window.panes {
println(' ${i+1}. Pane %${pane.id} - ${pane.cmd}')
}
println('\n=== Pane Resize Operations Available ===')
println(' resize_up(cells) - Make pane taller by shrinking pane above')
println(' resize_down(cells) - Make pane taller by shrinking pane below')
println(' resize_left(cells) - Make pane wider by shrinking pane to the left')
println(' resize_right(cells) - Make pane wider by shrinking pane to the right')
println(' resize(direction: "up/down/left/right", cells: N) - Generic resize method')
println('\nExample completed! You can attach to the session with:')
println(' tmux attach-session -t resize_demo')
println('\nThen use Ctrl+B followed by arrow keys to manually resize panes,')
println('or Ctrl+B followed by Alt+arrow keys for larger resize steps.')

170
examples/tmux/tmux_panes.vsh Executable file
View File

@@ -0,0 +1,170 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.osal.tmux
import time
println('=== Tmux Pane Splitting Example ===')
mut t := tmux.new()!
if !t.is_running()! {
println('Starting tmux server...')
t.start()!
}
if t.session_exist('panes_demo') {
println('Deleting existing panes_demo session...')
t.session_delete('panes_demo')!
}
// Create session and initial window
println('Creating panes_demo session...')
mut session := t.session_create(name: 'panes_demo')!
println('Creating main window...')
mut window := session.window_new(name: 'main', cmd: 'bash', reset: true)!
// Wait for initial setup
time.sleep(500 * time.millisecond)
t.scan()!
println('\n=== Initial State ===')
println('Window: ${window.name} (ID: ${window.id})')
println('Number of panes: ${window.panes.len}')
// Split the window horizontally (side by side)
println('\n=== Splitting Horizontally (Side by Side) ===')
mut right_pane := window.pane_split_horizontal('htop')!
time.sleep(500 * time.millisecond)
window.scan()!
println('After horizontal split:')
println('Number of panes: ${window.panes.len}')
for i, mut pane in window.panes {
println(' Pane ${i}: ID=%${pane.id}, PID=${pane.pid}, Active=${pane.active}, Cmd="${pane.cmd}"')
}
// Split the right pane vertically (top and bottom)
println('\n=== Splitting Right Pane Vertically (Top and Bottom) ===')
// Get a fresh reference to the right pane after the first split
window.scan()!
if window.panes.len > 0 {
// Find the pane with htop command (the one we just created)
mut right_pane_fresh := &window.panes[0]
for mut pane in window.panes {
if pane.cmd.contains('htop') {
right_pane_fresh = pane
break
}
}
// Select the right pane to make it active
right_pane_fresh.select()!
time.sleep(200 * time.millisecond)
}
mut bottom_pane := window.pane_split_vertical('top')!
time.sleep(500 * time.millisecond)
window.scan()!
println('After vertical split of right pane:')
println('Number of panes: ${window.panes.len}')
for i, mut pane in window.panes {
println(' Pane ${i}: ID=%${pane.id}, PID=${pane.pid}, Active=${pane.active}, Cmd="${pane.cmd}"')
}
// Send commands to different panes
println('\n=== Sending Commands to Panes ===')
// Get the first pane (left side) and send some commands
if window.panes.len > 0 {
mut left_pane := window.panes[0]
println('Sending commands to left pane (ID: %${left_pane.id})')
left_pane.send_command('echo "Hello from left pane!"')!
time.sleep(200 * time.millisecond)
left_pane.send_command('ls -la')!
time.sleep(200 * time.millisecond)
left_pane.send_command('pwd')!
time.sleep(200 * time.millisecond)
}
// Send command to bottom pane
if window.panes.len > 2 {
mut bottom_pane_ref := window.panes[2]
println('Sending command to bottom pane (ID: %${bottom_pane_ref.id})')
bottom_pane_ref.send_command('echo "Hello from bottom pane!"')!
time.sleep(200 * time.millisecond)
}
// Capture output from panes
println('\n=== Capturing Pane Output ===')
for i, mut pane in window.panes {
println('Output from Pane ${i} (ID: %${pane.id}):')
logs := pane.logs_all() or {
println(' Could not get logs: ${err}')
continue
}
if logs.len > 0 {
lines := logs.split_into_lines()
// Show last 3 lines
start_idx := if lines.len > 3 { lines.len - 3 } else { 0 }
for j in start_idx .. lines.len {
if lines[j].trim_space().len > 0 {
println(' ${lines[j]}')
}
}
}
println('')
}
// Demonstrate pane selection
println('\n=== Demonstrating Pane Selection ===')
for i, mut pane in window.panes {
println('Selecting pane ${i} (ID: %${pane.id})')
pane.select()!
time.sleep(300 * time.millisecond)
}
// Final state
println('\n=== Final Tmux State ===')
t.scan()!
println(t)
println('\n=== Pane Management Summary ===')
println('Created ${window.panes.len} panes in window "${window.name}":')
for i, pane in window.panes {
println(' ${i + 1}. Pane %${pane.id} - PID: ${pane.pid} - Command: ${pane.cmd}')
}
// Demonstrate killing a pane
println('\n=== Demonstrating Pane Killing ===')
if window.panes.len > 2 {
mut pane_to_kill := window.panes[2] // Kill the bottom pane
println('Killing pane %${pane_to_kill.id} (${pane_to_kill.cmd})')
pane_to_kill.kill()!
time.sleep(500 * time.millisecond)
window.scan()!
println('After killing pane:')
println('Number of panes: ${window.panes.len}')
for i, pane in window.panes {
println(' Pane ${i}: ID=%${pane.id}, PID=${pane.pid}, Cmd="${pane.cmd}"')
}
}
println('\n=== Available Pane Operations ===')
println(' Split panes horizontally (side by side)')
println(' Split panes vertically (top and bottom)')
println(' Send commands to specific panes')
println(' Send raw keys to panes')
println(' Select/activate panes')
println(' Capture pane output')
println(' Get pane process information')
println(' Kill individual panes')
println('\nExample completed! You can attach to the session with:')
println(' tmux attach-session -t panes_demo')

View File

@@ -7,6 +7,24 @@ import os
import time
import freeflowuniverse.herolib.ui.console
// Check if error message indicates tmux server is not running
fn is_tmux_server_not_running_error(error_msg string) bool {
// Common tmux server not running error patterns
tmux_not_running_patterns := [
'no server running',
'error connecting to',
'No such file or directory', // when socket doesn't exist
]
error_lower := error_msg.to_lower()
for pattern in tmux_not_running_patterns {
if error_lower.contains(pattern.to_lower()) {
return true
}
}
return false
}
@[heap]
pub struct Tmux {
pub mut:
@@ -177,7 +195,7 @@ pub fn (mut t Tmux) windows_get() []&Window {
pub fn (mut t Tmux) is_running() !bool {
res := os.execute('tmux info')
if res.exit_code != 0 {
if res.output.contains('no server running') {
if is_tmux_server_not_running_error(res.output) {
// console.print_debug(" TMUX NOT RUNNING")
return false
}

View File

@@ -7,6 +7,83 @@ import time
import os
import freeflowuniverse.herolib.ui.console
// Constants for memory calculations
const kb_to_bytes_factor = 1024
const memory_display_precision = 3
const memory_cache_ttl_seconds = 300 // Cache system memory for 5 minutes
// Global cache for system memory to avoid repeated syscalls
struct MemoryCache {
mut:
total_bytes u64
cached_at time.Time
}
__global (
memory_cache MemoryCache
)
// Platform-specific memory detection
fn get_total_system_memory() !u64 {
$if macos {
result := osal.execute_silent('sysctl -n hw.memsize') or {
return error('Failed to get system memory on macOS: ${err}')
}
return result.trim_space().u64()
} $else $if linux {
// Read from /proc/meminfo
content := os.read_file('/proc/meminfo') or {
return error('Failed to read /proc/meminfo on Linux: ${err}')
}
for line in content.split_into_lines() {
if line.starts_with('MemTotal:') {
parts := line.split_any(' \t').filter(it.len > 0)
if parts.len >= 2 {
kb_value := parts[1].u64()
return kb_value * kb_to_bytes_factor
}
}
}
return error('Could not parse MemTotal from /proc/meminfo')
} $else {
return error('Unsupported platform for memory detection')
}
}
// Get cached or fresh system memory
fn get_system_memory_cached() u64 {
now := time.now()
// Check if cache is valid
if memory_cache.total_bytes > 0
&& now.unix() - memory_cache.cached_at.unix() < memory_cache_ttl_seconds {
return memory_cache.total_bytes
}
// Refresh cache
total_memory := get_total_system_memory() or {
console.print_debug('Failed to get system memory: ${err}')
return 0
}
memory_cache.total_bytes = total_memory
memory_cache.cached_at = now
return total_memory
}
// Calculate accurate memory percentage
fn calculate_memory_percentage(memory_bytes u64, ps_fallback_percent f64) f64 {
total_memory := get_system_memory_cached()
if total_memory > 0 {
return (f64(memory_bytes) / f64(total_memory)) * 100.0
}
// Fallback to ps value if system memory detection fails
return ps_fallback_percent
}
@[heap]
struct Pane {
pub mut:
@@ -22,28 +99,47 @@ pub mut:
pub fn (mut p Pane) stats() !ProcessStats {
if p.pid == 0 {
return ProcessStats{}
return ProcessStats{
cpu_percent: 0.0
memory_percent: 0.0
memory_bytes: 0
}
}
// Use ps command to get CPU and memory stats
cmd := 'ps -p ${p.pid} -o %cpu,%mem,rss --no-headers'
// Use ps command to get CPU and memory stats (cross-platform compatible)
cmd := 'ps -p ${p.pid} -o %cpu,%mem,rss'
result := osal.execute_silent(cmd) or {
return error('Cannot get stats for PID ${p.pid}: ${err}')
}
if result.trim_space() == '' {
lines := result.split_into_lines()
if lines.len < 2 {
return error('Process ${p.pid} not found')
}
parts := result.trim_space().split_any(' \t').filter(it != '')
if parts.len < 3 {
return error('Invalid ps output: ${result}')
// Skip header line, get data line
data_line := lines[1].trim_space()
if data_line == '' {
return error('Process ${p.pid} not found')
}
parts := data_line.split_any(' \t').filter(it != '')
if parts.len < 3 {
return error('Invalid ps output: ${data_line}')
}
// Parse values from ps output
cpu_percent := parts[0].f64()
ps_memory_percent := parts[1].f64()
memory_bytes := parts[2].u64() * kb_to_bytes_factor
// Calculate accurate memory percentage using cached system memory
memory_percent := calculate_memory_percentage(memory_bytes, ps_memory_percent)
return ProcessStats{
cpu_percent: parts[0].f64()
memory_percent: parts[1].f64()
memory_bytes: parts[2].u64() * 1024 // ps returns KB, convert to bytes
cpu_percent: cpu_percent
memory_percent: memory_percent
memory_bytes: memory_bytes
}
}
@@ -147,3 +243,66 @@ pub fn (mut p Pane) processinfo_main() !osal.ProcessInfo {
return osal.processinfo_get(p.pid)!
}
// Send a command to this pane
pub fn (mut p Pane) send_command(command string) ! {
cmd := 'tmux send-keys -t ${p.window.session.name}:@${p.window.id}.%${p.id} "${command}" Enter'
osal.execute_silent(cmd) or { return error('Cannot send command to pane %${p.id}: ${err}') }
}
// Send raw keys to this pane (without Enter)
pub fn (mut p Pane) send_keys(keys string) ! {
cmd := 'tmux send-keys -t ${p.window.session.name}:@${p.window.id}.%${p.id} "${keys}"'
osal.execute_silent(cmd) or { return error('Cannot send keys to pane %${p.id}: ${err}') }
}
// Kill this specific pane
pub fn (mut p Pane) kill() ! {
cmd := 'tmux kill-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id}'
osal.execute_silent(cmd) or { return error('Cannot kill pane %${p.id}: ${err}') }
}
// Select/activate this pane
pub fn (mut p Pane) select() ! {
cmd := 'tmux select-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id}'
osal.execute_silent(cmd) or { return error('Cannot select pane %${p.id}: ${err}') }
p.active = true
}
@[params]
pub struct PaneResizeArgs {
pub mut:
direction string = 'right' // 'up', 'down', 'left', 'right'
cells int = 5 // number of cells to resize by
}
// Resize this pane
pub fn (mut p Pane) resize(args PaneResizeArgs) ! {
direction_flag := match args.direction.to_lower() {
'up', 'u' { '-U' }
'down', 'd' { '-D' }
'left', 'l' { '-L' }
'right', 'r' { '-R' }
else { return error('Invalid resize direction: ${args.direction}. Use up, down, left, or right') }
}
cmd := 'tmux resize-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id} ${direction_flag} ${args.cells}'
osal.execute_silent(cmd) or { return error('Cannot resize pane %${p.id}: ${err}') }
}
// Convenience methods for resizing
pub fn (mut p Pane) resize_up(cells int) ! {
p.resize(direction: 'up', cells: cells)!
}
pub fn (mut p Pane) resize_down(cells int) ! {
p.resize(direction: 'down', cells: cells)!
}
pub fn (mut p Pane) resize_left(cells int) ! {
p.resize(direction: 'left', cells: cells)!
}
pub fn (mut p Pane) resize_right(cells int) ! {
p.resize(direction: 'right', cells: cells)!
}

View File

@@ -5,73 +5,99 @@ import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.ui.console
import time
// Check if error message indicates tmux server is not running
fn is_tmux_server_not_running_error(error_msg string) bool {
// Common tmux server not running error patterns
tmux_not_running_patterns := [
'no server running',
'error connecting to',
'No such file or directory', // when socket doesn't exist
]
error_lower := error_msg.to_lower()
for pattern in tmux_not_running_patterns {
if error_lower.contains(pattern.to_lower()) {
return true
}
}
return false
}
fn (mut t Tmux) scan_add(line string) !&Pane {
// Parse the line to get session, window, and pane info
line_arr := line.split('|')
session_name := line_arr[0]
window_name := line_arr[1]
window_id := line_arr[2]
pane_active := line_arr[3]
pane_id := line_arr[4]
pane_pid := line_arr[5]
pane_start_command := line_arr[6] or { '' }
// Parse the line to get session, window, and pane info
line_arr := line.split('|')
if line_arr.len < 6 {
return error('Invalid tmux pane line format: ${line}')
}
session_name := line_arr[0]
window_name := line_arr[1]
window_id := line_arr[2]
pane_active := line_arr[3]
pane_id := line_arr[4]
pane_pid := line_arr[5]
pane_start_command := line_arr[6] or { '' }
wid := (window_id.replace('@', '')).int()
pid := (pane_id.replace('%', '')).int()
// Skip if window name is empty
if window_name.len == 0 {
return error('Window name is empty in line: ${line}')
}
mut s := t.session_get(session_name)!
wid := (window_id.replace('@', '')).int()
pid := (pane_id.replace('%', '')).int()
// Get or create window
mut w := if s.window_exist(name: window_name, id: wid) {
s.window_get(name: window_name, id: wid)!
} else {
mut new_w := Window{
session: s
name: texttools.name_fix(window_name)
id: wid
panes: []&Pane{}
}
s.windows << &new_w
&new_w
}
mut s := t.session_get(session_name)!
// Create or update pane
mut p := Pane{
window: w
id: pid
pid: pane_pid.int()
active: pane_active == '1'
cmd: pane_start_command
created_at: time.now()
}
// Get or create window
mut w := if s.window_exist(name: window_name, id: wid) {
s.window_get(name: window_name, id: wid)!
} else {
mut new_w := Window{
session: s
name: texttools.name_fix(window_name)
id: wid
panes: []&Pane{}
}
s.windows << &new_w
&new_w
}
// Check if pane already exists
mut found := false
for mut existing_pane in w.panes {
if existing_pane.id == pid {
existing_pane.pid = p.pid
existing_pane.active = p.active
existing_pane.cmd = p.cmd
found = true
break
}
}
// Create or update pane
mut p := Pane{
window: w
id: pid
pid: pane_pid.int()
active: pane_active == '1'
cmd: pane_start_command
created_at: time.now()
}
if !found {
w.panes << &p
}
// Check if pane already exists
mut found := false
for mut existing_pane in w.panes {
if existing_pane.id == pid {
existing_pane.pid = p.pid
existing_pane.active = p.active
existing_pane.cmd = p.cmd
found = true
break
}
}
return &p
if !found {
w.panes << &p
}
return &p
}
// scan the system to detect sessions .
//TODO needs to be done differently, here only find the sessions, then per session call the scan() which will find the windows, call scan() there as well ...
// TODO needs to be done differently, here only find the sessions, then per session call the scan() which will find the windows, call scan() there as well ...
pub fn (mut t Tmux) scan() ! {
// os.log('TMUX - Scanning ....')
cmd_list_session := "tmux list-sessions -F '#{session_name}'"
exec_list := osal.exec(cmd: cmd_list_session, stdout: false, name: 'tmux_list') or {
if err.msg().contains('no server running') {
if is_tmux_server_not_running_error(err.msg()) {
return
}
return error('could not execute list sessions.\n${err}')

View File

@@ -63,8 +63,11 @@ pub fn (mut s Session) scan() ! {
for line in result.split_into_lines() {
if line.contains('|') {
parts := line.split('|')
if parts.len >= 2 {
if parts.len >= 3 && parts[0].len > 0 && parts[1].len > 0 {
window_name := texttools.name_fix(parts[0])
if window_name.len == 0 {
continue
}
window_id := parts[1].replace('@', '').int()
window_active := parts[2] == '1'
@@ -73,7 +76,7 @@ pub fn (mut s Session) scan() ! {
// Update existing window or create new one
mut found := false
for mut w in s.windows {
if w.name == window_name {
if w.name.len > 0 && window_name.len > 0 && w.name == window_name {
w.id = window_id
w.active = window_active
w.scan()! // Scan panes for this window
@@ -99,7 +102,7 @@ pub fn (mut s Session) scan() ! {
}
// Remove windows that no longer exist in tmux
s.windows = s.windows.filter(current_windows[it.name] == true)
s.windows = s.windows.filter(it.name.len > 0 && current_windows[it.name] == true)
}
// window_name is the name of the window in session main (will always be called session main)
@@ -211,9 +214,12 @@ fn (mut s Session) window_exist(args_ WindowGetArgs) bool {
pub fn (mut s Session) window_get(args_ WindowGetArgs) !&Window {
mut args := args_
if args.name.len == 0 {
return error('Window name cannot be empty')
}
args.name = texttools.name_fix(args.name)
for w in s.windows {
if w.name == args.name {
if w.name.len > 0 && w.name == args.name {
if (args.id > 0 && w.id == args.id) || args.id == 0 {
return w
}

View File

@@ -13,7 +13,7 @@ pub mut:
session &Session @[skip]
name string
id int
panes []&Pane // windows contain multiple panes
panes []&Pane // windows contain multiple panes
active bool
env map[string]string
}
@@ -22,105 +22,104 @@ pub mut:
pub struct PaneNewArgs {
pub mut:
name string
reset bool //means we reset the pane if it already exists
reset bool // means we reset the pane if it already exists
cmd string
env map[string]string
env map[string]string
}
pub fn (mut w Window) scan() ! {
// Get current panes for this window
cmd := "tmux list-panes -t ${w.session.name}:@${w.id} -F '#{pane_id}|#{pane_pid}|#{pane_active}|#{pane_start_command}'"
result := osal.execute_silent(cmd) or {
// Window might not exist anymore
return
}
mut current_panes := map[int]bool{}
for line in result.split_into_lines() {
if line.contains('|') {
parts := line.split('|')
if parts.len >= 3 {
pane_id := parts[0].replace('%', '').int()
pane_pid := parts[1].int()
pane_active := parts[2] == '1'
pane_cmd := if parts.len > 3 { parts[3] } else { '' }
current_panes[pane_id] = true
// Update existing pane or create new one
mut found := false
for mut p in w.panes {
if p.id == pane_id {
p.pid = pane_pid
p.active = pane_active
p.cmd = pane_cmd
found = true
break
}
}
if !found {
mut new_pane := Pane{
window: &w
id: pane_id
pid: pane_pid
active: pane_active
cmd: pane_cmd
env: map[string]string{}
created_at: time.now()
last_output_offset: 0
}
w.panes << &new_pane
}
}
}
}
// Remove panes that no longer exist
w.panes = w.panes.filter(current_panes[it.id] == true)
}
// Get current panes for this window
cmd := "tmux list-panes -t ${w.session.name}:@${w.id} -F '#{pane_id}|#{pane_pid}|#{pane_active}|#{pane_start_command}'"
result := osal.execute_silent(cmd) or {
// Window might not exist anymore
return
}
mut current_panes := map[int]bool{}
for line in result.split_into_lines() {
if line.contains('|') {
parts := line.split('|')
if parts.len >= 3 {
pane_id := parts[0].replace('%', '').int()
pane_pid := parts[1].int()
pane_active := parts[2] == '1'
pane_cmd := if parts.len > 3 { parts[3] } else { '' }
current_panes[pane_id] = true
// Update existing pane or create new one
mut found := false
for mut p in w.panes {
if p.id == pane_id {
p.pid = pane_pid
p.active = pane_active
p.cmd = pane_cmd
found = true
break
}
}
if !found {
mut new_pane := Pane{
window: &w
id: pane_id
pid: pane_pid
active: pane_active
cmd: pane_cmd
env: map[string]string{}
created_at: time.now()
last_output_offset: 0
}
w.panes << &new_pane
}
}
}
}
// Remove panes that no longer exist
w.panes = w.panes.filter(current_panes[it.id] == true)
}
pub fn (mut w Window) stop() ! {
w.kill()!
w.kill()!
}
//helper function
//TODO env variables are not inserted in pane
// helper function
// TODO env variables are not inserted in pane
pub fn (mut w Window) create(cmd_ string) ! {
mut final_cmd := cmd_
if cmd_.contains('\n') {
os.mkdir_all('/tmp/tmux/${w.session.name}')!
// Fix: osal.exec_string doesn't exist, use file writing instead
script_path := '/tmp/tmux/${w.session.name}/${w.name}.sh'
script_content := '#!/bin/bash\n' + cmd_
os.write_file(script_path, script_content)!
os.chmod(script_path, 0o755)!
final_cmd = script_path
}
mut final_cmd := cmd_
if cmd_.contains('\n') {
os.mkdir_all('/tmp/tmux/${w.session.name}')!
// Fix: osal.exec_string doesn't exist, use file writing instead
script_path := '/tmp/tmux/${w.session.name}/${w.name}.sh'
script_content := '#!/bin/bash\n' + cmd_
os.write_file(script_path, script_content)!
os.chmod(script_path, 0o755)!
final_cmd = script_path
}
mut newcmd := '/bin/bash -c "${final_cmd}"'
if cmd_ == "" {
newcmd = '/bin/bash'
}
mut newcmd := '/bin/bash -c "${final_cmd}"'
if cmd_ == '' {
newcmd = '/bin/bash'
}
// Build environment arguments
mut env_args := ''
for key, value in w.env {
env_args += ' -e ${key}="${value}"'
}
// Build environment arguments
mut env_args := ''
for key, value in w.env {
env_args += ' -e ${key}="${value}"'
}
res_opt := "-P -F '#{session_name}|#{window_name}|#{window_id}|#{pane_active}|#{pane_id}|#{pane_pid}|#{pane_start_command}'"
cmd := 'tmux new-window ${res_opt}${env_args} -t ${w.session.name} -n ${w.name} \'${newcmd}\''
console.print_debug(cmd)
res := osal.exec(cmd: cmd, stdout: false, name: 'tmux_window_create') or {
return error("Can't create new window ${w.name} \n${cmd}\n${err}")
}
line_arr := res.output.split('|')
wid := line_arr[2] or { return error('cannot split line for window create.\n${line_arr}') }
w.id = wid.replace('@', '').int()
res_opt := "-P -F '#{session_name}|#{window_name}|#{window_id}|#{pane_active}|#{pane_id}|#{pane_pid}|#{pane_start_command}'"
cmd := 'tmux new-window ${res_opt}${env_args} -t ${w.session.name} -n ${w.name} \'${newcmd}\''
console.print_debug(cmd)
res := osal.exec(cmd: cmd, stdout: false, name: 'tmux_window_create') or {
return error("Can't create new window ${w.name} \n${cmd}\n${err}")
}
line_arr := res.output.split('|')
wid := line_arr[2] or { return error('cannot split line for window create.\n${line_arr}') }
w.id = wid.replace('@', '').int()
}
// stop the window
@@ -143,14 +142,14 @@ pub fn (window Window) str() string {
}
pub fn (mut w Window) stats() !ProcessStats {
mut total := ProcessStats{}
for mut pane in w.panes {
stats := pane.stats() or { continue }
total.cpu_percent += stats.cpu_percent
total.memory_bytes += stats.memory_bytes
total.memory_percent += stats.memory_percent
}
return total
mut total := ProcessStats{}
for mut pane in w.panes {
stats := pane.stats() or { continue }
total.cpu_percent += stats.cpu_percent
total.memory_bytes += stats.memory_bytes
total.memory_percent += stats.memory_percent
}
return total
}
// will select the current window so with tmux a we can go there .
@@ -169,10 +168,91 @@ pub fn (mut w Window) pane_list() []&Pane {
// Get active pane in window
pub fn (mut w Window) pane_active() ?&Pane {
for pane in w.panes {
if pane.active {
return pane
}
}
return none
for pane in w.panes {
if pane.active {
return pane
}
}
return none
}
@[params]
pub struct PaneSplitArgs {
pub mut:
cmd string // command to run in new pane
horizontal bool // true for horizontal split, false for vertical
env map[string]string // environment variables
}
// Split the active pane horizontally or vertically
pub fn (mut w Window) pane_split(args PaneSplitArgs) !&Pane {
mut cmd_to_run := args.cmd
if cmd_to_run == '' {
cmd_to_run = '/bin/bash'
}
// Build environment arguments
mut env_args := ''
for key, value in args.env {
env_args += ' -e ${key}="${value}"'
}
// Choose split direction
split_flag := if args.horizontal { '-h' } else { '-v' }
// Execute tmux split-window command
res_opt := "-P -F '#{session_name}|#{window_name}|#{window_id}|#{pane_active}|#{pane_id}|#{pane_pid}|#{pane_start_command}'"
cmd := 'tmux split-window ${split_flag} ${res_opt}${env_args} -t ${w.session.name}:@${w.id} \'${cmd_to_run}\''
console.print_debug('Splitting pane: ${cmd}')
res := osal.exec(cmd: cmd, stdout: false, name: 'tmux_pane_split') or {
return error("Can't split pane in window ${w.name}: ${err}")
}
// Parse the result to get new pane info
line_arr := res.output.split('|')
if line_arr.len < 7 {
return error('Invalid tmux split-window output: ${res.output}')
}
pane_id := line_arr[4].replace('%', '').int()
pane_pid := line_arr[5].int()
pane_active := line_arr[3] == '1'
pane_cmd := line_arr[6] or { '' }
// Create new pane object
mut new_pane := Pane{
window: &w
id: pane_id
pid: pane_pid
active: pane_active
cmd: pane_cmd
env: args.env
created_at: time.now()
last_output_offset: 0
}
// Add to window's panes and rescan to get current state
w.panes << &new_pane
w.scan()!
// Return reference to the new pane
for mut pane in w.panes {
if pane.id == pane_id {
return pane
}
}
return error('Could not find newly created pane with ID ${pane_id}')
}
// Split pane horizontally (side by side)
pub fn (mut w Window) pane_split_horizontal(cmd string) !&Pane {
return w.pane_split(cmd: cmd, horizontal: true)
}
// Split pane vertically (top and bottom)
pub fn (mut w Window) pane_split_vertical(cmd string) !&Pane {
return w.pane_split(cmd: cmd, horizontal: false)
}