Show only prefix/suffix if there are more than 12 breadcrumbs (#9220)

Antonio Scandurra created

Fixes https://github.com/zed-industries/zed/issues/9079

This should fix the arena panic we were observing. I saw that breadcrumb
rendering was on the stack trace for some of the panics, so my suspicion
is that it's being caused by some people navigating into deeply nested
files.

Release Notes:

- Fixed a panic that could occur when displaying too many breadcrumbs.
([#9079](https://github.com/zed-industries/zed/issues/9079))

Change summary

crates/breadcrumbs/src/breadcrumbs.rs | 21 +++++++++++++++++++--
1 file changed, 19 insertions(+), 2 deletions(-)

Detailed changes

crates/breadcrumbs/src/breadcrumbs.rs 🔗

@@ -4,10 +4,11 @@ use gpui::{
     ViewContext,
 };
 use itertools::Itertools;
+use std::cmp;
 use theme::ActiveTheme;
 use ui::{prelude::*, ButtonLike, ButtonStyle, Label, Tooltip};
 use workspace::{
-    item::{ItemEvent, ItemHandle},
+    item::{BreadcrumbText, ItemEvent, ItemHandle},
     ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
 };
 
@@ -31,14 +32,30 @@ impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
 
 impl Render for Breadcrumbs {
     fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        const MAX_SEGMENTS: usize = 12;
         let element = h_flex().text_ui();
         let Some(active_item) = self.active_item.as_ref() else {
             return element;
         };
-        let Some(segments) = active_item.breadcrumbs(cx.theme(), cx) else {
+        let Some(mut segments) = active_item.breadcrumbs(cx.theme(), cx) else {
             return element;
         };
 
+        let prefix_end_ix = cmp::min(segments.len(), MAX_SEGMENTS / 2);
+        let suffix_start_ix = cmp::max(
+            prefix_end_ix,
+            segments.len().saturating_sub(MAX_SEGMENTS / 2),
+        );
+        if suffix_start_ix > prefix_end_ix {
+            segments.splice(
+                prefix_end_ix..suffix_start_ix,
+                Some(BreadcrumbText {
+                    text: "⋯".into(),
+                    highlights: None,
+                }),
+            );
+        }
+
         let highlighted_segments = segments.into_iter().map(|segment| {
             let mut text_style = cx.text_style();
             text_style.color = Color::Muted.color(cx);