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:
@@ -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)
|
||||
|
||||
@@ -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 ....')
|
||||
|
||||
@@ -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)!
|
||||
}
|
||||
|
||||
@@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user