Skip to content

feat: add #{pane_last_special_key} — read-only non-text-key route signal#315

Closed
quazardous wants to merge 1 commit into
psmux:masterfrom
quazardous:feat/pane-last-special-key
Closed

feat: add #{pane_last_special_key} — read-only non-text-key route signal#315
quazardous wants to merge 1 commit into
psmux:masterfrom
quazardous:feat/pane-last-special-key

Conversation

@quazardous

Copy link
Copy Markdown
Contributor

What

Two read-only per-pane format variables describing the last non-text key that reached a pane via the interactive input route:

  • #{pane_last_special_key} — its canonical bind-key name (Escape, Enter, Tab, Up, F9, C-c, M-a, …), empty until the first one.
  • #{pane_last_special_key_ms} — ms since it arrived.
psmux display-message -p '#{pane_last_special_key} #{pane_last_special_key_ms}'   # "Escape 320"

The sibling of #{pane_last_text_input} (#311) for the complement: together they partition all interactive keys into text vs non-text.

Why / design (same constraints as #311)

  • No file, no lifecycle — one Option<(Instant, String)> on the Pane, freed with it.
  • No policy in core — it exposes the last key + its age; the consumer decides.
  • Route, not content — set only in forward_key_to_active; the injected route (send-keys / send-paste / send-textsend_text_to_active) never updates it.
  • Per-pane, stamping exactly the panes that receive the key (sync-input aware, via a small for_each_receiving_pane helper).
  • Reuses existing piecesis_text_input_key (feat: add #{pane_last_input} — read-only live human-input signal #311) for the text/non-text split and format_key_binding (the list-keys renderer) for the name, so the value is the canonical key syntax users already know.

Open question — is this the right orientation?

Before polishing I'd like your read on the shape:

  1. Is a single non-text "last special key" pair (name + age) the right granularity, or would you prefer a generic #{pane_last_key} over all keys (which would overlap #{pane_last_text_input}), or something narrower?
  2. Naming: pane_last_special_key / _ms OK?
  3. Render the name via format_key_binding (bind-key syntax, as here), or another form?

Happy to adjust to whatever fits psmux best.

Diff

One field on Pane + its inits; a for_each_receiving_pane helper + the text/non-text stamp in forward_key_to_active; two format arms; unit tests; a doc note.

Tests

cargo test --bin psmux pane_lasttests-rs/test_pane_last_special_key.rs, passes alongside #311's. The positive live path needs an attached client, so it's covered by the unit test + the single set site (same as #311).

The sibling of #{pane_last_text_input} (psmux#311) for the complement: the last
NON-text key that reached a pane via the interactive input route, by canonical
bind-key name, plus its age.

- #{pane_last_special_key}    — name (Escape, Enter, Up, F9, C-c, M-a, …)
- #{pane_last_special_key_ms} — ms since it arrived

Same contract as psmux#311: one Option<(Instant, String)> on the Pane (no file, no
lifecycle); set only in forward_key_to_active so the injected route never
updates it; per-pane, stamping exactly the panes that receive the key. Reuses
is_text_input_key for the text/non-text split and format_key_binding (the
list-keys renderer) for the name. Helper for_each_receiving_pane factors the
sync-input-aware pane selection.
psmux added a commit that referenced this pull request May 27, 2026
Add two read-only per-pane format variables for the last non-text key
received on the interactive input route:

- #{pane_last_special_key}    -- canonical bind-key name (Escape, Enter,
                                 Up, F9, C-c, M-a, ...)
- #{pane_last_special_key_ms} -- milliseconds since it arrived

Complement of #{pane_last_text_input} (#311): together they partition
all interactive keys into text vs non-text. Same route contract: set
only in forward_key_to_active (the injected route never updates it).

Also refactors the sync-input-aware pane selection into a shared
for_each_receiving_pane helper, deduplicating the text-input stamping.

Includes unit tests (key classification + naming) and E2E test.

Co-authored-by: David Berlioz <berliozdavid@gmail.com>
@psmux

psmux commented May 27, 2026

Copy link
Copy Markdown
Owner

Hey @quazardous, great follow-up to #311. Applied in aca1120 with your Co-authored-by credit.

What I verified:

  • Unit tests pass: key classification (is_text_input_key complement) and canonical naming (format_key_binding) for Escape, Enter, C-c, M-a, F9, Up, plus the text/non-text boundary
  • E2E: format variables resolve correctly (empty before any interactive key)
  • E2E: injected route (send-keys) correctly does NOT update either signal
  • E2E: TCP path resolves both variables
  • E2E: new panes start with empty last_special_key
  • for_each_receiving_pane refactor is a nice cleanup, deduplicating the sync-input aware pane selection

Regarding your open questions:

  1. A single non-text "last special key" pair is the right granularity. Having it partition with pane_last_text_input is clean and complete.
  2. Naming pane_last_special_key / _ms is fine. Consistent with the companion.
  3. format_key_binding (bind-key syntax) is the right choice. Users already know those names from list-keys.

Note on tmux parity: tmux does not have pane_last_special_key, pane_last_text_input, or any per-pane key tracking. These are psmux-specific extensions, which is perfectly fine. They fill a gap that tmux does not address.

Thanks for the clean implementation, David.

1 similar comment
@psmux

psmux commented May 27, 2026

Copy link
Copy Markdown
Owner

Hey @quazardous, great follow-up to #311. Applied in aca1120 with your Co-authored-by credit.

What I verified:

  • Unit tests pass: key classification (is_text_input_key complement) and canonical naming (format_key_binding) for Escape, Enter, C-c, M-a, F9, Up, plus the text/non-text boundary
  • E2E: format variables resolve correctly (empty before any interactive key)
  • E2E: injected route (send-keys) correctly does NOT update either signal
  • E2E: TCP path resolves both variables
  • E2E: new panes start with empty last_special_key
  • for_each_receiving_pane refactor is a nice cleanup, deduplicating the sync-input aware pane selection

Regarding your open questions:

  1. A single non-text "last special key" pair is the right granularity. Having it partition with pane_last_text_input is clean and complete.
  2. Naming pane_last_special_key / _ms is fine. Consistent with the companion.
  3. format_key_binding (bind-key syntax) is the right choice. Users already know those names from list-keys.

Note on tmux parity: tmux does not have pane_last_special_key, pane_last_text_input, or any per-pane key tracking. These are psmux-specific extensions, which is perfectly fine. They fill a gap that tmux does not address.

Thanks for the clean implementation, David.

@psmux

psmux commented May 27, 2026

Copy link
Copy Markdown
Owner

Applied in commit aca1120 with Co-authored-by credit. Thanks @quazardous!

@psmux psmux closed this May 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants