Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions internal/tui/model_core.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ type Model struct {
filterText string
sortMode sortMode
sortAsc bool
attachTarget string // non-empty → exec zmx attach after quit
attachTarget string // non-empty → attach to this session
attachReplace bool // true → syscall.Exec (replace), false → exec.Command (loop)

preview string
previewScrollX int
Expand Down Expand Up @@ -200,8 +201,8 @@ func NewModel() Model {
return initialModel()
}

func (m Model) AttachTarget() string {
return m.attachTarget
func (m Model) AttachTarget() (string, bool) {
return m.attachTarget, m.attachReplace
}

// visibleSessions returns sessions matching the current filter, sorted by sortMode.
Expand Down
7 changes: 7 additions & 0 deletions internal/tui/model_input.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,19 @@ func (m Model) handleKey(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
case tea.KeyEnter:
if m.cursor < len(visible) {
m.attachTarget = visible[m.cursor].Name
m.attachReplace = false // Loop back after detach
return m, tea.Quit
}

default:
if msg.Text != "" {
switch msg.Text {
case "e":
if m.cursor < len(visible) {
m.attachTarget = visible[m.cursor].Name
m.attachReplace = true // Replace process
return m, tea.Quit
}
case "k":
targets := m.killTargets()
if len(targets) > 0 {
Expand Down
1 change: 1 addition & 0 deletions internal/tui/model_view.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ func (m Model) renderHelp() string {
helpKeyStyle.Render("space") + helpStyle.Render(" sel"),
helpKeyStyle.Render("^a") + helpStyle.Render(" all"),
helpKeyStyle.Render("enter") + helpStyle.Render(" attach"),
helpKeyStyle.Render("e") + helpStyle.Render(" exec"),
helpKeyStyle.Render("k") + helpStyle.Render(" kill"),
helpKeyStyle.Render("c") + helpStyle.Render(" copy cmd"),
helpKeyStyle.Render("s") + helpStyle.Render(" sort"),
Expand Down
47 changes: 37 additions & 10 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,43 @@ func main() {
os.Exit(1)
}

p := tea.NewProgram(tui.NewModel())
finalModel, err := p.Run()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
for {
p := tea.NewProgram(tui.NewModel())
finalModel, err := p.Run()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}

// If the user pressed a key to attach, run zmx attach
if m, ok := finalModel.(tui.Model); ok {
target, replace := m.AttachTarget()
if target != "" {
if replace {
env := os.Environ()
syscall.Exec(zmxPath, []string{"zmx", "attach", target}, env)
// If syscall.Exec fails, we might still be here
fmt.Fprintf(os.Stderr, "Error: failed to exec into session %q\n", target)
os.Exit(1)
}

cmd := exec.Command(zmxPath, "attach", target)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Error: failed to attach to session %q: %v\n", target, err)
// Wait for user to read the error before returning to zsm
fmt.Print("Press Enter to return to zsm...")
var dummy string
fmt.Scanln(&dummy)
}
continue
}
}

// If the user pressed Enter to attach, exec into zmx attach
if m, ok := finalModel.(tui.Model); ok && m.AttachTarget() != "" {
env := os.Environ()
syscall.Exec(zmxPath, []string{"zmx", "attach", m.AttachTarget()}, env)
// Otherwise, the user quit the TUI normally
break
}
}