Skip to content

Commit bdcbe6f

Browse files
committed
nit
1 parent a8941be commit bdcbe6f

3 files changed

Lines changed: 62 additions & 10 deletions

File tree

crates/warpui_core/src/elements/tui/text.rs

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ enum TuiTextOverflow {
4747
Clip,
4848
Ellipsis,
4949
}
50+
#[derive(Clone, Copy)]
51+
struct TuiTextMeasurement {
52+
available_width: u16,
53+
natural_size: TuiSize,
54+
}
5055

5156
pub struct TuiText {
5257
/// Styled runs that concatenate into the full text. Runs may contain hard
@@ -56,6 +61,7 @@ pub struct TuiText {
5661
style: TuiStyle,
5762
wrap: bool,
5863
overflow: TuiTextOverflow,
64+
cached_measurement: Option<TuiTextMeasurement>,
5965
size: Option<TuiSize>,
6066
origin: Option<TuiScreenPoint>,
6167
}
@@ -75,6 +81,7 @@ impl TuiText {
7581
style: TuiStyle::default(),
7682
wrap: true,
7783
overflow: TuiTextOverflow::default(),
84+
cached_measurement: None,
7885
size: None,
7986
origin: None,
8087
}
@@ -88,13 +95,15 @@ impl TuiText {
8895
/// Lays each hard line out as a single (clipped) row instead of wrapping.
8996
pub fn truncate(mut self) -> Self {
9097
self.wrap = false;
98+
self.cached_measurement = None;
9199
self
92100
}
93101
/// Truncates each hard line at grapheme boundaries and appends `...`
94102
/// inside the width supplied during layout.
95103
pub fn truncate_with_ellipsis(mut self) -> Self {
96104
self.wrap = false;
97105
self.overflow = TuiTextOverflow::Ellipsis;
106+
self.cached_measurement = None;
98107
self
99108
}
100109

@@ -262,18 +271,30 @@ impl TuiElement for TuiText {
262271
_ctx: &mut TuiLayoutContext,
263272
_app: &AppContext,
264273
) -> TuiSize {
265-
let size = if self.is_empty() {
266-
constraint.clamp(TuiSize::ZERO)
274+
let width = constraint.max.width;
275+
let natural_size = if self.is_empty() {
276+
TuiSize::ZERO
277+
} else if let Some(measurement) = self
278+
.cached_measurement
279+
.filter(|measurement| measurement.available_width == width)
280+
{
281+
measurement.natural_size
267282
} else {
268283
let paragraph = self.paragraph(constraint.max.width);
269284
let height =
270285
u16::try_from(paragraph.line_count(constraint.max.width)).unwrap_or(u16::MAX);
271286
let content_width = u16::try_from(paragraph.line_width()).unwrap_or(u16::MAX);
272-
TuiSize::new(
273-
constraint.constrain_width(content_width),
274-
constraint.constrain_height(height),
275-
)
287+
let size = TuiSize::new(content_width, height);
288+
self.cached_measurement = Some(TuiTextMeasurement {
289+
available_width: width,
290+
natural_size: size,
291+
});
292+
size
276293
};
294+
let size = TuiSize::new(
295+
constraint.constrain_width(natural_size.width),
296+
constraint.constrain_height(natural_size.height),
297+
);
277298
self.size = Some(size);
278299
size
279300
}

crates/warpui_core/src/elements/tui/text_tests.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,34 @@ fn layout_reports_content_width_and_row_count() {
6969
});
7070
}
7171

72+
#[test]
73+
fn truncation_invalidates_a_cached_wrapped_measurement() {
74+
App::test((), |app| async move {
75+
app.read(|app_ctx| {
76+
let constraint = TuiConstraint::loose(TuiSize::new(5, 10));
77+
for truncate in [
78+
TuiText::truncate as fn(TuiText) -> TuiText,
79+
TuiText::truncate_with_ellipsis,
80+
] {
81+
let mut text = TuiText::new("hello world");
82+
let mut rendered_views = EntityIdMap::default();
83+
let mut ctx = TuiLayoutContext {
84+
rendered_views: &mut rendered_views,
85+
};
86+
assert_eq!(
87+
text.layout(constraint, &mut ctx, app_ctx),
88+
TuiSize::new(5, 2)
89+
);
90+
91+
text = truncate(text);
92+
assert_eq!(
93+
text.layout(constraint, &mut ctx, app_ctx),
94+
TuiSize::new(5, 1)
95+
);
96+
}
97+
});
98+
});
99+
}
72100
#[test]
73101
fn word_wraps_at_the_width_boundary() {
74102
let text = TuiText::new("hello world foo");

crates/warpui_core/src/elements/tui/viewported_list.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,12 @@ where
147147
let Some(resolved) = self.state.resolved_viewport() else {
148148
return;
149149
};
150+
let selection_is_valid = selection.validate_width(size.width);
151+
let selection_range = selection_is_valid.then(|| selection.range()).flatten();
152+
if selection_range.is_none() && !self.trim_selection_line_ends {
153+
self.selection_snapshot.borrow_mut().take();
154+
return;
155+
}
150156
let visible_height = size.height.saturating_sub(resolved.screen_offset).min(
151157
resolved
152158
.content_height
@@ -171,10 +177,7 @@ where
171177
.collect::<Vec<_>>()
172178
});
173179
*self.selection_snapshot.borrow_mut() = Some((resolved, snapshot));
174-
if !selection.validate_width(size.width) {
175-
return;
176-
}
177-
let Some(range) = selection.range() else {
180+
let Some(range) = selection_range else {
178181
return;
179182
};
180183
let viewport_bottom = resolved.window.scroll_top.saturating_add(usize::from(

0 commit comments

Comments
 (0)