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,
|
/// Whether the caret is inside the window's horizontal viewport —
|
||||||
/// asked the way production asks it: `pos_to_display` returns
|
/// **both edges**.
|
||||||
/// `None` for a position LEFT of the edge (Q#HS7(c′)), which is the
|
///
|
||||||
/// exact condition that decides whether a vertical wheel can carry
|
/// `pos_to_display` returns `None` for a position LEFT of the edge
|
||||||
/// point at all.
|
/// (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 {
|
fn caret_inside_viewport(s: &EditorState) -> bool {
|
||||||
let core = s.core.borrow();
|
let core = s.core.borrow();
|
||||||
let win = core.active_window();
|
let win = core.active_window();
|
||||||
|
|
@ -10683,7 +10688,7 @@ mod tests {
|
||||||
let buf = reg.get(win.buffer_id).expect("live buffer");
|
let buf = reg.get(win.buffer_id).expect("live buffer");
|
||||||
win.text_view
|
win.text_view
|
||||||
.pos_to_display(buf, win.cursor, win.layout_ctx())
|
.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
|
/// The state every L-row starts from: a real sideways wheel gesture
|
||||||
|
|
@ -10846,11 +10851,12 @@ mod tests {
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
caret_inside_viewport(&s),
|
caret_inside_viewport(&s),
|
||||||
"setup: and it must still be INSIDE the viewport afterwards. \
|
"setup: and it must still be INSIDE the viewport afterwards, \
|
||||||
A caret that landed on a short line would clamp to that \
|
on BOTH edges. Past either one a normal follow would move \
|
||||||
line's end, left of the origin — and then the origin would \
|
the origin — left, if the caret clamped to a short line's \
|
||||||
discriminate too, so the claim below about the latch being \
|
end; right, if it ran off the far side — and then the \
|
||||||
the only discriminator would be false"
|
origin would discriminate too, making the claim below \
|
||||||
|
about the latch being the only discriminator false"
|
||||||
);
|
);
|
||||||
paint_once(&s, term_size_24x80());
|
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`**,
|
/// L8, replacement leg — the same for **`install_buffer_in_window`**,
|
||||||
/// the path that targets an explicit window rather than the active
|
/// the path that targets an explicit window rather than the active
|
||||||
/// one.
|
/// one.
|
||||||
|
|
|
||||||
|
|
@ -5416,6 +5416,7 @@ impl EditorCore {
|
||||||
win.cursor = 0;
|
win.cursor = 0;
|
||||||
win.selection = None;
|
win.selection = None;
|
||||||
win.view_top = 0;
|
win.view_top = 0;
|
||||||
|
win.forget_manual_horizontal_origin();
|
||||||
win.goal_col = None;
|
win.goal_col = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -499,9 +499,25 @@ impl Window {
|
||||||
/// symptom — and the TUI's three replacement paths had neither the
|
/// symptom — and the TUI's three replacement paths had neither the
|
||||||
/// origin reset nor the latch clear.
|
/// 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
|
/// path gets the rule by calling it; each **call site** stays
|
||||||
/// individually removable, which is what keeps its own row honest.
|
/// 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) {
|
pub fn forget_manual_horizontal_origin(&mut self) {
|
||||||
self.view_left = 0;
|
self.view_left = 0;
|
||||||
self.manual_left_authority = false;
|
self.manual_left_authority = false;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue