diff --git a/cmd/kctl-tui/full.go b/cmd/kctl-tui/full.go index b963dad..b56d8da 100644 --- a/cmd/kctl-tui/full.go +++ b/cmd/kctl-tui/full.go @@ -7,7 +7,6 @@ import ( "fmt" "os" "os/exec" - "runtime" "sort" tea "github.com/charmbracelet/bubbletea" @@ -281,54 +280,40 @@ func (m *fullModel) startTmuxSession() tea.Cmd { ctxA := m.cfg.ResolveContext(envA, m.selectedContext) k9sCmdA := fmt.Sprintf("k9s --context %s -n %s", ctxA, m.selectedNamespace) - // Kill stale session first (ignore error if none exists). - killErr := exec.Command("tmux", "kill-session", "-t", "kctl").Run() fmt.Fprintf(os.Stderr, "[debug] selfPath=%s\n", selfPath) fmt.Fprintf(os.Stderr, "[debug] panelCmd=%s\n", panelCmd) - fmt.Fprintf(os.Stderr, "[debug] kill-session err=%v\n", killErr) - var c *exec.Cmd - if runtime.GOOS == "windows" { - // On Windows, psmux doesn't handle ; separators when args are - // passed individually via exec.Command. Build the full command - // string and run it through cmd.exe. - tmuxScript := fmt.Sprintf( - "tmux new-session -d -s kctl %q ; set-option -t kctl remain-on-exit on ; split-window -v -t kctl:0.0 %q", - panelCmd, k9sCmdA, - ) - if len(m.cfg.Envs) > 1 { - envB := m.cfg.Envs[1] - ctxB := m.cfg.ResolveContext(envB, m.selectedContext) - k9sCmdB := fmt.Sprintf("k9s --context %s -n %s", ctxB, m.selectedNamespace) - tmuxScript += fmt.Sprintf(" ; split-window -v -t kctl:0.1 %q", k9sCmdB) + // Kill stale session first (ignore error if none exists). + exec.Command("tmux", "kill-session", "-t", "kctl").Run() + + // Run setup commands individually — this avoids the ; separator + // issue on Windows where psmux doesn't handle chained args. + setup := [][]string{ + {"new-session", "-d", "-s", "kctl", panelCmd}, + {"set-option", "-t", "kctl", "remain-on-exit", "on"}, + {"split-window", "-v", "-t", "kctl:0.0", k9sCmdA}, + } + if len(m.cfg.Envs) > 1 { + envB := m.cfg.Envs[1] + ctxB := m.cfg.ResolveContext(envB, m.selectedContext) + k9sCmdB := fmt.Sprintf("k9s --context %s -n %s", ctxB, m.selectedNamespace) + setup = append(setup, []string{"split-window", "-v", "-t", "kctl:0.1", k9sCmdB}) + } + setup = append(setup, + []string{"select-layout", "-t", "kctl", "even-vertical"}, + []string{"select-pane", "-t", "kctl:0.0"}, + ) + + for _, args := range setup { + if out, err := exec.Command("tmux", args...).CombinedOutput(); err != nil { + fmt.Fprintf(os.Stderr, "[debug] tmux %s failed: %v\n%s\n", args[0], err, out) + return func() tea.Msg { return tmuxDoneMsg{err: fmt.Errorf("tmux %s: %w", args[0], err)} } } - tmuxScript += " ; select-layout -t kctl even-vertical ; select-pane -t kctl:0.0 ; attach -t kctl" - fmt.Fprintf(os.Stderr, "[debug] windows cmd: %s\n", tmuxScript) - c = exec.Command("cmd.exe", "/c", tmuxScript) - } else { - args := []string{ - "new-session", "-d", "-s", "kctl", - panelCmd, ";", - "set-option", "-t", "kctl", "remain-on-exit", "on", ";", - "split-window", "-v", "-t", "kctl:0.0", k9sCmdA, ";", - } - if len(m.cfg.Envs) > 1 { - envB := m.cfg.Envs[1] - ctxB := m.cfg.ResolveContext(envB, m.selectedContext) - k9sCmdB := fmt.Sprintf("k9s --context %s -n %s", ctxB, m.selectedNamespace) - args = append(args, - "split-window", "-v", "-t", "kctl:0.1", k9sCmdB, ";", - ) - } - args = append(args, - "select-layout", "-t", "kctl", "even-vertical", ";", - "select-pane", "-t", "kctl:0.0", ";", - "attach", "-t", "kctl", - ) - fmt.Fprintf(os.Stderr, "[debug] unix args: %v\n", args) - c = exec.Command("tmux", args...) } + // Only attach uses tea.ExecProcess so it takes over the terminal. + fmt.Fprintf(os.Stderr, "[debug] tmux attach -t kctl\n") + c := exec.Command("tmux", "attach", "-t", "kctl") return tea.ExecProcess(c, func(err error) tea.Msg { return tmuxDoneMsg{err: err} })