feat: add declarative tmux and ttyd management
- Implement `tmux.pane_split` action - Add declarative `tmux.session_ttyd` and `tmux.window_ttyd` - Include `tmux.session_ttyd_stop`, `window_ttyd_stop`, `ttyd_stop_all` - Update tmux documentation and add usage examples - Improve robustness of tmux session and window scanning
This commit is contained in:
44
examples/tmux/tmux_cleanup.heroscript
Normal file
44
examples/tmux/tmux_cleanup.heroscript
Normal file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env hero
|
||||
|
||||
// Tmux Cleanup Script - Tears down all tmux sessions, windows, and panes
|
||||
// Run this after tmux_setup.heroscript to clean up everything
|
||||
|
||||
// Kill specific windows first (optional - sessions will kill all windows anyway)
|
||||
!!tmux.window_delete
|
||||
name:"dev|editor"
|
||||
|
||||
!!tmux.window_delete
|
||||
name:"dev|server"
|
||||
|
||||
!!tmux.window_delete
|
||||
name:"dev|logs"
|
||||
|
||||
!!tmux.window_delete
|
||||
name:"dev|services"
|
||||
|
||||
!!tmux.window_delete
|
||||
name:"monitoring|htop"
|
||||
|
||||
!!tmux.window_delete
|
||||
name:"monitoring|network"
|
||||
|
||||
// Delete all sessions (this will kill all windows and panes within them)
|
||||
!!tmux.session_delete
|
||||
name:'dev'
|
||||
|
||||
!!tmux.session_delete
|
||||
name:'monitoring'
|
||||
|
||||
// Optional: Kill any remaining panes explicitly (usually not needed after session delete)
|
||||
!!tmux.pane_kill
|
||||
name:"dev|editor|main"
|
||||
|
||||
!!tmux.pane_kill
|
||||
name:"dev|server|main"
|
||||
|
||||
!!tmux.pane_kill
|
||||
name:"monitoring|htop|main"
|
||||
|
||||
// Stop any remaining ttyd processes system-wide
|
||||
// This will clean up all ttyd processes regardless of which sessions exist
|
||||
!!tmux.ttyd_stop_all
|
||||
105
examples/tmux/tmux_setup.heroscript
Normal file
105
examples/tmux/tmux_setup.heroscript
Normal file
@@ -0,0 +1,105 @@
|
||||
// Create development session
|
||||
!!tmux.session_create
|
||||
name:'dev'
|
||||
reset:true
|
||||
|
||||
// Create monitoring session
|
||||
!!tmux.session_create
|
||||
name:'monitoring'
|
||||
reset:true
|
||||
|
||||
// Create development windows (use reset to ensure clean state)
|
||||
!!tmux.window_create
|
||||
name:"dev|editor"
|
||||
cmd:'vim'
|
||||
reset:true
|
||||
|
||||
!!tmux.window_create
|
||||
name:"dev|server"
|
||||
cmd:'python3 -m http.server 8000'
|
||||
env:'PORT=8000,DEBUG=true'
|
||||
reset:true
|
||||
|
||||
!!tmux.window_create
|
||||
name:"dev|logs"
|
||||
cmd:'tail -f /var/log/system.log'
|
||||
reset:true
|
||||
|
||||
// Create monitoring windows
|
||||
!!tmux.window_create
|
||||
name:"monitoring|htop"
|
||||
cmd:'htop'
|
||||
reset:true
|
||||
|
||||
!!tmux.window_create
|
||||
name:"monitoring|network"
|
||||
cmd:'netstat -tuln'
|
||||
reset:true
|
||||
|
||||
// Create a multi-service window with 4 panes
|
||||
!!tmux.window_create
|
||||
name:"dev|services"
|
||||
cmd:'htop'
|
||||
reset:true
|
||||
|
||||
// Split the services window into 4 panes (1 initial + 3 splits = 4 total)
|
||||
!!tmux.pane_split
|
||||
name:"dev|services"
|
||||
cmd:'python3 -m http.server 3000'
|
||||
horizontal:true
|
||||
|
||||
!!tmux.pane_split
|
||||
name:"dev|services"
|
||||
cmd:'watch -n 1 ps aux'
|
||||
horizontal:false
|
||||
|
||||
!!tmux.pane_split
|
||||
name:"dev|services"
|
||||
cmd:'tail -f /var/log/system.log'
|
||||
horizontal:true
|
||||
|
||||
!!tmux.pane_split
|
||||
name:"dev|services"
|
||||
cmd:'echo Fourth pane ready for commands'
|
||||
horizontal:false
|
||||
|
||||
// Execute welcome commands in panes
|
||||
!!tmux.pane_execute
|
||||
name:"dev|editor|main"
|
||||
cmd:'echo Welcome to the editor pane'
|
||||
|
||||
!!tmux.pane_execute
|
||||
name:"dev|server|main"
|
||||
cmd:'echo Starting development server'
|
||||
|
||||
!!tmux.pane_execute
|
||||
name:"monitoring|htop|main"
|
||||
cmd:'echo System monitoring active'
|
||||
|
||||
// Split panes for better workflow
|
||||
!!tmux.pane_split
|
||||
name:"dev|editor"
|
||||
cmd:'echo Split pane for terminal'
|
||||
horizontal:false
|
||||
|
||||
!!tmux.pane_split
|
||||
name:"monitoring|htop"
|
||||
cmd:'watch -n 1 df -h'
|
||||
horizontal:true
|
||||
|
||||
// Expose sessions and windows via web browser using ttyd
|
||||
!!tmux.session_ttyd
|
||||
name:'dev'
|
||||
port:8080
|
||||
editable:true
|
||||
|
||||
!!tmux.window_ttyd
|
||||
name:"monitoring|htop"
|
||||
port:8081
|
||||
editable:false
|
||||
|
||||
// Expose the 4-pane services window via web
|
||||
!!tmux.window_ttyd
|
||||
name:"dev|services"
|
||||
port:7681
|
||||
editable:false
|
||||
@@ -7,6 +7,7 @@ import freeflowuniverse.herolib.web.site
|
||||
import freeflowuniverse.herolib.web.docusaurus
|
||||
import freeflowuniverse.herolib.clients.openai
|
||||
import freeflowuniverse.herolib.clients.giteaclient
|
||||
import freeflowuniverse.herolib.osal.tmux
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// run – entry point for all HeroScript play‑commands
|
||||
@@ -39,6 +40,9 @@ pub fn run(args_ PlayArgs) ! {
|
||||
// Git actions
|
||||
play_git(mut plbook)!
|
||||
|
||||
// Tmux actions
|
||||
tmux.play(mut plbook)!
|
||||
|
||||
// Business model (e.g. currency, bizmodel)
|
||||
bizmodel.play(mut plbook)!
|
||||
|
||||
|
||||
@@ -23,7 +23,12 @@ pub fn play(mut plbook PlayBook) ! {
|
||||
play_window_delete(mut plbook, mut tmux_instance)!
|
||||
play_pane_execute(mut plbook, mut tmux_instance)!
|
||||
play_pane_kill(mut plbook, mut tmux_instance)!
|
||||
// TODO: Implement pane_create, pane_delete, pane_split when pane API is extended
|
||||
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)!
|
||||
}
|
||||
|
||||
struct ParsedWindowName {
|
||||
@@ -43,8 +48,8 @@ fn parse_window_name(name string) !ParsedWindowName {
|
||||
return error('Window name must be in format "session|window", got: ${name}')
|
||||
}
|
||||
return ParsedWindowName{
|
||||
session: texttools.name_fix(parts[0])
|
||||
window: texttools.name_fix(parts[1])
|
||||
session: texttools.name_fix_token(parts[0])
|
||||
window: texttools.name_fix_token(parts[1])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,9 +59,9 @@ fn parse_pane_name(name string) !ParsedPaneName {
|
||||
return error('Pane name must be in format "session|window|pane", got: ${name}')
|
||||
}
|
||||
return ParsedPaneName{
|
||||
session: texttools.name_fix(parts[0])
|
||||
window: texttools.name_fix(parts[1])
|
||||
pane: texttools.name_fix(parts[2])
|
||||
session: texttools.name_fix_token(parts[0])
|
||||
window: texttools.name_fix_token(parts[1])
|
||||
pane: texttools.name_fix_token(parts[2])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,3 +202,138 @@ fn play_pane_kill(mut plbook PlayBook, mut tmux_instance Tmux) ! {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# TMUX
|
||||
|
||||
|
||||
TMUX is a very capable process manager.
|
||||
|
||||
> TODO: TTYD, need to integrate with TMUX for exposing TMUX over http
|
||||
@@ -11,7 +10,6 @@ TMUX is a very capable process manager.
|
||||
- session = is a set of windows, it has a name and groups windows
|
||||
- window = is typically one process running (you can have panes but in our implementation we skip this)
|
||||
|
||||
|
||||
## structure
|
||||
|
||||
tmux library provides functions for managing tmux sessions
|
||||
@@ -20,33 +18,156 @@ tmux library provides functions for managing tmux sessions
|
||||
- then windows (is where you see the app running)
|
||||
- then panes in windows (we don't support yet)
|
||||
|
||||
|
||||
## to attach to a tmux session
|
||||
|
||||
> TODO:
|
||||
## HeroScript Usage Examples
|
||||
>
|
||||
## HeroScript Declarative Support
|
||||
|
||||
The tmux module supports declarative configuration through heroscript, allowing you to define tmux sessions, windows, and panes in a structured way.
|
||||
|
||||
### Running HeroScript
|
||||
|
||||
```bash
|
||||
hero run -p <heroscript_file>
|
||||
```
|
||||
|
||||
### Supported Actions
|
||||
|
||||
#### Session Management
|
||||
|
||||
```heroscript
|
||||
!!tmux.session_create
|
||||
// Create a new session
|
||||
!!tmux.session_create
|
||||
name:'mysession'
|
||||
reset:true
|
||||
reset:true // Optional: delete existing session first
|
||||
|
||||
!!tmux.session_delete
|
||||
// Delete a session
|
||||
!!tmux.session_delete
|
||||
name:'mysession'
|
||||
```
|
||||
|
||||
!!tmux.window_create
|
||||
#### Window Management
|
||||
|
||||
```heroscript
|
||||
// Create a new window
|
||||
!!tmux.window_create
|
||||
name:"mysession|mywindow" // Format: session|window
|
||||
cmd:'htop' // Optional: command to run
|
||||
env:'VAR1=value1,VAR2=value2' // Optional: environment variables
|
||||
reset:true // Optional: recreate if exists
|
||||
|
||||
// Delete a window
|
||||
!!tmux.window_delete
|
||||
name:"mysession|mywindow"
|
||||
cmd:'htop'
|
||||
env:'VAR1=value1,VAR2=value2'
|
||||
reset:true
|
||||
```
|
||||
|
||||
!!tmux.window_delete
|
||||
name:"mysession|mywindow"
|
||||
#### Pane Management
|
||||
|
||||
!!tmux.pane_execute
|
||||
name:"mysession|mywindow|mypane"
|
||||
```heroscript
|
||||
// Execute command in a pane
|
||||
!!tmux.pane_execute
|
||||
name:"mysession|mywindow|mypane" // Format: session|window|pane
|
||||
cmd:'ls -la'
|
||||
|
||||
!!tmux.pane_kill
|
||||
// Kill a pane
|
||||
!!tmux.pane_kill
|
||||
name:"mysession|mywindow|mypane"
|
||||
```
|
||||
```
|
||||
|
||||
#### Ttyd Management
|
||||
|
||||
```heroscript
|
||||
// Start ttyd for session access
|
||||
!!tmux.session_ttyd
|
||||
name:'mysession'
|
||||
port:8080
|
||||
editable:true // Optional: allows write access
|
||||
|
||||
// Start ttyd for window access
|
||||
!!tmux.window_ttyd
|
||||
name:"mysession|mywindow"
|
||||
port:8081
|
||||
editable:false // Optional: read-only access
|
||||
|
||||
// Stop ttyd for session
|
||||
!!tmux.session_ttyd_stop
|
||||
name:'mysession'
|
||||
port:8080
|
||||
|
||||
// Stop ttyd for window
|
||||
!!tmux.window_ttyd_stop
|
||||
name:"mysession|mywindow"
|
||||
port:8081
|
||||
|
||||
// Stop all ttyd processes
|
||||
!!tmux.ttyd_stop_all
|
||||
```
|
||||
|
||||
### Complete Example
|
||||
|
||||
```heroscript
|
||||
#!/usr/bin/env hero
|
||||
|
||||
// Create development environment
|
||||
!!tmux.session_create
|
||||
name:'dev'
|
||||
reset:true
|
||||
|
||||
!!tmux.window_create
|
||||
name:"dev|editor"
|
||||
cmd:'vim'
|
||||
reset:true
|
||||
|
||||
!!tmux.window_create
|
||||
name:"dev|server"
|
||||
cmd:'python3 -m http.server 8000'
|
||||
env:'PORT=8000,DEBUG=true'
|
||||
reset:true
|
||||
|
||||
!!tmux.pane_execute
|
||||
name:"dev|editor|main"
|
||||
cmd:'echo "Welcome to development!"'
|
||||
```
|
||||
|
||||
### Naming Convention
|
||||
|
||||
- **Sessions**: Simple names like `dev`, `monitoring`, `main`
|
||||
- **Windows**: Use pipe separator: `session|window` (e.g., `dev|editor`)
|
||||
- **Panes**: Use pipe separator: `session|window|pane` (e.g., `dev|editor|main`)
|
||||
|
||||
Names are automatically normalized using `texttools.name_fix()` for consistency.
|
||||
|
||||
## Example Usage
|
||||
|
||||
### Setup and Cleanup Scripts
|
||||
|
||||
Two example heroscripts are provided to demonstrate complete tmux environment management:
|
||||
|
||||
#### 1. Setup Script (`tmux_setup.heroscript`)
|
||||
|
||||
Creates a complete development environment with multiple sessions and windows:
|
||||
|
||||
```bash
|
||||
hero run examples/tmux/tmux_setup.heroscript
|
||||
```
|
||||
|
||||
This creates:
|
||||
|
||||
- **dev session** with editor, server, logs, and a 4-pane services window
|
||||
- **monitoring session** with htop and network monitoring windows
|
||||
- **Web access** via ttyd on ports 8080, 8081, and 7681
|
||||
|
||||
#### 2. Cleanup Script (`tmux_cleanup.heroscript`)
|
||||
|
||||
Tears down all created tmux resources:
|
||||
|
||||
```bash
|
||||
hero run examples/tmux/tmux_cleanup.heroscript
|
||||
```
|
||||
|
||||
This removes:
|
||||
|
||||
- All windows from both sessions
|
||||
- Both dev and monitoring sessions
|
||||
- All associated panes
|
||||
- All ttyd web processes (ports 8080, 8081, 7681)
|
||||
|
||||
@@ -61,16 +61,42 @@ pub fn (mut s Session) scan() ! {
|
||||
|
||||
mut current_windows := map[string]bool{}
|
||||
for line in result.split_into_lines() {
|
||||
if line.contains('|') {
|
||||
parts := line.split('|')
|
||||
line_trimmed := line.trim_space()
|
||||
if line_trimmed.len == 0 {
|
||||
continue
|
||||
}
|
||||
if line_trimmed.contains('|') {
|
||||
parts := line_trimmed.split('|')
|
||||
if parts.len >= 3 && parts[0].len > 0 && parts[1].len > 0 {
|
||||
window_name := texttools.name_fix(parts[0])
|
||||
// Safely extract window name with additional validation
|
||||
raw_window_name := parts[0].trim_space()
|
||||
if raw_window_name.len == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Use safer name processing instead of texttools.name_fix
|
||||
mut window_name := raw_window_name.to_lower().trim_space()
|
||||
// Replace problematic characters with underscores
|
||||
window_name = window_name.replace(' ', '_').replace('-', '_').replace('.',
|
||||
'_')
|
||||
|
||||
// Remove any non-ASCII characters safely
|
||||
mut safe_name := ''
|
||||
for c in window_name {
|
||||
if c.is_letter() || c.is_digit() || c == `_` {
|
||||
safe_name += c.ascii_str()
|
||||
}
|
||||
}
|
||||
window_name = safe_name
|
||||
|
||||
if window_name.len == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
window_id := parts[1].replace('@', '').int()
|
||||
window_active := parts[2] == '1'
|
||||
|
||||
// Safe map assignment
|
||||
current_windows[window_name] = true
|
||||
|
||||
// Update existing window or create new one
|
||||
@@ -102,7 +128,24 @@ pub fn (mut s Session) scan() ! {
|
||||
}
|
||||
|
||||
// Remove windows that no longer exist in tmux
|
||||
s.windows = s.windows.filter(it.name.len > 0 && current_windows[it.name] == true)
|
||||
mut valid_windows := []&Window{}
|
||||
for window in s.windows {
|
||||
// Safety check: ensure window.name is valid
|
||||
if window.name.len > 0 {
|
||||
// Avoid map access entirely - check if window still exists by comparing with current windows
|
||||
mut window_exists := false
|
||||
for current_name, _ in current_windows {
|
||||
if window.name == current_name {
|
||||
window_exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if window_exists {
|
||||
valid_windows << window
|
||||
}
|
||||
}
|
||||
}
|
||||
s.windows = valid_windows
|
||||
}
|
||||
|
||||
// window_name is the name of the window in session main (will always be called session main)
|
||||
@@ -217,7 +260,7 @@ pub fn (mut s Session) window_get(args_ WindowGetArgs) !&Window {
|
||||
if args.name.len == 0 {
|
||||
return error('Window name cannot be empty')
|
||||
}
|
||||
args.name = texttools.name_fix(args.name)
|
||||
args.name = texttools.name_fix_token(args.name)
|
||||
for w in s.windows {
|
||||
if w.name.len > 0 && w.name == args.name {
|
||||
if (args.id > 0 && w.id == args.id) || args.id == 0 {
|
||||
@@ -231,7 +274,7 @@ pub fn (mut s Session) window_get(args_ WindowGetArgs) !&Window {
|
||||
pub fn (mut s Session) window_delete(args_ WindowGetArgs) ! {
|
||||
// $if debug { console.print_debug(" - window delete: $args_")}
|
||||
mut args := args_
|
||||
args.name = texttools.name_fix(args.name)
|
||||
args.name = texttools.name_fix_token(args.name)
|
||||
if !(s.window_exist(args)) {
|
||||
return
|
||||
}
|
||||
@@ -284,3 +327,16 @@ pub fn (mut s Session) run_ttyd(args TtydArgs) ! {
|
||||
pub fn (mut s Session) run_ttyd_readonly(port int) ! {
|
||||
s.run_ttyd(port: port, editable: false)!
|
||||
}
|
||||
|
||||
// Stop ttyd for this session by killing the process on the specified port
|
||||
pub fn (mut s Session) stop_ttyd(port int) ! {
|
||||
// Kill any process running on the specified port
|
||||
cmd := 'lsof -ti:${port} | xargs kill -9'
|
||||
osal.execute_silent(cmd) or { return error("Can't stop ttyd on port ${port}: ${err}") }
|
||||
}
|
||||
|
||||
// Stop all ttyd processes (kills all ttyd processes system-wide)
|
||||
pub fn stop_all_ttyd() ! {
|
||||
cmd := 'pkill ttyd'
|
||||
osal.execute_silent(cmd) or { return error("Can't stop all ttyd processes: ${err}") }
|
||||
}
|
||||
|
||||
@@ -37,11 +37,27 @@ pub fn (mut w Window) scan() ! {
|
||||
|
||||
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()
|
||||
line_trimmed := line.trim_space()
|
||||
if line_trimmed.len == 0 {
|
||||
continue
|
||||
}
|
||||
if line_trimmed.contains('|') {
|
||||
parts := line_trimmed.split('|')
|
||||
if parts.len >= 3 && parts[0].len > 0 && parts[1].len > 0 {
|
||||
// Safely parse pane ID
|
||||
pane_id_str := parts[0].replace('%', '').trim_space()
|
||||
if pane_id_str.len == 0 {
|
||||
continue
|
||||
}
|
||||
pane_id := pane_id_str.int()
|
||||
|
||||
// Safely parse PID
|
||||
pane_pid_str := parts[1].trim_space()
|
||||
if pane_pid_str.len == 0 {
|
||||
continue
|
||||
}
|
||||
pane_pid := pane_pid_str.int()
|
||||
|
||||
pane_active := parts[2] == '1'
|
||||
pane_cmd := if parts.len > 3 { parts[3] } else { '' }
|
||||
|
||||
@@ -77,7 +93,14 @@ pub fn (mut w Window) scan() ! {
|
||||
}
|
||||
|
||||
// Remove panes that no longer exist
|
||||
w.panes = w.panes.filter(current_panes[it.id] == true)
|
||||
mut valid_panes := []&Pane{}
|
||||
for pane in w.panes {
|
||||
// Use safe map access with 'in' operator first
|
||||
if pane.id in current_panes && current_panes[pane.id] == true {
|
||||
valid_panes << pane
|
||||
}
|
||||
}
|
||||
w.panes = valid_panes
|
||||
}
|
||||
|
||||
pub fn (mut w Window) stop() ! {
|
||||
@@ -237,14 +260,8 @@ pub fn (mut w Window) pane_split(args PaneSplitArgs) !&Pane {
|
||||
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}')
|
||||
// Return the new pane reference
|
||||
return &new_pane
|
||||
}
|
||||
|
||||
// Split pane horizontally (side by side)
|
||||
@@ -289,3 +306,10 @@ pub fn (mut w Window) run_ttyd(args TtydArgs) ! {
|
||||
pub fn (mut w Window) run_ttyd_readonly(port int) ! {
|
||||
w.run_ttyd(port: port, editable: false)!
|
||||
}
|
||||
|
||||
// Stop ttyd for this window by killing the process on the specified port
|
||||
pub fn (mut w Window) stop_ttyd(port int) ! {
|
||||
// Kill any process running on the specified port
|
||||
cmd := 'lsof -ti:${port} | xargs kill -9'
|
||||
osal.execute_silent(cmd) or { return error("Can't stop ttyd on port ${port}: ${err}") }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user