From 6a8d7c99be5b9dcbb1e722d4a9863bfca80ed25a Mon Sep 17 00:00:00 2001 From: altsem Date: Thu, 13 Nov 2025 18:34:29 +0100 Subject: [PATCH] wip: wrap words Here's a sketch on how far i got into trying to make (at least) word-wrap a thing. Since it's just word-wrap, it won't deal with very long single- word lines still. (Should the layout module be able to split spans?). This still doesn't solve the problem if the Editor (src/screen/mod.rs) not being aware of wrapped lines. I think either we'll have to: - Feed data from the Layout back to the Editor. How tall is each Item? Is there a way to scroll the screen without recomputing from the top? Maybe keep an Item as anchor: "We are 7 lines down from Item 42" - Do wrapping separately from (or with the help of?) the Layout module. Keep each Item = 1 line. When the internal model is constructed, split long lines into multiple items. Make them behave as if 1 when selecting etc. --- src/screen/mod.rs | 23 ++--------------------- src/ui.rs | 20 ++++++++++++++++++-- src/ui/layout/mod.rs | 16 ++++++---------- 3 files changed, 26 insertions(+), 33 deletions(-) diff --git a/src/screen/mod.rs b/src/screen/mod.rs index 2e1e15fc50..5379d55030 100644 --- a/src/screen/mod.rs +++ b/src/screen/mod.rs @@ -399,27 +399,8 @@ pub(crate) fn layout_screen<'a>(layout: &mut UiTree<'a>, size: Size, screen: &'a let span_width = span.content.graphemes(true).count(); - if line_end + span_width >= size.width as usize { - // Truncate the span and insert an ellipsis to indicate overflow - let overflow = line_end + span_width - size.width as usize; - line_end = size.width as usize; - ui::layout_span( - layout, - ( - span.content - .graphemes(true) - .take(span_width.saturating_sub(overflow + 1)) - .collect::() - .into(), - style, - ), - ); - layout_span(layout, ("…".into(), bg)); - } else { - // Insert the span as normal - line_end += span_width; - ui::layout_span(layout, (span.content, style)); - } + line_end += span_width; + ui::layout_span(layout, (span.content, style)); }); // Add ellipsis indicator for collapsed sections diff --git a/src/ui.rs b/src/ui.rs index 5ea5be773c..ceb1e06a3a 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -111,8 +111,24 @@ pub(crate) fn layout_line<'a>(layout: &mut UiTree<'a>, line: Line<'a>) { } pub(crate) fn layout_span<'a>(layout: &mut UiTree<'a>, span: (Cow<'a, str>, Style)) { - let width = span.0.graphemes(true).count() as u16; - layout.leaf_with_size(span, [width, 1]); + match span.0 { + Cow::Borrowed(s) => { + for word in s.split_word_bounds() { + layout.leaf_with_size( + (Cow::Borrowed(word), span.1), + [word.graphemes(true).count() as u16, 1], + ); + } + } + Cow::Owned(s) => { + for word in s.split_word_bounds() { + layout.leaf_with_size( + (Cow::Owned(word.into()), span.1), + [word.graphemes(true).count() as u16, 1], + ); + } + } + } } pub(crate) fn repeat_chars(layout: &mut UiTree, count: usize, chars: &'static str, style: Style) { diff --git a/src/ui/layout/mod.rs b/src/ui/layout/mod.rs index 5d47f61216..aab9f02eb6 100644 --- a/src/ui/layout/mod.rs +++ b/src/ui/layout/mod.rs @@ -242,17 +242,13 @@ impl LayoutTree { child_data.pos = Some(start + cursor); } else { // Child doesn't fit where cursor currently is - // TODO Uncomment to include wrapping (tests below) - // // Try wrapping to next line/column first - // let next_line = size * dir.axis().flip(); + let next_line = size * dir.axis().flip(); - // if (next_line + child_data.size).fits(avail_size) { - // // Fits completely on next line - // cursor = next_line; - // child_data.pos = Some(start + cursor); - // } else - - if (cursor + Vec2(1, 1)).fits(avail_size) { + if (next_line + child_data.size).fits(avail_size) { + // Fits completely on next line + cursor = next_line; + child_data.pos = Some(start + cursor); + } else if (cursor + Vec2(1, 1)).fits(avail_size) { // Can't wrap, but we can fit at least one cell where the cursor currently is child_data.pos = Some(start + cursor); child_data.size = child_data.size.min(avail_size.saturating_sub(cursor));