Skip to content
Merged
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
2 changes: 1 addition & 1 deletion cmd/sidecar/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func main() {
// Unset TMUX so sidecar's internal tmux sessions are independent of any
// outer tmux session. This allows prefix+d to detach from the workspace's
// inner session rather than the user's outer tmux.
os.Unsetenv("TMUX")
_ = os.Unsetenv("TMUX")

// Start pprof server if enabled (for memory profiling)
if pprofPort := os.Getenv("SIDECAR_PPROF"); pprofPort != "" {
Expand Down
18 changes: 18 additions & 0 deletions internal/plugins/workspace/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package workspace

import (
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
Expand All @@ -22,6 +23,11 @@ const (
shellSessionPrefix = "sidecar-sh-" // Distinct from worktree prefix "sidecar-ws-"
)

// launchedInsideTmux is true if sidecar was started from within an existing
// tmux session (i.e. TMUX env var was set at process start). This is captured
// at package init time, before main() unsets TMUX for nested session support.
var launchedInsideTmux = os.Getenv("TMUX") != ""

// tmuxInstalled caches whether tmux is available in PATH.
// Checked once and cached to avoid repeated exec calls.
var (
Expand Down Expand Up @@ -70,6 +76,18 @@ func getTmuxPrefix() string {
return tmuxPrefixCached
}

// getTmuxDetachHint returns the key sequence hint for detaching from a nested
// tmux session. When sidecar was launched inside an existing tmux session, the
// user needs to press the prefix twice (once to reach the inner session) before
// pressing d. Otherwise a single prefix + d suffices.
func getTmuxDetachHint() string {
prefix := getTmuxPrefix()
if launchedInsideTmux {
return prefix + " " + prefix + " d"
}
return prefix + " d"
}

// tmuxNotationToHuman converts tmux key notation to human-readable format.
// Examples: C-b → Ctrl-b, C-a → Ctrl-a, M-x → Alt-x
func tmuxNotationToHuman(notation string) string {
Expand Down
12 changes: 6 additions & 6 deletions internal/plugins/workspace/view_preview.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,11 @@ func (p *Plugin) renderOutputContent(width, height int) string {
hint = interactiveStyle.Render("INTERACTIVE") + " " + dimText(p.getInteractiveExitKey()+" exit • "+p.getInteractiveAttachKey()+" attach")
} else {
// Only show "E for interactive" hint if feature flag is enabled
prefix := getTmuxPrefix()
detach := getTmuxDetachHint()
if features.IsEnabled(features.TmuxInteractiveInput.Name) {
hint = dimText(fmt.Sprintf("t to attach • E for interactive • %s d to detach", prefix))
hint = dimText(fmt.Sprintf("t to attach • E for interactive • %s to detach", detach))
} else {
hint = dimText(fmt.Sprintf("t to attach • %s d to detach", prefix))
hint = dimText(fmt.Sprintf("t to attach • %s to detach", detach))
}
}
height-- // Reserve line for hint
Expand Down Expand Up @@ -432,11 +432,11 @@ func (p *Plugin) renderShellOutput(width, height int) string {
hint = interactiveStyle.Render("INTERACTIVE") + " " + dimText(p.getInteractiveExitKey()+" exit")
} else {
// Only show "E for interactive" hint if feature flag is enabled
prefix := getTmuxPrefix()
detach := getTmuxDetachHint()
if features.IsEnabled(features.TmuxInteractiveInput.Name) {
hint = dimText(fmt.Sprintf("t to attach • E for interactive • %s d to detach", prefix))
hint = dimText(fmt.Sprintf("t to attach • E for interactive • %s to detach", detach))
} else {
hint = dimText(fmt.Sprintf("t to attach • %s d to detach", prefix))
hint = dimText(fmt.Sprintf("t to attach • %s to detach", detach))
}
}
height-- // Reserve line for hint
Expand Down
Loading