fix(gui-1b): the fourth replacement path, and a probe open at one end
The replacement census was taken by recall and was short. `kill_buffer` rebinds every window showing the doomed buffer to a fallback, resetting cursor, selection and view_top a line at a time --- with the horizontal origin missing from that list exactly as the other three had it. Retaken by grepping every write of a window's buffer_id rather than by listing the paths someone had thought of. Four production sites: switch_active_buffer_for, install_buffer_in_window, kill_buffer's fallback rebind, and the daemon's align_primary_document_window. EditorCore::from_bytes also assigns one and is deliberately excluded --- a fresh core has no prior origin to inherit --- and the helper's doc now names that exclusion so the next census need not re-decide it. The remaining seven buffer_id writes are test code. L8e covers the new site and fires only on its own call's removal. And L4's caret probe was open at one end. `pos_to_display(..).is_some()` proves the caret is not LEFT of the viewport, which is the condition deciding whether the vertical wheel carries point --- but a caret past the RIGHT edge also returns Some, and there a normal follow moves the origin. That is precisely the state in which the row's claim, that the latch is its only discriminator, would be false. The probe now requires `col < last_content_cols`. Verified by walking the caret 380 columns right: the setup assertion fires, where `is_some()` alone let it pass. Gates: fmt; clippy --workspace --all-targets -D warnings; --lib 2008; --lib --features crdt 2201; pmacs-gpu 312; git diff --check.
This commit is contained in:
parent
0ce3544bf0
commit
deb3f0b101
|
|
@ -10670,11 +10670,16 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
/// Whether the caret is inside the window's horizontal viewport,
|
||||
/// asked the way production asks it: `pos_to_display` returns
|
||||
/// `None` for a position LEFT of the edge (Q#HS7(c′)), which is the
|
||||
/// exact condition that decides whether a vertical wheel can carry
|
||||
/// point at all.
|
||||
/// Whether the caret is inside the window's horizontal viewport —
|
||||
/// **both edges**.
|
||||
///
|
||||
/// `pos_to_display` returns `None` for a position LEFT of the edge
|
||||
/// (Q#HS7(c′)), which is the condition deciding whether a vertical
|
||||
/// wheel can carry point at all. On its own that is only half the
|
||||
/// question: a caret past the RIGHT edge still returns `Some`, and
|
||||
/// there a normal follow would move the origin — so a row claiming
|
||||
/// the latch is its only discriminator would be wrong in exactly
|
||||
/// the way `is_some()` cannot see. The column bound closes it.
|
||||
fn caret_inside_viewport(s: &EditorState) -> bool {
|
||||
let core = s.core.borrow();
|
||||
let win = core.active_window();
|
||||
|
|
@ -10683,7 +10688,7 @@ mod tests {
|
|||
let buf = reg.get(win.buffer_id).expect("live buffer");
|
||||
win.text_view
|
||||
.pos_to_display(buf, win.cursor, win.layout_ctx())
|
||||
.is_some()
|
||||
.is_some_and(|coord| coord.col < win.last_content_cols)
|
||||
}
|
||||
|
||||
/// The state every L-row starts from: a real sideways wheel gesture
|
||||
|
|
@ -10846,11 +10851,12 @@ mod tests {
|
|||
);
|
||||
assert!(
|
||||
caret_inside_viewport(&s),
|
||||
"setup: and it must still be INSIDE the viewport afterwards. \
|
||||
A caret that landed on a short line would clamp to that \
|
||||
line's end, left of the origin — and then the origin would \
|
||||
discriminate too, so the claim below about the latch being \
|
||||
the only discriminator would be false"
|
||||
"setup: and it must still be INSIDE the viewport afterwards, \
|
||||
on BOTH edges. Past either one a normal follow would move \
|
||||
the origin — left, if the caret clamped to a short line's \
|
||||
end; right, if it ran off the far side — and then the \
|
||||
origin would discriminate too, making the claim below \
|
||||
about the latch being the only discriminator false"
|
||||
);
|
||||
paint_once(&s, term_size_24x80());
|
||||
|
||||
|
|
@ -11104,6 +11110,46 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
/// L8, replacement leg — **killing the displayed buffer** rebinds
|
||||
/// the window to a fallback, which is a replacement like any other.
|
||||
///
|
||||
/// This path was missed by the first census, which listed the
|
||||
/// replacement sites someone had thought of rather than the ones a
|
||||
/// grep for `buffer_id` writes turns up. It resets cursor,
|
||||
/// selection and `view_top` a line at a time, exactly like the
|
||||
/// other two in this file, and had the horizontal origin missing
|
||||
/// from the same list.
|
||||
///
|
||||
/// *Mutation: drop the `forget_manual_horizontal_origin()` call
|
||||
/// from `kill_buffer`'s fallback rebind → this row.*
|
||||
#[test]
|
||||
fn l8e_killing_the_displayed_buffer_clears_the_origin_and_the_latch() {
|
||||
let (s, _) = scrolled_sideways();
|
||||
let doomed = s.core.borrow().active_window().buffer_id;
|
||||
|
||||
s.core
|
||||
.borrow_mut()
|
||||
.kill_buffer(doomed)
|
||||
.expect("the scratch buffer stands as a fallback");
|
||||
|
||||
assert_ne!(
|
||||
s.core.borrow().active_window().buffer_id,
|
||||
doomed,
|
||||
"setup: the window must actually have been rebound, else \
|
||||
this row measures nothing"
|
||||
);
|
||||
assert_eq!(
|
||||
s.core.borrow().active_window().view_left,
|
||||
0,
|
||||
"a fallback rebind must not inherit the dead buffer's \
|
||||
sideways viewport"
|
||||
);
|
||||
assert!(
|
||||
!s.core.borrow().active_window().manual_left_authority,
|
||||
"nor the authority defending it"
|
||||
);
|
||||
}
|
||||
|
||||
/// L8, replacement leg — the same for **`install_buffer_in_window`**,
|
||||
/// the path that targets an explicit window rather than the active
|
||||
/// one.
|
||||
|
|
|
|||
|
|
@ -5416,6 +5416,7 @@ impl EditorCore {
|
|||
win.cursor = 0;
|
||||
win.selection = None;
|
||||
win.view_top = 0;
|
||||
win.forget_manual_horizontal_origin();
|
||||
win.goal_col = None;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -499,9 +499,25 @@ impl Window {
|
|||
/// symptom — and the TUI's three replacement paths had neither the
|
||||
/// origin reset nor the latch clear.
|
||||
///
|
||||
/// One helper rather than three copies, so a fourth replacement
|
||||
/// One helper rather than a copy per site, so a new replacement
|
||||
/// path gets the rule by calling it; each **call site** stays
|
||||
/// individually removable, which is what keeps its own row honest.
|
||||
///
|
||||
/// **The census, taken by grepping every write of a window's
|
||||
/// `buffer_id` rather than by recalling which paths exist** — an
|
||||
/// earlier version of this doc said "three" and was wrong, because
|
||||
/// it listed the paths someone had thought of. Four production
|
||||
/// sites rebind a live window to a different buffer:
|
||||
/// `EditorCore::switch_active_buffer_for`,
|
||||
/// `EditorCore::install_buffer_in_window`,
|
||||
/// `EditorCore::kill_buffer`'s fallback rebind, and the daemon's
|
||||
/// `align_primary_document_window`. Each has its own row
|
||||
/// (L8b–L8e).
|
||||
///
|
||||
/// `EditorCore::from_bytes` also assigns `buffer_id`, and is
|
||||
/// **deliberately not on that list**: it builds a fresh core whose
|
||||
/// window has no prior origin to inherit. Named here so the next
|
||||
/// census does not have to re-decide it.
|
||||
pub fn forget_manual_horizontal_origin(&mut self) {
|
||||
self.view_left = 0;
|
||||
self.manual_left_authority = false;
|
||||
|
|
|
|||
Loading…
Reference in New Issue