Skip to content

fix(cli): apply focus to tmux pane for external sessions#307

Draft
sei40kr wants to merge 1 commit intofolke:mainfrom
sei40kr:fix-179
Draft

fix(cli): apply focus to tmux pane for external sessions#307
sei40kr wants to merge 1 commit intofolke:mainfrom
sei40kr:fix-179

Conversation

@sei40kr
Copy link
Copy Markdown

@sei40kr sei40kr commented Apr 20, 2026

Description

cli.toggle({ focus = true }) and cli.focus() did not focus the tmux pane when using external mux sessions (cli.mux.enabled = true with create = "split" / "window").

Root cause

sidekick.cli was built around state.terminal (nvim's embedded terminal). When external mux sessions were added, the UI layer (M.toggle / M.focus) still keyed off state.terminal, and Session had no focus() method — so there was literally no path from the UI to tmux select-pane. Terminal focus kept working only because nvim_set_current_win is a synchronous API the UI callback could call directly via a captured opts.focus, bypassing the missing propagation.

Changes

  • Promote focus, is_focused, and blur to Session contract (empty / false defaults on the base for backends that don't support them)
  • Implement them on the tmux backend:
    • focus: tmux select-window then tmux select-paneselect-window first so create = "window" switches the client to the pane's window (no-op for create = "split")
    • is_focused: tmux display-message comparing #{client_active_pane} with the pane id
    • blur: switch back to the editor pane identified via $TMUX_PANE (the env var tmux sets for the pane nvim is running in) — works regardless of tmux history
  • Unify the UI layer through state.session:*. Terminal is already registered as a Session backend (state.terminal === state.session for terminal-backed sessions), so the same calls work for both paths:
    • terminal-backed session → Terminal:focus() / :is_focused() / :blur() (existing methods)
    • external tmux session → Tmux:focus() / :is_focused() / :blur() (new)
  • M.attach now fires focus on opts.show and opts.focus ~= false for both backends, fixing the silent default-false asymmetry external previously had against terminal
  • M.toggle callback handles both backends via state.session:focus(), keeping the existing is_open() gate so toggling terminal visibility off doesn't immediately re-open it
  • M.focus callback uses state.session:is_focused() / :blur() / :focus() directly — the toggle-focus behavior now applies to external panes too (when invoked from inside the pane, e.g. via a tmux keybinding that calls back into nvim)

Behavior matrix after the fix

Call terminal external tmux
toggle() toggle visibility + focus focus pane
toggle({ focus = true }) toggle visibility + focus focus pane
toggle({ focus = false }) toggle visibility only no-op
send("x") show + focus, then send focus pane, then send
send("x", { focus = false }) show, then send send only
focus() from editor focus pane (terminal not currently focused) focus pane
focus() from inside the pane blur (back to previous window) blur (back to $TMUX_PANE)
hide() hide window no-op (filter excludes external)
close() close terminal detach tracking (pane stays)

Related Issue(s)

Screenshots

N/A — behavioral fix with no UI change.

🤖 Generated with Claude Code

@github-actions github-actions Bot added the size/m Medium PR (<50 lines changed) label Apr 20, 2026
@sei40kr sei40kr force-pushed the fix-179 branch 4 times, most recently from ef2ada6 to 54125df Compare April 20, 2026 15:53
@github-actions github-actions Bot added size/l Large PR (<100 lines changed) and removed size/m Medium PR (<50 lines changed) labels Apr 20, 2026
@github-actions github-actions Bot added size/m Medium PR (<50 lines changed) and removed size/l Large PR (<100 lines changed) labels May 1, 2026
@github-actions github-actions Bot added size/l Large PR (<100 lines changed) and removed size/m Medium PR (<50 lines changed) labels May 1, 2026
Adds `Session:focus()`, `Session:is_focused()`, and `Session:blur()`
backend hooks with tmux implementations, and unifies the UI layer to
dispatch through `state.session:*`. `Terminal` is already a `Session`
backend so `state.terminal === state.session` for terminal-backed
sessions — the same calls cover both paths.

tmux:
- focus: select-window then select-pane (window switch handles
  create="window", select-pane handles the pane within the window)
- is_focused: compare client_active_pane with the pane id
- blur: switch back to the editor pane via $TMUX_PANE

Also fixes the default-focus asymmetry in `M.attach`: external focus
now fires on `opts.show and opts.focus ~= false` to match terminal,
so `send()` and `toggle()` (no args) focus external panes the same
way they focus terminals.

Closes folke#179.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l Large PR (<100 lines changed)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: focus is not appplied with tmux pane

1 participant