fix(text): the caret after a line break sits on the next line

`caret` put the index right after a line break (a newline, or a line
separator) at the end of the line above, so after Enter the caret showed
on the previous line until something was typed, and Up/Down counted
lines from there. The index before the break stays at the end of its
line; the one after it is now on the next line, as after a wrap.
Selection highlights already started such lines at their own start
(#665); their comment no longer describes the old caret.
This commit is contained in:
AganzeFelicite
2026-10-19 12:12:54 +02:00
parent 9e4dcc9204
commit 1b66fec3ec
2 changed files with 20 additions and 9 deletions
+4 -9
View File
@@ -1171,16 +1171,12 @@ impl TextLayout {
pub fn caret(&self, index: usize) -> (f64, usize) {
for (li, line) in self.lines.iter().enumerate() {
let last_line = li + 1 == self.lines.len();
// A caret at a line's end belongs to the next line, whether the line wraps or ends with a
// line break (before the break it is still on this line: past its glyphs).
let in_line = index >= line.char_start && (index < line.char_end || (last_line && index <= line.char_end));
// A caret at the end of a soft-wrapped line belongs to the next line.
if !(in_line || index == line.char_end && line.break_after && !last_line) {
if !in_line {
continue;
}
if index == line.char_end && line.break_after && !last_line {
// Before the line break: end of this line.
let x = self.line_end_x(line);
return (x, li);
}
let gl = self.glyphs.get(line.glyph_start..line.glyph_end).unwrap_or(&[]);
for g in gl {
if g.char_end == g.char_start {
@@ -1303,8 +1299,7 @@ impl TextLayout {
out.extend(Self::mixed_selection(gl, a, b, line));
continue;
}
// From the line's own start: `caret(char_start)` after a hard break is the end of the
// line above.
// From the line's own start.
let x0 = if a <= line.char_start { self.line_start_x(line) } else { self.caret(a).0 };
let x1 = if b >= line.char_end {
// The last line has no paragraph break to show, so it ends at its glyphs.
+16
View File
@@ -481,3 +481,19 @@ fn selection_after_a_line_break_starts_at_the_line_start() {
// A break followed by text still doesn't highlight the next line's start.
assert!(rect_on(&l, &l.selection_rects(0, 6), 1).is_none());
}
#[test]
fn the_caret_after_a_line_break_sits_on_the_next_line() {
if !have_fonts() {
return;
}
// Before the break: the end of its line; after it: the start of the next (after Enter, the
// caret shows on the new line, and Up/Down count from there).
let l = layout(&TextData::new("ab\ncd"), None, None);
assert_eq!(l.caret(2).1, 0);
assert!(l.caret(2).0 > 0.0);
assert_eq!(l.caret(3), (0.0, 1));
// A trailing break: the caret sits on the empty last line.
let l = layout(&TextData::new("ab\n"), None, None);
assert_eq!(l.caret(3), (0.0, 1));
}