refactor: Improve tmux API consistency and formatting

- Refactor `logs_get_new` to use `LogsGetArgs` struct
- Return window as reference from `window_new`
- Standardize indentation and spacing
- Remove excessive blank lines
- Comment out initial example usage
This commit is contained in:
Mahmoud-Emad
2025-08-24 16:31:04 +03:00
parent 810cbda176
commit 117c9ac67c
4 changed files with 199 additions and 214 deletions

View File

@@ -1,20 +1,20 @@
#!/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)!
// 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)

View File

@@ -14,7 +14,6 @@ pub mut:
sessionid string // unique link to job
}
// get session (session has windows) .
// returns none if not found
pub fn (mut t Tmux) session_get(name_ string) !&Session {
@@ -56,8 +55,6 @@ pub mut:
reset bool
}
// create session, if reset will re-create
pub fn (mut t Tmux) session_create(args SessionCreateArgs) !&Session {
name := texttools.name_fix(args.name)
@@ -83,7 +80,6 @@ pub fn (mut t Tmux) session_create(args SessionCreateArgs) !&Session {
return s
}
@[params]
pub struct TmuxNewArgs {
sessionid string
@@ -116,17 +112,16 @@ pub fn (mut t Tmux) window_new(args WindowNewArgs) !&Window {
} else {
t.session_create(name: args.session_name)!
}
// Create window in session
return session.window_new(
name: args.name
cmd: args.cmd
env: args.env
name: args.name
cmd: args.cmd
env: args.env
reset: args.reset
)!
}
pub fn (mut t Tmux) stop() ! {
$if debug {
console.print_debug('Stopping tmux...')
@@ -156,7 +151,6 @@ pub fn (mut t Tmux) start() ! {
t.scan()!
}
// print list of tmux sessions
pub fn (mut t Tmux) list_print() {
// os.log('TMUX - Start listing ....')

View File

@@ -10,141 +10,140 @@ import freeflowuniverse.herolib.ui.console
@[heap]
struct Pane {
pub mut:
window &Window @[str: skip]
id int // pane id (e.g., %1, %2)
pid int // process id
active bool // is this the active pane
cmd string // command running in pane
env map[string]string
created_at time.Time
last_output_offset int // for tracking new logs
window &Window @[str: skip]
id int // pane id (e.g., %1, %2)
pid int // process id
active bool // is this the active pane
cmd string // command running in pane
env map[string]string
created_at time.Time
last_output_offset int // for tracking new logs
}
pub fn (mut p Pane) stats() !ProcessStats {
if p.pid == 0 {
return ProcessStats{}
}
if p.pid == 0 {
return ProcessStats{}
}
// Use ps command to get CPU and memory stats
cmd := 'ps -p ${p.pid} -o %cpu,%mem,rss --no-headers'
result := osal.execute_silent(cmd) or {
return error('Cannot get stats for PID ${p.pid}: ${err}')
}
// Use ps command to get CPU and memory stats
cmd := 'ps -p ${p.pid} -o %cpu,%mem,rss --no-headers'
result := osal.execute_silent(cmd) or {
return error('Cannot get stats for PID ${p.pid}: ${err}')
}
if result.trim_space() == '' {
return error('Process ${p.pid} not found')
}
if result.trim_space() == '' {
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}')
}
parts := result.trim_space().split_any(' \t').filter(it != '')
if parts.len < 3 {
return error('Invalid ps output: ${result}')
}
return ProcessStats{
cpu_percent: parts[0].f64()
memory_percent: parts[1].f64()
memory_bytes: parts[2].u64() * 1024 // ps returns KB, convert to bytes
}
return ProcessStats{
cpu_percent: parts[0].f64()
memory_percent: parts[1].f64()
memory_bytes: parts[2].u64() * 1024 // ps returns KB, convert to bytes
}
}
pub struct TMuxLogEntry {
pub mut:
content string
timestamp time.Time
offset int
content string
timestamp time.Time
offset int
}
pub fn (mut p Pane) logs_get_new(reset bool) ![]TMuxLogEntry {
pub struct LogsGetArgs {
pub mut:
reset bool
}
if reset{
p.last_output_offset = 0
}
// Capture pane content with line numbers
cmd := 'tmux capture-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id} -S ${p.last_output_offset} -p'
result := osal.execute_silent(cmd) or {
return error('Cannot capture pane output: ${err}')
}
// get new logs since last call
pub fn (mut p Pane) logs_get_new(args LogsGetArgs) ![]TMuxLogEntry {
if args.reset {
p.last_output_offset = 0
}
// Capture pane content with line numbers
cmd := 'tmux capture-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id} -S ${p.last_output_offset} -p'
result := osal.execute_silent(cmd) or { return error('Cannot capture pane output: ${err}') }
lines := result.split_into_lines()
mut entries := []TMuxLogEntry{}
lines := result.split_into_lines()
mut entries := []TMuxLogEntry{}
mut i:= 0
for line in lines {
if line.trim_space() != '' {
entries << TMuxLogEntry{
content: line
timestamp: time.now()
offset: p.last_output_offset + i + 1
}
}
}
// Update offset to avoid duplicates next time
if entries.len > 0 {
p.last_output_offset = entries.last().offset
}
return entries
mut i := 0
for line in lines {
if line.trim_space() != '' {
entries << TMuxLogEntry{
content: line
timestamp: time.now()
offset: p.last_output_offset + i + 1
}
}
}
// Update offset to avoid duplicates next time
if entries.len > 0 {
p.last_output_offset = entries.last().offset
}
return entries
}
pub fn (mut p Pane) exit_status() !ProcessStatus {
// Get the last few lines to see if there's an exit status
logs := p.logs_all()!
lines := logs.split_into_lines()
// Get the last few lines to see if there's an exit status
logs := p.logs_all()!
lines := logs.split_into_lines()
// Look for shell prompt indicating command finished
for line in lines.reverse() {
line_clean := line.trim_space()
if line_clean.contains('$') || line_clean.contains('#') || line_clean.contains('>') {
// Found shell prompt, command likely finished
// Could also check for specific exit codes in history
return .finished_ok
}
}
return .finished_error
// Look for shell prompt indicating command finished
for line in lines.reverse() {
line_clean := line.trim_space()
if line_clean.contains('$') || line_clean.contains('#') || line_clean.contains('>') {
// Found shell prompt, command likely finished
// Could also check for specific exit codes in history
return .finished_ok
}
}
return .finished_error
}
pub fn (mut p Pane) logs_all() !string {
cmd := 'tmux capture-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id} -S -2000 -p'
return osal.execute_silent(cmd) or {
error('Cannot capture pane output: ${err}')
}
cmd := 'tmux capture-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id} -S -2000 -p'
return osal.execute_silent(cmd) or { error('Cannot capture pane output: ${err}') }
}
// Fix the output_wait method to use correct method name
pub fn (mut p Pane) output_wait(c_ string, timeoutsec int) ! {
mut t := ourtime.now()
start := t.unix()
c := c_.replace('\n', '')
for i in 0 .. 2000 {
entries := p.logs_get_new(reset: false)!
for entry in entries {
if entry.content.replace('\n', '').contains(c) {
return
}
}
mut t2 := ourtime.now()
if t2.unix() > start + timeoutsec {
return error('timeout on output wait for tmux.\n${p} .\nwaiting for:\n${c}')
}
time.sleep(100 * time.millisecond)
}
mut t := ourtime.now()
start := t.unix()
c := c_.replace('\n', '')
for i in 0 .. 2000 {
entries := p.logs_get_new(reset: false)!
for entry in entries {
if entry.content.replace('\n', '').contains(c) {
return
}
}
mut t2 := ourtime.now()
if t2.unix() > start + timeoutsec {
return error('timeout on output wait for tmux.\n${p} .\nwaiting for:\n${c}')
}
time.sleep(100 * time.millisecond)
}
}
// Get process information for this pane and all its children
pub fn (mut p Pane) processinfo() !osal.ProcessMap {
if p.pid == 0 {
return error('Pane has no associated process (pid is 0)')
}
return osal.processinfo_with_children(p.pid)!
if p.pid == 0 {
return error('Pane has no associated process (pid is 0)')
}
return osal.processinfo_with_children(p.pid)!
}
// Get process information for just this pane's main process
pub fn (mut p Pane) processinfo_main() !osal.ProcessInfo {
if p.pid == 0 {
return error('Pane has no associated process (pid is 0)')
}
return osal.processinfo_get(p.pid)!
if p.pid == 0 {
return error('Pane has no associated process (pid is 0)')
}
return osal.processinfo_get(p.pid)!
}

View File

@@ -21,87 +21,86 @@ pub mut:
env map[string]string
reset bool
}
@[params]
pub struct WindowGetArgs {
pub mut:
name string
id int
name string
id int
}
pub fn (mut s Session) create() ! {
// Check if session already exists
cmd_check := "tmux has-session -t ${s.name}"
check_result := osal.exec(cmd: cmd_check, stdout: false, ignore_error: true) or {
// Session doesn't exist, this is expected
osal.Job{}
}
if check_result.exit_code == 0 {
return error('duplicate session: ${s.name}')
}
// Create new session
cmd := "tmux new-session -d -s ${s.name}"
osal.exec(cmd: cmd, stdout: false, name: 'tmux_session_create') or {
return error("Can't create session ${s.name}: ${err}")
}
// Check if session already exists
cmd_check := 'tmux has-session -t ${s.name}'
check_result := osal.exec(cmd: cmd_check, stdout: false, ignore_error: true) or {
// Session doesn't exist, this is expected
osal.Job{}
}
if check_result.exit_code == 0 {
return error('duplicate session: ${s.name}')
}
// Create new session
cmd := 'tmux new-session -d -s ${s.name}'
osal.exec(cmd: cmd, stdout: false, name: 'tmux_session_create') or {
return error("Can't create session ${s.name}: ${err}")
}
}
//load info from reality
// load info from reality
pub fn (mut s Session) scan() ! {
// Get current windows from tmux for this session
cmd := "tmux list-windows -t ${s.name} -F '#{window_name}|#{window_id}|#{window_active}'"
result := osal.execute_silent(cmd) or {
if err.msg().contains('session not found') {
return // Session doesn't exist anymore
}
return error('Cannot list windows for session ${s.name}: ${err}')
}
mut current_windows := map[string]bool{}
for line in result.split_into_lines() {
if line.contains('|') {
parts := line.split('|')
if parts.len >= 2 {
window_name := texttools.name_fix(parts[0])
window_id := parts[1].replace('@', '').int()
window_active := parts[2] == '1'
current_windows[window_name] = true
// Update existing window or create new one
mut found := false
for mut w in s.windows {
if w.name == window_name {
w.id = window_id
w.active = window_active
w.scan()! // Scan panes for this window
found = true
break
}
}
if !found {
mut new_window := Window{
session: &s
name: window_name
id: window_id
active: window_active
panes: []&Pane{}
env: map[string]string{}
}
new_window.scan()! // Scan panes for new window
s.windows << &new_window
}
}
}
}
// Remove windows that no longer exist in tmux
s.windows = s.windows.filter(current_windows[it.name] == true)
}
// Get current windows from tmux for this session
cmd := "tmux list-windows -t ${s.name} -F '#{window_name}|#{window_id}|#{window_active}'"
result := osal.execute_silent(cmd) or {
if err.msg().contains('session not found') {
return
}
return error('Cannot list windows for session ${s.name}: ${err}')
}
mut current_windows := map[string]bool{}
for line in result.split_into_lines() {
if line.contains('|') {
parts := line.split('|')
if parts.len >= 2 {
window_name := texttools.name_fix(parts[0])
window_id := parts[1].replace('@', '').int()
window_active := parts[2] == '1'
current_windows[window_name] = true
// Update existing window or create new one
mut found := false
for mut w in s.windows {
if w.name == window_name {
w.id = window_id
w.active = window_active
w.scan()! // Scan panes for this window
found = true
break
}
}
if !found {
mut new_window := Window{
session: &s
name: window_name
id: window_id
active: window_active
panes: []&Pane{}
env: map[string]string{}
}
new_window.scan()! // Scan panes for new window
s.windows << &new_window
}
}
}
}
// Remove windows that no longer exist in tmux
s.windows = s.windows.filter(current_windows[it.name] == true)
}
// window_name is the name of the window in session main (will always be called session main)
// cmd to execute e.g. bash file
@@ -116,7 +115,7 @@ pub fn (mut s Session) scan() ! {
// reset bool
// }
// ```
pub fn (mut s Session) window_new(args WindowArgs) !Window {
pub fn (mut s Session) window_new(args WindowArgs) !&Window {
$if debug {
console.print_header(' start window: \n${args}')
}
@@ -128,7 +127,7 @@ pub fn (mut s Session) window_new(args WindowArgs) !Window {
return error('cannot create new window it already exists, window ${namel} in session:${s.name}')
}
}
mut w := Window{
mut w := &Window{
session: &s
name: namel
panes: []&Pane{}
@@ -139,14 +138,10 @@ pub fn (mut s Session) window_new(args WindowArgs) !Window {
// Create the window with the specified command
w.create(args.cmd)!
s.scan()!
return w
}
// get all windows as found in a session
pub fn (mut s Session) windows_get() []&Window {
mut res := []&Window{}
@@ -179,14 +174,14 @@ pub fn (mut s Session) str() string {
}
pub fn (mut s Session) stats() !ProcessStats {
mut total := ProcessStats{}
for mut window in s.windows {
stats := window.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 window in s.windows {
stats := window.stats() or { continue }
total.cpu_percent += stats.cpu_percent
total.memory_bytes += stats.memory_bytes
total.memory_percent += stats.memory_percent
}
return total
}
// pub fn (mut s Session) activate()! {
@@ -208,8 +203,6 @@ pub fn (mut s Session) stats() !ProcessStats {
// }
// }
fn (mut s Session) window_exist(args_ WindowGetArgs) bool {
mut args := args_
s.window_get(args) or { return false }
@@ -249,7 +242,6 @@ pub fn (mut s Session) window_delete(args_ WindowGetArgs) ! {
s.windows.delete(i) // i is now the one in the list which needs to be removed
}
pub fn (mut s Session) restart() ! {
s.stop()!
s.create()!
@@ -259,4 +251,4 @@ pub fn (mut s Session) stop() ! {
osal.execute_silent('tmux kill-session -t ${s.name}') or {
return error("Can't delete session ${s.name} - This may happen when session is not found: ${err}")
}
}
}