diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 62b481bc530..77d33ca2489 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -1038,7 +1038,7 @@ impl StateInner { let mut rendered_focused_item = false; let available_item_space = size( - available_width.map_or(AvailableSpace::MinContent, |width| { + available_width.map_or(AvailableSpace::MaxContent, |width| { AvailableSpace::Definite(width) }), AvailableSpace::MinContent, diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index b64f65a37c5..3470ac94e27 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -618,6 +618,7 @@ struct TextLayoutInner { lines: SmallVec<[WrappedLine; 1]>, line_height: Pixels, wrap_width: Option, + truncate_width: Option, size: Option>, bounds: Option>, } @@ -680,16 +681,20 @@ impl TextLayout { // 2. wrap_width matches (or both are None) // 3. truncate_width is None (if truncate_width is Some, we need to re-layout // because the previous layout may have been computed without truncation) + // 4. the cached layout was not truncated (a truncated layout answers an + // unconstrained probe with the truncated size, which poisons intrinsic + // sizing with whatever width some earlier measure pass happened to use) if let Some(text_layout) = element_state.0.borrow().as_ref() && let Some(size) = text_layout.size && (wrap_width.is_none() || wrap_width == text_layout.wrap_width) && truncate_width.is_none() + && text_layout.truncate_width.is_none() { return size; } let mut line_wrapper = cx.text_system().line_wrapper(text_style.font(), font_size); - let (text, runs) = if truncate_width.is_some() { + let (text, runs) = if let Some(truncate_width) = truncate_width { if let Some(max_lines) = text_style.line_clamp && let Some(wrap_width) = wrap_width { @@ -701,10 +706,25 @@ impl TextLayout { &runs, truncate_from, ) + } else if let Some(unclipped) = window + .text_system() + .shape_text(text.clone(), font_size, &runs, None, None) + .log_err() + && unclipped + .iter() + .all(|line| line.size(line_height).width <= truncate_width) + { + // The truncation decision below sums per-character advances, + // which overestimates the shaped width (no kerning), truncating + // text that fits exactly in its measured width. Skip truncation + // whenever the honestly-shaped text fits; the shaping result + // comes from the line layout cache when the same text was + // already measured untruncated this frame. + (text.clone(), Cow::Borrowed(&*runs)) } else { line_wrapper.truncate_line( text.clone(), - truncate_width.unwrap_or(Pixels::MAX), + truncate_width, &truncation_affix, &runs, truncate_from, @@ -731,6 +751,7 @@ impl TextLayout { len: 0, line_height, wrap_width, + truncate_width, size: Some(Size::default()), bounds: None, }); @@ -749,6 +770,7 @@ impl TextLayout { len, line_height, wrap_width, + truncate_width, size: Some(size), bounds: None, }); diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index 0a3314573f0..94ac0021ec8 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -671,7 +671,7 @@ impl UniformList { return Size::default(); }; let available_space = size( - list_width.map_or(AvailableSpace::MinContent, |width| { + list_width.map_or(AvailableSpace::MaxContent, |width| { AvailableSpace::Definite(width) }), AvailableSpace::MinContent,