pmacs-gpu: forward chorded deletion keys (C-BS word delete)

Backspace/Delete with modifiers were withheld by the chord filter,
so C-BS / C-DEL / M-BS — word-level deletes in the default keymap —
silently did nothing in the GPU while C-<left> word motion worked.
Deletion keys now forward with their modifiers exactly like motion
keys; an unbound chord is a harmless no-op at the daemon keymap, and
chorded deletes never apply optimistically (optimistic_delete_range
requires empty modifiers), so they always round-trip into their
bound commands. Acceptance test drives C-BS / C-DEL through the real
dispatch path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Levi Neuwirth 2026-06-10 11:48:23 -04:00
parent 21671cc590
commit 2339a10b03
2 changed files with 49 additions and 11 deletions

View File

@ -2649,26 +2649,31 @@ fn is_motion_key(key: ProtocolKey) -> bool {
}
/// Whether to forward a translated key to the daemon (session B2).
/// Motion keys go through with any modifiers. Plain text-editing keys
/// (`Char` / `Backspace` / `Enter` / `Delete` / `Tab`) go through only
/// *without* a Ctrl/Alt/Meta chord modifier: a bare key edits text,
/// but a chord drives commands and minibuffer flows the GUI can't
/// render or interact with yet (deferred to a later session). Shift is
/// not a chord modifier — `Shift`+a already arrives as `Char('A')`.
/// Motion keys go through with any modifiers (C-<left> is word
/// motion). Deletion keys do too: C-BS / C-DEL / M-BS are word-level
/// deletes in the default keymap — the same editing-command family as
/// chorded motion, and an unbound chord is a harmless no-op at the
/// daemon keymap. (Chorded deletes never apply optimistically:
/// `optimistic_delete_range` requires empty modifiers, so they always
/// round-trip into their bound commands.) The remaining text keys
/// (`Char` / `Enter` / `Tab`) go through only *without* a
/// Ctrl/Alt/Meta chord modifier: a bare key edits text, but those
/// chords drive commands and minibuffer flows the GUI can't render or
/// interact with yet (deferred to a later session). Shift is not a
/// chord modifier — `Shift`+a already arrives as `Char('A')`.
fn should_forward_key(key: ProtocolKey, mods: Modifiers) -> bool {
if is_motion_key(key) {
return true;
}
if matches!(key, ProtocolKey::Backspace | ProtocolKey::Delete) {
return true;
}
if !is_plain_text_modifiers(mods) {
return false;
}
matches!(
key,
ProtocolKey::Char(_)
| ProtocolKey::Backspace
| ProtocolKey::Enter
| ProtocolKey::Delete
| ProtocolKey::Tab
ProtocolKey::Char(_) | ProtocolKey::Enter | ProtocolKey::Tab
)
}
@ -3600,6 +3605,13 @@ mod tests {
assert!(should_forward_key(ProtocolKey::Left, ctrl));
assert!(should_forward_key(ProtocolKey::Down, shift));
assert!(should_forward_key(ProtocolKey::PageUp, none));
// Deletion keys forward regardless of modifiers too — C-BS /
// C-DEL / M-BS are word-level deletes in the default keymap,
// the same editing-command family as chorded motion.
assert!(should_forward_key(ProtocolKey::Backspace, ctrl));
assert!(should_forward_key(ProtocolKey::Delete, ctrl));
assert!(should_forward_key(ProtocolKey::Backspace, Modifiers::ALT));
}
#[test]

View File

@ -74,6 +74,32 @@ fn backspace_deletes_the_shift_selected_region() {
assert_eq!(cursor, 1);
}
/// C-Backspace deletes the previous word (and C-Delete the next),
/// mirroring C-arrow word motion. The pmacs-gpu frontend forwards
/// chorded deletion keys to this same dispatch path.
#[test]
fn ctrl_backspace_deletes_the_previous_word() {
let mut s = EditorState::new();
type_str(&mut s, "alpha beta");
s.dispatch_key(
FrontendId::LOCAL,
key(KeyCode::Backspace, KeyModifiers::CONTROL),
);
let (text, _, cursor) = probe(&s);
assert_eq!(text, "alpha ", "C-BS deletes back through the previous word");
assert_eq!(cursor, 6);
s.dispatch_key(FrontendId::LOCAL, key(KeyCode::Left, KeyModifiers::CONTROL));
s.dispatch_key(
FrontendId::LOCAL,
key(KeyCode::Delete, KeyModifiers::CONTROL),
);
let (text, _, cursor) = probe(&s);
assert_eq!(text, " ", "C-DEL deletes forward through the next word");
assert_eq!(cursor, 0);
}
#[test]
fn typing_replaces_the_shift_selected_region() {
let mut s = EditorState::new();