From d1430e30544495c5928cc074a9c68f7835d2014a Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Thu, 15 Oct 2026 03:56:27 +0200 Subject: [PATCH] Keep text selection highlight inside the box at the end of the text Fixes #365 Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Opus 5.5 --- crates/text/src/layout.rs | 3 ++- crates/text/src/tests.rs | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/crates/text/src/layout.rs b/crates/text/src/layout.rs index 110c7589..b5b617d0 100644 --- a/crates/text/src/layout.rs +++ b/crates/text/src/layout.rs @@ -1213,7 +1213,8 @@ impl TextLayout { } let (x0, _) = self.caret(a.max(line.char_start)); let x1 = if b >= line.char_end { - self.line_end_x(line) + if line.para_end && b > line.char_end.saturating_sub(1) { 4.0 } else { 0.0 } + // The last line has no paragraph break to show, so it ends at its glyphs. + self.line_end_x(line) + if line.para_end && li + 1 < self.lines.len() && b > line.char_end.saturating_sub(1) { 4.0 } else { 0.0 } } else { self.caret(b).0 }; diff --git a/crates/text/src/tests.rs b/crates/text/src/tests.rs index 526ae0b0..e34f0110 100644 --- a/crates/text/src/tests.rs +++ b/crates/text/src/tests.rs @@ -416,3 +416,24 @@ fn arrows_move_visually_through_right_to_left_runs() { // Plain left-to-right lines step by char (the caller's default). assert!(layout(&TextData::new("plain"), None, None).visual_step(1, true).is_none()); } + +#[test] +fn selection_through_text_end_stays_inside_auto_width_box() { + if !have_fonts() { + return; + } + for size in [12.0, 24.0, 96.0] { + let mut t = TextData::new("Hello world"); + t.style.font_size = size; + let l = layout(&t, None, None); + let n = "Hello world".chars().count(); + let right = l.selection_rects(0, n).last().map_or(f64::NAN, |r| r.x1); + assert!(right <= l.width.ceil(), "size {size}: right {right} > ceil(width) {}", l.width.ceil()); + } + // A selected paragraph break still shows past the end of its line. + let t = TextData::new("Hello\nworld"); + let l = layout(&t, None, None); + let r = l.selection_rects(0, 6); + let first = r.first().map_or(f64::NAN, |r| r.x1); + assert!(first > l.caret(5).0 + 1.0, "{first}"); +}