Smart attach: link window when inside tmux
When 'gt X attach' is run from inside a tmux session, link the target session's window as a new tab instead of switching sessions entirely. Use C-b n/p to navigate between tabs. Outside tmux: unchanged behavior (full attach) Inside tmux: links window as tab for convenient multi-session viewing - Add tmux.LinkWindow() and tmux.IsInsideTmux() - Update attachToTmuxSession() with smart detection - Update mayor, deacon, crew, refinery attach commands
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/steveyegge/gastown/internal/git"
|
"github.com/steveyegge/gastown/internal/git"
|
||||||
"github.com/steveyegge/gastown/internal/rig"
|
"github.com/steveyegge/gastown/internal/rig"
|
||||||
"github.com/steveyegge/gastown/internal/style"
|
"github.com/steveyegge/gastown/internal/style"
|
||||||
|
"github.com/steveyegge/gastown/internal/tmux"
|
||||||
"github.com/steveyegge/gastown/internal/workspace"
|
"github.com/steveyegge/gastown/internal/workspace"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -168,8 +169,21 @@ func isInTmuxSession(targetSession string) bool {
|
|||||||
return currentSession == targetSession
|
return currentSession == targetSession
|
||||||
}
|
}
|
||||||
|
|
||||||
// attachToTmuxSession attaches to a tmux session with proper TTY forwarding.
|
// attachToTmuxSession attaches to a tmux session with smart behavior:
|
||||||
|
// - If already inside tmux: links the session as a new tab (use C-b n/p to switch)
|
||||||
|
// - If outside tmux: attaches normally (takes over terminal)
|
||||||
func attachToTmuxSession(sessionID string) error {
|
func attachToTmuxSession(sessionID string) error {
|
||||||
|
// If already inside tmux, link the window as a tab instead of switching sessions
|
||||||
|
if tmux.IsInsideTmux() {
|
||||||
|
t := tmux.NewTmux()
|
||||||
|
if err := t.LinkWindow(sessionID, 0); err != nil {
|
||||||
|
return fmt.Errorf("linking window: %w", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Linked %s as a new tab. Use C-b n to switch to it.\n", sessionID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outside tmux: attach normally
|
||||||
tmuxPath, err := exec.LookPath("tmux")
|
tmuxPath, err := exec.LookPath("tmux")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("tmux not found: %w", err)
|
return fmt.Errorf("tmux not found: %w", err)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -199,13 +198,8 @@ func runDeaconAttach(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
// Session uses a respawn loop, so Claude restarts automatically if it exits
|
// Session uses a respawn loop, so Claude restarts automatically if it exits
|
||||||
|
|
||||||
// Use exec to replace current process with tmux attach
|
// Use shared attach helper (smart: links if inside tmux, attaches if outside)
|
||||||
tmuxPath, err := exec.LookPath("tmux")
|
return attachToTmuxSession(DeaconSessionName)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("tmux not found: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return execCommand(tmuxPath, "attach-session", "-t", DeaconSessionName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDeaconStatus(cmd *cobra.Command, args []string) error {
|
func runDeaconStatus(cmd *cobra.Command, args []string) error {
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -179,25 +177,8 @@ func runMayorAttach(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use exec to replace current process with tmux attach
|
// Use shared attach helper (smart: links if inside tmux, attaches if outside)
|
||||||
tmuxPath, err := exec.LookPath("tmux")
|
return attachToTmuxSession(MayorSessionName)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("tmux not found: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return execCommand(tmuxPath, "attach-session", "-t", MayorSessionName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// execCommand replaces the current process with the given command.
|
|
||||||
// This is used for attaching to tmux sessions.
|
|
||||||
func execCommand(name string, args ...string) error {
|
|
||||||
// On Unix, we would use syscall.Exec to replace the process
|
|
||||||
// For portability, we use exec.Command and wait
|
|
||||||
cmd := exec.Command(name, args...)
|
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMayorStatus(cmd *cobra.Command, args []string) error {
|
func runMayorStatus(cmd *cobra.Command, args []string) error {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -519,6 +520,21 @@ func (t *Tmux) ConfigureGasTownSession(session string, theme Theme, rig, worker,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LinkWindow links a window from another session into the current session.
|
||||||
|
// This allows viewing another session's window as a tab without switching sessions.
|
||||||
|
// Useful when already inside tmux and want to see another session.
|
||||||
|
func (t *Tmux) LinkWindow(sourceSession string, windowIndex int) error {
|
||||||
|
source := fmt.Sprintf("%s:%d", sourceSession, windowIndex)
|
||||||
|
_, err := t.run("link-window", "-s", source)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsInsideTmux checks if the current process is running inside a tmux session.
|
||||||
|
// This is detected by the presence of the TMUX environment variable.
|
||||||
|
func IsInsideTmux() bool {
|
||||||
|
return os.Getenv("TMUX") != ""
|
||||||
|
}
|
||||||
|
|
||||||
// SetMailClickBinding configures left-click on status-right to show mail preview.
|
// SetMailClickBinding configures left-click on status-right to show mail preview.
|
||||||
// This creates a popup showing the first unread message when clicking the mail icon area.
|
// This creates a popup showing the first unread message when clicking the mail icon area.
|
||||||
func (t *Tmux) SetMailClickBinding(session string) error {
|
func (t *Tmux) SetMailClickBinding(session string) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user