test(gui-1b): step 3 witnesses the panel viewport, not the emitted event
The framing says it in as many words: "Not 'a PanelPointer was emitted' --- the observable effect on the panel's viewport." The row I wrote filtered and counted panel-pointer events, which is the blind spot the framing exists to close rather than the defect it guards against. It would have passed if the vertical axis emitted a horizontal gesture, if the receiver dropped what arrived, or if some other event accompanied a sub-threshold delta. Both halves now run in one row. The PRODUCER is this frontend's apply_wheel, reached through dispatch_window_event. The RECEIVER is a real pmacs::editor::EditorState with a live panel window, driven through classify_panel_pointer + apply_panel_pointer --- the pair the daemon itself calls --- and the assertion is the panel window's (view_top, view_left). Per axis: a sub-threshold delta puts nothing on the wire and moves the viewport by nothing; the delta that completes the notch moves it by exactly one step, on that axis and not the other. That needs the editor crate, so pmacs-gpu gains a DEV-dependency on pmacs --- test-only, never in the shipped graph --- and pmacs gains three #[doc(hidden)] test-support methods beside the ones already there: install_panel_view_for_test (which daemon.rs's own semantic_panel_view now delegates to, so there is one fixture rather than two), seed_window_buffer_for_test, and window_view_origin_for_test. Three things the row failed on before passing, each now a named setup fact rather than a silent dependency. The panel buffer starts empty and scroll_window clamps to line_count - 1, so an unseeded panel cannot scroll at all. Seeding it is not enough either: TextView caches the line partition it was built with, so the window has to be handed a rebuilt view. And the receiver re-derives the panel grid from an accepted geometry declaration --- without one every coordinate is outside a grid that does not exist and the gesture is Refused before it can do anything. The mutation that matters is the one no emission count could see: dropping PKind::ScrollLeft/ScrollRight from the daemon's panel arm --- the receiver half, the axis whose arm did not exist before B2 --- fires this row. So do rounding instead of banking, and collapsing the two axes into one accumulator. Gates: fmt; clippy --workspace --all-targets -D warnings; pmacs-gpu 322; --lib 2009; --lib --features crdt 2202; git diff --check.
This commit is contained in:
parent
a7006faf47
commit
e50f38ae20
|
|
@ -2627,6 +2627,7 @@ dependencies = [
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"glyphon",
|
"glyphon",
|
||||||
"loro",
|
"loro",
|
||||||
|
"pmacs",
|
||||||
"pmacs-protocol",
|
"pmacs-protocol",
|
||||||
"pollster",
|
"pollster",
|
||||||
"sys-locale",
|
"sys-locale",
|
||||||
|
|
|
||||||
|
|
@ -70,3 +70,8 @@ unicode-width = "0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
# TEST-ONLY, and never a runtime dependency: step 3's panel-wheel
|
||||||
|
# witness has to observe the RECEIVER's effect, not the event this
|
||||||
|
# frontend emits, because the defect it exists for is precisely
|
||||||
|
# "the frontend emits and the receiver discards".
|
||||||
|
pmacs = { path = ".." }
|
||||||
|
|
|
||||||
|
|
@ -21972,6 +21972,18 @@ mod tests {
|
||||||
// Bottom panel Stage 2B-3 — the GPU band
|
// Bottom panel Stage 2B-3 — the GPU band
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
|
fn panel_frame_of_buffer(
|
||||||
|
buffer_id: BufferId,
|
||||||
|
rows: u32,
|
||||||
|
cols: u32,
|
||||||
|
geometry_epoch: u64,
|
||||||
|
panel_epoch: u64,
|
||||||
|
) -> PanelFrame {
|
||||||
|
let mut frame = panel_frame_of(rows, cols, geometry_epoch, panel_epoch);
|
||||||
|
frame.buffer_id = buffer_id;
|
||||||
|
frame
|
||||||
|
}
|
||||||
|
|
||||||
fn panel_frame_of(rows: u32, cols: u32, geometry_epoch: u64, panel_epoch: u64) -> PanelFrame {
|
fn panel_frame_of(rows: u32, cols: u32, geometry_epoch: u64, panel_epoch: u64) -> PanelFrame {
|
||||||
let cells = (0..(rows as usize * cols as usize))
|
let cells = (0..(rows as usize * cols as usize))
|
||||||
.map(|_| terminal_cell(pmacs_protocol::Glyph::Char('x'), CellStyle::default()))
|
.map(|_| terminal_cell(pmacs_protocol::Glyph::Char('x'), CellStyle::default()))
|
||||||
|
|
@ -22012,13 +22024,17 @@ mod tests {
|
||||||
/// `Metrics`; a second surface declaration is suppressed by design
|
/// `Metrics`; a second surface declaration is suppressed by design
|
||||||
/// and returns `None`.
|
/// and returns `None`.
|
||||||
fn present_panel_in_harness(h: &mut EffectHarness, panel_epoch: u64) {
|
fn present_panel_in_harness(h: &mut EffectHarness, panel_epoch: u64) {
|
||||||
|
present_panel_of_buffer(h, BufferId::from_raw(77), panel_epoch);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn present_panel_of_buffer(h: &mut EffectHarness, buffer_id: BufferId, panel_epoch: u64) {
|
||||||
{
|
{
|
||||||
let state = h.app.state.as_mut().expect("harness state");
|
let state = h.app.state.as_mut().expect("harness state");
|
||||||
state.set_panel_wire(PANEL_MIN_VERSION);
|
state.set_panel_wire(PANEL_MIN_VERSION);
|
||||||
let (epoch, total) = state
|
let (epoch, total) = state
|
||||||
.next_geometry_declaration(GeometryTrigger::Metrics)
|
.next_geometry_declaration(GeometryTrigger::Metrics)
|
||||||
.expect("metrics always advance the panel geometry epoch");
|
.expect("metrics always advance the panel geometry epoch");
|
||||||
let frame = panel_frame_of(4, total.cols.max(1), epoch, panel_epoch);
|
let frame = panel_frame_of_buffer(buffer_id, 4, total.cols.max(1), epoch, panel_epoch);
|
||||||
let _ = state.apply_attach_message(InstanceMessage::PanelFrame(
|
let _ = state.apply_attach_message(InstanceMessage::PanelFrame(
|
||||||
PanelFramePayload::Present(frame),
|
PanelFramePayload::Present(frame),
|
||||||
));
|
));
|
||||||
|
|
@ -22031,36 +22047,51 @@ mod tests {
|
||||||
let _ = h.read_until_sentinel();
|
let _ = h.read_until_sentinel();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Step 3 — **the panel's fractional wheel, end to end, per axis.**
|
/// The RECEIVER half of step 3: a real editor with a bottom panel,
|
||||||
|
/// seeded with something to scroll in both directions, and with a
|
||||||
|
/// frame geometry accepted.
|
||||||
///
|
///
|
||||||
/// Revision 20 sharpened this witness because its earlier form was
|
/// Two things here are load-bearing, and each was found by the row
|
||||||
/// satisfiable with the mechanism it protects entirely broken: a
|
/// failing without it. The panel buffer starts **empty**, and
|
||||||
/// whole tick passes straight through #243's vertical receiver even
|
/// `scroll_window` clamps to `line_count - 1`, so an unseeded panel
|
||||||
/// if B1's accumulator discards every sub-tick it is given. So the
|
/// cannot scroll at all. And the receiver re-derives the panel grid
|
||||||
/// row requires **fractional input** — a first sub-threshold delta
|
/// from an accepted geometry declaration — without one, every
|
||||||
/// produces **no** gesture, and accumulated same-panel deltas
|
/// coordinate is outside a grid that does not exist and the gesture
|
||||||
/// produce **exactly one** — and it requires that on **each axis**
|
/// is `Refused` before it can have any effect.
|
||||||
/// separately, because a single accumulator fed by both axes passes
|
fn panel_receiver() -> (
|
||||||
/// any one-axis row.
|
pmacs::editor::EditorState,
|
||||||
|
pmacs::protocol::FrontendId,
|
||||||
|
pmacs::window::WindowId,
|
||||||
|
BufferId,
|
||||||
|
) {
|
||||||
|
let editor = pmacs::editor::EditorState::new();
|
||||||
|
let fid = pmacs::protocol::FrontendId(4242);
|
||||||
|
let (_document, panel) = editor.install_panel_view_for_test(fid, true);
|
||||||
|
let panel = panel.expect("the fixture installs a panel window");
|
||||||
|
let wide_line = "w".repeat(400);
|
||||||
|
editor.seed_window_buffer_for_test(panel, &format!("{wide_line}\n").repeat(50));
|
||||||
|
let panel_buffer = editor
|
||||||
|
.window_buffer_for_test(panel)
|
||||||
|
.expect("the panel window has a buffer");
|
||||||
|
let _ =
|
||||||
|
editor.accept_semantic_frame_geometry(fid, 1, pmacs_protocol::CellSize::new(24, 80));
|
||||||
|
(editor, fid, panel, panel_buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The PRODUCER half of step 3: this frontend, presenting a panel
|
||||||
|
/// for the very buffer the receiver is showing — so the gestures it
|
||||||
|
/// emits are about the same window the assertions read — with the
|
||||||
|
/// pointer parked on a panel **cell**.
|
||||||
///
|
///
|
||||||
/// Driven through `dispatch_window_event` and observed on the wire,
|
/// Panel chrome banks nothing at all, and a probe that drifted onto
|
||||||
/// which is where a panel gesture actually goes.
|
/// it would satisfy every "nothing moved" assertion for entirely
|
||||||
///
|
/// the wrong reason, so the target is asserted here.
|
||||||
/// *Mutations: round the notch instead of banking it → the
|
fn panel_producer(panel_buffer: BufferId) -> EffectHarness {
|
||||||
/// sub-threshold legs (a 0.6 delta becomes a whole tick); bank into
|
|
||||||
/// one accumulator per surface instead of per (surface, axis) → the
|
|
||||||
/// second axis's first leg, which the first axis's leftover
|
|
||||||
/// completes.*
|
|
||||||
#[test]
|
|
||||||
fn step3_a_panel_wheel_needs_a_whole_notch_on_each_axis() {
|
|
||||||
use winit::dpi::PhysicalPosition;
|
use winit::dpi::PhysicalPosition;
|
||||||
use winit::event::{DeviceId, MouseScrollDelta, TouchPhase};
|
use winit::event::DeviceId;
|
||||||
|
|
||||||
let mut h = EffectHarness::new();
|
let mut h = EffectHarness::new();
|
||||||
present_panel_in_harness(&mut h, 1);
|
present_panel_of_buffer(&mut h, panel_buffer, 1);
|
||||||
|
|
||||||
// A pixel inside the panel's content, so the wheel target is a
|
|
||||||
// cell rather than the band's chrome.
|
|
||||||
let (px, py, _, ph) = h
|
let (px, py, _, ph) = h
|
||||||
.app
|
.app
|
||||||
.state
|
.state
|
||||||
|
|
@ -22078,63 +22109,145 @@ mod tests {
|
||||||
h.app.classify_wheel_target(point.0, point.1),
|
h.app.classify_wheel_target(point.0, point.1),
|
||||||
WheelTarget::PanelCell { .. }
|
WheelTarget::PanelCell { .. }
|
||||||
),
|
),
|
||||||
"setup: the probe must be a panel CELL — panel chrome banks \
|
"setup: the probe must be a panel CELL"
|
||||||
nothing at all, and would satisfy every 'no gesture' \
|
|
||||||
assertion below for the wrong reason"
|
|
||||||
);
|
);
|
||||||
|
h
|
||||||
|
}
|
||||||
|
|
||||||
let mut wheel = |dx: f32, dy: f32| {
|
/// Step 3 — **the panel wheel's END-TO-END effect, on both axes,
|
||||||
h.feed(&WindowEvent::MouseWheel {
|
/// driven by fractional input.**
|
||||||
device_id: DeviceId::dummy(),
|
///
|
||||||
delta: MouseScrollDelta::LineDelta(dx, -dy),
|
/// The framing is explicit that an emission-only witness will not
|
||||||
phase: TouchPhase::Moved,
|
/// do: *"Not 'a `PanelPointer` was emitted' — the observable effect
|
||||||
})
|
/// on the panel's viewport."* The defect it guards is exactly "the
|
||||||
|
/// frontend emits and the receiver discards", and a row that counts
|
||||||
|
/// emitted events reproduces that blind spot rather than catching
|
||||||
|
/// it — it would pass if the vertical axis emitted a horizontal
|
||||||
|
/// gesture, if the receiver dropped it, or if some other event
|
||||||
|
/// accompanied a sub-threshold delta.
|
||||||
|
///
|
||||||
|
/// So this row runs both halves. The **producer** is this
|
||||||
|
/// frontend's `apply_wheel`, reached through
|
||||||
|
/// `dispatch_window_event`. The **receiver** is a real
|
||||||
|
/// `pmacs::editor::EditorState` with a live panel window, driven
|
||||||
|
/// through `classify_panel_pointer` + `apply_panel_pointer`, the
|
||||||
|
/// pair the daemon itself calls. The assertion is the panel
|
||||||
|
/// window's `(view_top, view_left)`.
|
||||||
|
///
|
||||||
|
/// Per axis: a first sub-threshold delta moves the viewport by
|
||||||
|
/// **nothing** and puts **nothing** on the wire, and the delta that
|
||||||
|
/// completes the notch moves it by **exactly one step** — once, not
|
||||||
|
/// twice, and not the sum of everything banked.
|
||||||
|
///
|
||||||
|
/// *Mutations: round the notch instead of banking it → the
|
||||||
|
/// sub-threshold legs; bank into one accumulator per surface rather
|
||||||
|
/// than per (surface, axis) → the second axis's first leg, which
|
||||||
|
/// the first axis's leftover completes; drop `PKind::ScrollLeft` /
|
||||||
|
/// `ScrollRight` from the daemon's panel arm → the horizontal
|
||||||
|
/// completion, which no emission count can see.*
|
||||||
|
#[test]
|
||||||
|
fn step3_a_panel_wheel_moves_the_panel_viewport_once_per_notch_per_axis() {
|
||||||
|
use winit::event::{DeviceId, MouseScrollDelta, TouchPhase};
|
||||||
|
|
||||||
|
let (mut editor, fid, panel, panel_buffer) = panel_receiver();
|
||||||
|
let origin = |editor: &pmacs::editor::EditorState| {
|
||||||
|
editor
|
||||||
|
.window_view_origin_for_test(panel)
|
||||||
|
.expect("the panel window is live")
|
||||||
};
|
};
|
||||||
let gestures = |step: &Step| {
|
assert_eq!(origin(&editor), (0, 0), "setup: the panel starts home");
|
||||||
|
|
||||||
|
let mut h = panel_producer(panel_buffer);
|
||||||
|
|
||||||
|
// One turn of the wheel, carried all the way through: the
|
||||||
|
// frontend's events are replayed into the editor exactly as the
|
||||||
|
// daemon replays them.
|
||||||
|
let turn =
|
||||||
|
|h: &mut EffectHarness, editor: &mut pmacs::editor::EditorState, dx: f32, dy: f32| {
|
||||||
|
let step = h.feed(&WindowEvent::MouseWheel {
|
||||||
|
device_id: DeviceId::dummy(),
|
||||||
|
delta: MouseScrollDelta::LineDelta(dx, -dy),
|
||||||
|
phase: TouchPhase::Moved,
|
||||||
|
});
|
||||||
|
let mut replayed = 0usize;
|
||||||
|
for event in &step.outbound {
|
||||||
|
if let pmacs_protocol::FrontendEvent::PanelPointer {
|
||||||
|
buffer_id,
|
||||||
|
coord,
|
||||||
|
kind,
|
||||||
|
mods,
|
||||||
|
..
|
||||||
|
} = event
|
||||||
|
{
|
||||||
|
let disposition =
|
||||||
|
editor.classify_panel_pointer(fid, *buffer_id, *coord, *kind);
|
||||||
|
editor.apply_panel_pointer(fid, &disposition, *coord, *kind, *mods);
|
||||||
|
replayed += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(step, replayed)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Vertical, sub-threshold: nothing on the wire, nothing moves.
|
||||||
|
let (step, replayed) = turn(&mut h, &mut editor, 0.0, 0.6);
|
||||||
|
assert!(
|
||||||
|
step.outbound.is_empty(),
|
||||||
|
"a sub-threshold vertical delta must put NOTHING on the \
|
||||||
|
wire, got {:?}",
|
||||||
step.outbound
|
step.outbound
|
||||||
.iter()
|
|
||||||
.filter(|e| {
|
|
||||||
matches!(
|
|
||||||
e,
|
|
||||||
pmacs_protocol::FrontendEvent::PanelPointer { .. }
|
|
||||||
| pmacs_protocol::FrontendEvent::PanelPointerMapped { .. }
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.count()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Vertical: 0.6 of a notch is not a notch.
|
|
||||||
let step = wheel(0.0, 0.6);
|
|
||||||
assert_eq!(
|
|
||||||
gestures(&step),
|
|
||||||
0,
|
|
||||||
"a sub-threshold vertical delta must reach the panel as \
|
|
||||||
nothing at all"
|
|
||||||
);
|
);
|
||||||
// Horizontal, before the vertical bank is completed: if the two
|
assert_eq!(replayed, 0);
|
||||||
// axes shared one accumulator, this 0.6 would finish the
|
|
||||||
// vertical 0.6 and fire here.
|
|
||||||
let step = wheel(0.6, 0.0);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
gestures(&step),
|
origin(&editor),
|
||||||
0,
|
(0, 0),
|
||||||
"and a sub-threshold horizontal delta must not be completed \
|
"and the panel viewport must not move"
|
||||||
by the vertical one banked before it"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Completing each axis produces exactly one gesture, not two,
|
// Horizontal, sub-threshold, with the vertical bank still
|
||||||
// and not the sum of everything banked so far.
|
// standing: one accumulator fed by both axes would complete
|
||||||
let step = wheel(0.0, 0.6);
|
// here and scroll.
|
||||||
assert_eq!(
|
let (step, _) = turn(&mut h, &mut editor, 0.6, 0.0);
|
||||||
gestures(&step),
|
assert!(
|
||||||
1,
|
step.outbound.is_empty(),
|
||||||
"0.6 + 0.6 is one vertical notch, and exactly one"
|
"a sub-threshold horizontal delta must not be completed by \
|
||||||
|
the vertical one banked before it, got {:?}",
|
||||||
|
step.outbound
|
||||||
);
|
);
|
||||||
let step = wheel(0.6, 0.0);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
gestures(&step),
|
origin(&editor),
|
||||||
1,
|
(0, 0),
|
||||||
"and the horizontal axis completes on its own count"
|
"and still nothing has moved on either axis"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Completing the vertical notch: the viewport moves ONE step
|
||||||
|
// down, and the horizontal origin stays put.
|
||||||
|
let (_, replayed) = turn(&mut h, &mut editor, 0.0, 0.6);
|
||||||
|
assert_eq!(replayed, 1, "one notch is one gesture");
|
||||||
|
let after_vertical = origin(&editor);
|
||||||
|
assert!(
|
||||||
|
after_vertical.0 > 0,
|
||||||
|
"the completed vertical notch must scroll the panel"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
after_vertical.1, 0,
|
||||||
|
"and must not move it sideways: a vertical notch that \
|
||||||
|
emitted a horizontal gesture would show up exactly here"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Completing the horizontal notch: sideways this time, and the
|
||||||
|
// vertical origin does not move again.
|
||||||
|
let (_, replayed) = turn(&mut h, &mut editor, 0.6, 0.0);
|
||||||
|
assert_eq!(replayed, 1, "one notch is one gesture");
|
||||||
|
let after_horizontal = origin(&editor);
|
||||||
|
assert!(
|
||||||
|
after_horizontal.1 > 0,
|
||||||
|
"the completed horizontal notch must scroll the panel \
|
||||||
|
sideways — the axis whose receiver arm did not exist before \
|
||||||
|
B2, and which no emission count can see"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
after_horizontal.0, after_vertical.0,
|
||||||
|
"and must not scroll it vertically a second time"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6416,58 +6416,9 @@ mod tests {
|
||||||
fid: FrontendId,
|
fid: FrontendId,
|
||||||
with_panel: bool,
|
with_panel: bool,
|
||||||
) -> (crate::window::WindowId, Option<crate::window::WindowId>) {
|
) -> (crate::window::WindowId, Option<crate::window::WindowId>) {
|
||||||
use crate::window::{FrontendView, Layout, LayoutNode, Orientation, Window, WindowParams};
|
// One fixture, shared with `pmacs-gpu`'s step-3 effect witness,
|
||||||
|
// which needs a real panel window to observe an effect on.
|
||||||
let mut core = editor.core.borrow_mut();
|
editor.install_panel_view_for_test(fid, with_panel)
|
||||||
let doc_buf = core.active_window().buffer_id;
|
|
||||||
let document = crate::window::WindowId::next();
|
|
||||||
let doc_view = {
|
|
||||||
let reg = core.registry.borrow();
|
|
||||||
crate::text_view::TextView::new(reg.get(doc_buf).expect("doc"))
|
|
||||||
};
|
|
||||||
core.windows
|
|
||||||
.insert(document, Window::new(document, doc_buf, doc_view));
|
|
||||||
let panel = with_panel.then(|| {
|
|
||||||
let panel_buf = core.registry.borrow_mut().create("*panel*");
|
|
||||||
let panel_id = crate::window::WindowId::next();
|
|
||||||
let panel_view = {
|
|
||||||
let reg = core.registry.borrow();
|
|
||||||
crate::text_view::TextView::new(reg.get(panel_buf).expect("panel"))
|
|
||||||
};
|
|
||||||
let mut window = Window::new(panel_id, panel_buf, panel_view);
|
|
||||||
let mut params = WindowParams::default();
|
|
||||||
params.side = Some(crate::window::Side::Bottom);
|
|
||||||
params.fixed_rows = Some(4);
|
|
||||||
window.params = params;
|
|
||||||
core.windows.insert(panel_id, window);
|
|
||||||
panel_id
|
|
||||||
});
|
|
||||||
let layout = match panel {
|
|
||||||
Some(panel) => Layout {
|
|
||||||
root: LayoutNode::Split {
|
|
||||||
orientation: Orientation::Horizontal,
|
|
||||||
children: vec![LayoutNode::Leaf(document), LayoutNode::Leaf(panel)],
|
|
||||||
weights: vec![1, 1],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
None => Layout::single(document),
|
|
||||||
};
|
|
||||||
core.register_frontend_view(
|
|
||||||
fid,
|
|
||||||
FrontendView {
|
|
||||||
layout,
|
|
||||||
active: document,
|
|
||||||
fold_projection: false,
|
|
||||||
// Stage 2B-2 is dark: production negotiation still sets
|
|
||||||
// this `false` for every semantic session, so the
|
|
||||||
// projection is exercised through a test-only view (the
|
|
||||||
// framing's §7.2.2 posture).
|
|
||||||
panel_capable: true,
|
|
||||||
frame_geometry: None,
|
|
||||||
panel_hidden: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
(document, panel)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn session(version: u32, semantic: bool) -> crate::presence::SessionState {
|
fn session(version: u32, semantic: bool) -> crate::presence::SessionState {
|
||||||
|
|
|
||||||
116
src/editor.rs
116
src/editor.rs
|
|
@ -2956,6 +2956,122 @@ impl EditorState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Install a frontend view with a bottom panel, for tests that need
|
||||||
|
/// a real panel window to observe an effect on.
|
||||||
|
///
|
||||||
|
/// **Test support, not production.** It exists because the panel
|
||||||
|
/// receiver's effect is only observable against a live side window,
|
||||||
|
/// and `pmacs-gpu`'s step-3 witness has to see that effect rather
|
||||||
|
/// than the event it emits — the defect it guards is precisely
|
||||||
|
/// "the frontend emits and the receiver discards", which an
|
||||||
|
/// emission-only row reproduces instead of catching.
|
||||||
|
///
|
||||||
|
/// Returns `(document, panel)`.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn install_panel_view_for_test(
|
||||||
|
&self,
|
||||||
|
frontend_id: FrontendId,
|
||||||
|
with_panel: bool,
|
||||||
|
) -> (WindowId, Option<WindowId>) {
|
||||||
|
use crate::window::{FrontendView, Layout, LayoutNode, Orientation, Window, WindowParams};
|
||||||
|
|
||||||
|
let mut core = self.core.borrow_mut();
|
||||||
|
let doc_buf = core.active_window().buffer_id;
|
||||||
|
let document = WindowId::next();
|
||||||
|
let doc_view = {
|
||||||
|
let reg = core.registry.borrow();
|
||||||
|
crate::text_view::TextView::new(reg.get(doc_buf).expect("doc"))
|
||||||
|
};
|
||||||
|
core.windows
|
||||||
|
.insert(document, Window::new(document, doc_buf, doc_view));
|
||||||
|
let panel = with_panel.then(|| {
|
||||||
|
let panel_buf = core.registry.borrow_mut().create("*panel*");
|
||||||
|
let panel_id = WindowId::next();
|
||||||
|
let panel_view = {
|
||||||
|
let reg = core.registry.borrow();
|
||||||
|
crate::text_view::TextView::new(reg.get(panel_buf).expect("panel"))
|
||||||
|
};
|
||||||
|
let mut window = Window::new(panel_id, panel_buf, panel_view);
|
||||||
|
let mut params = WindowParams::default();
|
||||||
|
params.side = Some(crate::window::Side::Bottom);
|
||||||
|
params.fixed_rows = Some(4);
|
||||||
|
window.params = params;
|
||||||
|
core.windows.insert(panel_id, window);
|
||||||
|
panel_id
|
||||||
|
});
|
||||||
|
let layout = match panel {
|
||||||
|
Some(panel) => Layout {
|
||||||
|
root: LayoutNode::Split {
|
||||||
|
orientation: Orientation::Horizontal,
|
||||||
|
children: vec![LayoutNode::Leaf(document), LayoutNode::Leaf(panel)],
|
||||||
|
weights: vec![1, 1],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
None => Layout::single(document),
|
||||||
|
};
|
||||||
|
core.register_frontend_view(
|
||||||
|
frontend_id,
|
||||||
|
FrontendView {
|
||||||
|
layout,
|
||||||
|
active: document,
|
||||||
|
fold_projection: false,
|
||||||
|
panel_capable: true,
|
||||||
|
frame_geometry: None,
|
||||||
|
panel_hidden: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
(document, panel)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replace a window's buffer contents, so a test can give a panel
|
||||||
|
/// something to scroll. Without it the `*panel*` buffer is empty
|
||||||
|
/// and every scroll clamps to zero — a viewport row against it
|
||||||
|
/// would measure nothing.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn seed_window_buffer_for_test(&self, win_id: WindowId, text: &str) {
|
||||||
|
let core = self.core.borrow();
|
||||||
|
let Some(buffer_id) = core.windows.get(&win_id).map(|w| w.buffer_id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let registry = core.registry.clone();
|
||||||
|
drop(core);
|
||||||
|
{
|
||||||
|
let mut reg = registry.borrow_mut();
|
||||||
|
if let Ok(buf) = reg.get_mut(buffer_id) {
|
||||||
|
let _ = buf.set_generated_contents(text.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// **And rebuild the window's view.** `TextView` caches the line
|
||||||
|
// partition it was built with, and `scroll_window` reads its
|
||||||
|
// `line_count` — so a window left holding the pre-seed view
|
||||||
|
// clamps every scroll to zero and a viewport row against it
|
||||||
|
// measures nothing.
|
||||||
|
let view = {
|
||||||
|
let reg = registry.borrow();
|
||||||
|
reg.get(buffer_id).ok().map(crate::text_view::TextView::new)
|
||||||
|
};
|
||||||
|
if let Some(view) = view
|
||||||
|
&& let Some(window) = self.core.borrow_mut().windows.get_mut(&win_id)
|
||||||
|
{
|
||||||
|
window.text_view = view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A window's viewport origin, `(view_top, view_left)` — the pair a
|
||||||
|
/// panel-wheel effect moves.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn window_view_origin_for_test(&self, win_id: WindowId) -> Option<(usize, u32)> {
|
||||||
|
let core = self.core.borrow();
|
||||||
|
let window = core.windows.get(&win_id)?;
|
||||||
|
Some((window.view_top, window.view_left))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The buffer a window is showing.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn window_buffer_for_test(&self, win_id: WindowId) -> Option<crate::buffer::BufferId> {
|
||||||
|
Some(self.core.borrow().windows.get(&win_id)?.buffer_id)
|
||||||
|
}
|
||||||
|
|
||||||
/// Classify an authenticated panel gesture, WITHOUT applying it
|
/// Classify an authenticated panel gesture, WITHOUT applying it
|
||||||
/// (Q#BP-R4).
|
/// (Q#BP-R4).
|
||||||
///
|
///
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue