gpui: Assert validity of text runs for `StyleText` (#39581)

Lukas Wirth created

Should help with figuring out the char boundary panic in text shaping on
windows

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Change summary

crates/file_finder/src/open_path_prompt.rs  |  4 ++--
crates/gpui/src/elements/text.rs            | 20 +++++++++++++++++---
crates/gpui/src/text_system/line_wrapper.rs |  6 +-----
3 files changed, 20 insertions(+), 10 deletions(-)

Detailed changes

crates/file_finder/src/open_path_prompt.rs 🔗

@@ -755,7 +755,7 @@ impl PickerDelegate for OpenPathDelegate {
                                     .with_default_highlights(
                                         &window.text_style(),
                                         vec![(
-                                            delta..delta + label_len,
+                                            delta..label_len,
                                             HighlightStyle::color(Color::Conflict.color(cx)),
                                         )],
                                     )
@@ -765,7 +765,7 @@ impl PickerDelegate for OpenPathDelegate {
                                     .with_default_highlights(
                                         &window.text_style(),
                                         vec![(
-                                            delta..delta + label_len,
+                                            delta..label_len,
                                             HighlightStyle::color(Color::Created.color(cx)),
                                         )],
                                     )

crates/gpui/src/elements/text.rs 🔗

@@ -180,8 +180,7 @@ impl StyledText {
             "Can't use `with_default_highlights` and `with_highlights`"
         );
         let runs = Self::compute_runs(&self.text, default_style, highlights);
-        self.runs = Some(runs);
-        self
+        self.with_runs(runs)
     }
 
     /// Set the styling attributes for the given text, as well as
@@ -194,7 +193,15 @@ impl StyledText {
             self.runs.is_none(),
             "Can't use `with_highlights` and `with_default_highlights`"
         );
-        self.delayed_highlights = Some(highlights.into_iter().collect::<Vec<_>>());
+        self.delayed_highlights = Some(
+            highlights
+                .into_iter()
+                .inspect(|(run, _)| {
+                    debug_assert!(self.text.is_char_boundary(run.start));
+                    debug_assert!(self.text.is_char_boundary(run.end));
+                })
+                .collect::<Vec<_>>(),
+        );
         self
     }
 
@@ -207,8 +214,10 @@ impl StyledText {
         let mut ix = 0;
         for (range, highlight) in highlights {
             if ix < range.start {
+                debug_assert!(text.is_char_boundary(range.start));
                 runs.push(default_style.clone().to_run(range.start - ix));
             }
+            debug_assert!(text.is_char_boundary(range.end));
             runs.push(
                 default_style
                     .clone()
@@ -225,6 +234,11 @@ impl StyledText {
 
     /// Set the text runs for this piece of text.
     pub fn with_runs(mut self, runs: Vec<TextRun>) -> Self {
+        let mut text = &**self.text;
+        for run in &runs {
+            text = text.get(run.len..).expect("invalid text run");
+        }
+        assert!(text.is_empty(), "invalid text run");
         self.runs = Some(runs);
         self
     }

crates/gpui/src/text_system/line_wrapper.rs 🔗

@@ -225,19 +225,15 @@ impl LineWrapper {
 
 fn update_runs_after_truncation(result: &str, ellipsis: &str, runs: &mut Vec<TextRun>) {
     let mut truncate_at = result.len() - ellipsis.len();
-    let mut run_end = None;
     for (run_index, run) in runs.iter_mut().enumerate() {
         if run.len <= truncate_at {
             truncate_at -= run.len;
         } else {
             run.len = truncate_at + ellipsis.len();
-            run_end = Some(run_index + 1);
+            runs.truncate(run_index + 1);
             break;
         }
     }
-    if let Some(run_end) = run_end {
-        runs.truncate(run_end);
-    }
 }
 
 /// A fragment of a line that can be wrapped.