user: fix text selection on wrapped lines

scroll_pane: screen_to_item() now properly accounts for wrapped
lines using textwrap to compute actual character positions instead
of just using mouse_x directly.

selectable: new module with PUA markers for wrap-aware selection.
Not yet integrated into chat.rs but ready for future use. Uses
continuation markers to track logical vs visual lines.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-12 15:49:57 -04:00
parent ab0f16a3b5
commit dcd647764c
5 changed files with 632 additions and 1 deletions

View file

@ -106,7 +106,27 @@ impl ScrollPaneState {
let h = self.heights.get(line_idx).copied().unwrap_or(1) as i32;
if (mouse_y as i32) < row + h {
let line_text: String = lines[line_idx].spans.iter().map(|s| s.content.as_ref()).collect();
let col = (mouse_x as usize).min(line_text.len());
// Which visual row within this wrapped line?
let visual_row_in_item = ((mouse_y as i32) - row).max(0) as usize;
// Use textwrap to find actual break positions
let wrap_width = self.cached_width as usize;
let wrapped = textwrap::wrap(&line_text, wrap_width);
// Sum lengths of previous wrapped rows to get char offset base
let char_base: usize = wrapped.iter()
.take(visual_row_in_item)
.map(|s| s.len())
.sum();
// Add mouse x position within current row
let current_row_len = wrapped.get(visual_row_in_item)
.map(|s| s.len())
.unwrap_or(0);
let col = char_base + (mouse_x as usize).min(current_row_len);
let col = col.min(line_text.len());
return Some((line_idx, col));
}
row += h;