B1 fix — route semantic-frontend Key events into the editor core
Visual validation found "nothing occurs" when typing in pmacs-gpu. Root cause is daemon-side, not consumer-side: the dispatcher's catch-all arm only called `apply_event` (→ `dispatch_key`) when the source frontend had a `RenderState` — i.e. a grid frontend. A semantic frontend like pmacs-gpu has only a `SemanticRenderState`, so its `Key`/`Mouse`/etc. events hit the `else` branch and were silently dropped (the long-standing "M11.5 scope" posture). So pmacs-gpu's keys never reached the keymap; the cursor never moved. This contradicts the Phase B framing's "consumer-only" claim: the Explore fact-check verified `apply_event` → `dispatch_key` (true for grid frontends) but not that the dispatcher gates that call on `render_state`, so semantic-frontend keys never reach `apply_event`. Exactly the gap visual validation exists to catch. Fix: when the source has no `render_state` but is a registered semantic session, route its input through a new `apply_semantic_input_event` — `Key` → `dispatch_key`, `Mouse` → `dispatch_mouse` — the same core path the TUI uses. No grid state is needed (the editor core owns the cursor/buffer/commands); the resulting motion/edit flows back to pmacs-gpu as `CursorByte` / `CrdtOp`. Regression test `semantic_frontend_key_event_reaches_the_core`: a printable `Key` from a semantic frontend self-inserts and advances its window cursor (0→1). Before the fix the dispatcher dropped it. Gates green: - cargo fmt --all -- --check - cargo clippy --all-targets --workspace -- -D warnings (default + crdt) - pmacs lib 1334; crdt daemon tests pass - m4_acceptance 88, m11_5_semantic_acceptance (--features crdt) 2 Still awaiting visual confirmation (caret tracks arrow keys in a running pmacs-gpu) before merge, per the framing's process rule. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5eec8a6f10
commit
98ef140a84
|
|
@ -1354,15 +1354,22 @@ fn handle_dispatcher_event(
|
|||
if let Some(render_state) = render_states.get_mut(&source) {
|
||||
apply_event(editor, event, &mut term_size, render_state);
|
||||
term_sizes.insert(source, term_size);
|
||||
} else if semantic_states.contains_key(&source) {
|
||||
// Phase B (session B1) — a semantic (grid-less)
|
||||
// session has no `RenderState`, but its keyboard
|
||||
// input still drives the shared editor core. The
|
||||
// input events that don't need grid state
|
||||
// (`Key`, `Mouse`) dispatch through the same
|
||||
// `dispatch_key` / `dispatch_mouse` path the TUI
|
||||
// uses; the resulting cursor move / edit flows
|
||||
// back as `CursorByte` / `CrdtOp`. (Earlier this
|
||||
// arm dropped these events — the "M11.5 scope"
|
||||
// posture — which is why typing in pmacs-gpu did
|
||||
// nothing before B1.)
|
||||
apply_semantic_input_event(editor, event, term_size);
|
||||
} else {
|
||||
// T M11.2 — a semantic (grid-less) session has
|
||||
// no `RenderState`. Key/Mouse/Paste/Focus
|
||||
// command handling for semantic frontends is
|
||||
// M11.5 scope; until then these events are
|
||||
// dropped rather than panicking the
|
||||
// dispatcher on the absent grid state.
|
||||
debug_assert!(
|
||||
semantic_states.contains_key(&source),
|
||||
false,
|
||||
"fid with neither a render_state nor a semantic_state \
|
||||
sent a frontend event"
|
||||
);
|
||||
|
|
@ -1897,6 +1904,30 @@ fn build_presence_snapshot(editor: &EditorState, frontend_id: FrontendId) -> Pre
|
|||
}
|
||||
}
|
||||
|
||||
/// Dispatch a semantic (grid-less) frontend's input event into the
|
||||
/// shared editor core (Phase B, session B1). Mirrors the `Key` / `Mouse`
|
||||
/// arms of [`apply_event`] but takes no `RenderState` — a semantic
|
||||
/// frontend lays out locally, so the only state these events touch is
|
||||
/// the editor core (cursor, buffer, commands), which `dispatch_key` /
|
||||
/// `dispatch_mouse` operate on directly. `Resize` / `Paste` / `Focus`
|
||||
/// have no grid-less effect yet and are dropped; `Viewport` / `CrdtOp`
|
||||
/// are handled in their own dispatcher arms and never reach here.
|
||||
#[allow(clippy::needless_pass_by_value)] // consumes the event, mirroring `apply_event`.
|
||||
fn apply_semantic_input_event(editor: &mut EditorState, ev: FrontendEvent, term_size: CellSize) {
|
||||
match ev {
|
||||
FrontendEvent::Key(pmacs_key) => {
|
||||
if let Some(ct_key) = key_to_crossterm(&pmacs_key) {
|
||||
editor.dispatch_key(pmacs_key.frontend_id, ct_key);
|
||||
}
|
||||
}
|
||||
FrontendEvent::Mouse(pmacs_mouse) => {
|
||||
let ct_mouse = mouse_to_crossterm(&pmacs_mouse);
|
||||
editor.dispatch_mouse(pmacs_mouse.frontend_id, ct_mouse, term_size);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Takes `ev` by value because it semantically consumes the event;
|
||||
// the caller pulls events out of the channel one at a time and never
|
||||
// needs to look at them again.
|
||||
|
|
@ -2100,4 +2131,55 @@ mod tests {
|
|||
"buffer.after-edit must fire when handle_remote_crdt_op produces a text Edit"
|
||||
);
|
||||
}
|
||||
|
||||
/// Session B1 regression: a `Key` event from a *semantic*
|
||||
/// (grid-less) frontend must reach the editor core. Before B1 the
|
||||
/// dispatcher's catch-all only called `apply_event` when the
|
||||
/// frontend had a `RenderState`, so a semantic frontend's keys were
|
||||
/// silently dropped — typing in pmacs-gpu did nothing. The routing
|
||||
/// now goes through `apply_semantic_input_event`; a printable char
|
||||
/// must self-insert at the frontend's window cursor.
|
||||
#[cfg(feature = "crdt")]
|
||||
#[test]
|
||||
fn semantic_frontend_key_event_reaches_the_core() {
|
||||
use crate::editor::EditorState;
|
||||
use crate::protocol::FrontendId;
|
||||
use pmacs_protocol::{Key, KeyEvent, Modifiers};
|
||||
|
||||
let mut editor = EditorState::new();
|
||||
let fid = FrontendId(99);
|
||||
let view = build_fresh_frontend_view(&mut editor);
|
||||
editor.core.borrow_mut().register_frontend_view(fid, view);
|
||||
|
||||
let before = editor
|
||||
.core
|
||||
.borrow()
|
||||
.active_window_for(fid)
|
||||
.expect("fid window")
|
||||
.cursor;
|
||||
|
||||
apply_semantic_input_event(
|
||||
&mut editor,
|
||||
FrontendEvent::Key(KeyEvent {
|
||||
frontend_id: fid,
|
||||
key: Key::Char('X'),
|
||||
mods: Modifiers::NONE,
|
||||
timestamp_ns: 0,
|
||||
}),
|
||||
CellSize::new(24, 80),
|
||||
);
|
||||
|
||||
let after = editor
|
||||
.core
|
||||
.borrow()
|
||||
.active_window_for(fid)
|
||||
.expect("fid window")
|
||||
.cursor;
|
||||
assert_eq!(
|
||||
after,
|
||||
before + 1,
|
||||
"a semantic frontend's printable Key must self-insert and advance its window cursor \
|
||||
(pre-B1 the dispatcher dropped it)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue