Render a "Jump to Buffer" icon on all excerpt headers

Antonio Scandurra created

Change summary

crates/diagnostics/src/diagnostics.rs      | 64 ++--------------------
crates/editor/src/display_map/block_map.rs |  2 
crates/editor/src/editor.rs                |  4 +
crates/editor/src/element.rs               | 68 ++++++++++++++++++++++-
crates/language/src/buffer.rs              |  4 
crates/theme/src/theme.rs                  |  2 
styles/src/styleTree/editor.ts             | 16 ++--
7 files changed, 86 insertions(+), 74 deletions(-)

Detailed changes

crates/diagnostics/src/diagnostics.rs 🔗

@@ -9,13 +9,13 @@ use editor::{
     ToOffset,
 };
 use gpui::{
-    actions, elements::*, fonts::TextStyle, impl_internal_actions, platform::CursorStyle,
-    serde_json, AnyViewHandle, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext,
-    Task, View, ViewContext, ViewHandle, WeakViewHandle,
+    actions, elements::*, fonts::TextStyle, impl_internal_actions, serde_json, AnyViewHandle,
+    AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext,
+    ViewHandle, WeakViewHandle,
 };
 use language::{
     Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
-    SelectionGoal, ToPoint,
+    SelectionGoal,
 };
 use project::{DiagnosticSummary, Project, ProjectPath};
 use serde_json::json;
@@ -342,20 +342,13 @@ impl ProjectDiagnosticsEditor {
                                 is_first_excerpt_for_group = false;
                                 let mut primary =
                                     group.entries[group.primary_ix].diagnostic.clone();
-                                let anchor = group.entries[group.primary_ix].range.start;
-                                let position = anchor.to_point(&snapshot);
                                 primary.message =
                                     primary.message.split('\n').next().unwrap().to_string();
                                 group_state.block_count += 1;
                                 blocks_to_add.push(BlockProperties {
                                     position: header_position,
                                     height: 2,
-                                    render: diagnostic_header_renderer(
-                                        primary,
-                                        path.clone(),
-                                        position,
-                                        anchor,
-                                    ),
+                                    render: diagnostic_header_renderer(primary),
                                     disposition: BlockDisposition::Above,
                                 });
                             }
@@ -612,18 +605,10 @@ impl workspace::Item for ProjectDiagnosticsEditor {
     }
 }
 
-fn diagnostic_header_renderer(
-    diagnostic: Diagnostic,
-    path: ProjectPath,
-    position: Point,
-    anchor: Anchor,
-) -> RenderBlock {
-    enum JumpIcon {}
-
+fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
     let (message, highlights) = highlight_diagnostic_message(&diagnostic.message);
     Arc::new(move |cx| {
         let settings = cx.global::<Settings>();
-        let tooltip_style = settings.theme.tooltip.clone();
         let theme = &settings.theme.editor;
         let style = theme.diagnostic_header.clone();
         let font_size = (style.text_scale_factor * settings.buffer_font_size).round();
@@ -664,43 +649,6 @@ fn diagnostic_header_renderer(
                     .aligned()
                     .boxed()
             }))
-            .with_child(
-                MouseEventHandler::new::<JumpIcon, _, _>(diagnostic.group_id, cx, |state, _| {
-                    let style = style.jump_icon.style_for(state, false);
-                    Svg::new("icons/jump.svg")
-                        .with_color(style.color)
-                        .constrained()
-                        .with_width(style.icon_width)
-                        .aligned()
-                        .contained()
-                        .with_style(style.container)
-                        .constrained()
-                        .with_width(style.button_width)
-                        .with_height(style.button_width)
-                        .boxed()
-                })
-                .with_cursor_style(CursorStyle::PointingHand)
-                .on_click({
-                    let path = path.clone();
-                    move |_, _, cx| {
-                        cx.dispatch_action(Jump {
-                            path: path.clone(),
-                            position,
-                            anchor,
-                        });
-                    }
-                })
-                .with_tooltip(
-                    diagnostic.group_id,
-                    "Jump to diagnostic".to_string(),
-                    Some(Box::new(editor::OpenExcerpts)),
-                    tooltip_style,
-                    cx,
-                )
-                .aligned()
-                .flex_float()
-                .boxed(),
-            )
             .contained()
             .with_style(style.container)
             .with_padding_left(x_padding)

crates/editor/src/display_map/block_map.rs 🔗

@@ -97,6 +97,7 @@ struct Transform {
 pub enum TransformBlock {
     Custom(Arc<Block>),
     ExcerptHeader {
+        key: usize,
         buffer: BufferSnapshot,
         range: ExcerptRange<text::Anchor>,
         height: u8,
@@ -359,6 +360,7 @@ impl BlockMap {
                                 .from_point(Point::new(excerpt_boundary.row, 0), Bias::Left)
                                 .row(),
                             TransformBlock::ExcerptHeader {
+                                key: excerpt_boundary.key,
                                 buffer: excerpt_boundary.buffer,
                                 range: excerpt_boundary.range,
                                 height: if excerpt_boundary.starts_new_buffer {

crates/editor/src/editor.rs 🔗

@@ -5849,9 +5849,13 @@ impl Editor {
                 } else {
                     buffer.clip_point(position, Bias::Left)
                 };
+
+                let nav_history = editor.nav_history.take();
                 editor.change_selections(Some(Autoscroll::Newest), cx, |s| {
                     s.select_ranges([cursor..cursor]);
                 });
+                editor.nav_history = nav_history;
+
                 Some(())
             })?;
             Some(())

crates/editor/src/element.rs 🔗

@@ -27,6 +27,7 @@ use gpui::{
 };
 use json::json;
 use language::{Bias, DiagnosticSeverity, Selection};
+use project::ProjectPath;
 use settings::Settings;
 use smallvec::SmallVec;
 use std::{
@@ -795,6 +796,7 @@ impl EditorElement {
             return Default::default();
         };
 
+        let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
         let scroll_x = snapshot.scroll_position.x();
         snapshot
             .blocks_in_range(rows.clone())
@@ -827,10 +829,60 @@ impl EditorElement {
                         })
                     }
                     TransformBlock::ExcerptHeader {
+                        key,
                         buffer,
+                        range,
                         starts_new_buffer,
                         ..
                     } => {
+                        let jump_icon = project::File::from_dyn(buffer.file()).map(|file| {
+                            let jump_position = range
+                                .primary
+                                .as_ref()
+                                .map_or(range.context.start, |primary| primary.start);
+                            let jump_action = crate::Jump {
+                                path: ProjectPath {
+                                    worktree_id: file.worktree_id(cx),
+                                    path: file.path.clone(),
+                                },
+                                position: language::ToPoint::to_point(&jump_position, buffer),
+                                anchor: jump_position,
+                            };
+
+                            enum JumpIcon {}
+                            cx.render(&editor, |_, cx| {
+                                MouseEventHandler::new::<JumpIcon, _, _>(*key, cx, |state, _| {
+                                    let style = style.jump_icon.style_for(state, false);
+                                    Svg::new("icons/jump.svg")
+                                        .with_color(style.color)
+                                        .constrained()
+                                        .with_width(style.icon_width)
+                                        .aligned()
+                                        .contained()
+                                        .with_style(style.container)
+                                        .constrained()
+                                        .with_width(style.button_width)
+                                        .with_height(style.button_width)
+                                        .boxed()
+                                })
+                                .with_cursor_style(CursorStyle::PointingHand)
+                                .on_click({
+                                    move |_, _, cx| cx.dispatch_action(jump_action.clone())
+                                })
+                                .with_tooltip(
+                                    *key,
+                                    "Jump to Buffer".to_string(),
+                                    Some(Box::new(crate::OpenExcerpts)),
+                                    tooltip_style.clone(),
+                                    cx,
+                                )
+                                .aligned()
+                                .flex_float()
+                                .boxed()
+                            })
+                        });
+
+                        let padding = gutter_padding + scroll_x * em_width;
                         if *starts_new_buffer {
                             let style = &self.style.diagnostic_path_header;
                             let font_size =
@@ -854,6 +906,7 @@ impl EditorElement {
                                     )
                                     .contained()
                                     .with_style(style.filename.container)
+                                    .aligned()
                                     .boxed(),
                                 )
                                 .with_children(parent_path.map(|path| {
@@ -863,20 +916,25 @@ impl EditorElement {
                                     )
                                     .contained()
                                     .with_style(style.path.container)
+                                    .aligned()
                                     .boxed()
                                 }))
-                                .aligned()
-                                .left()
+                                .with_children(jump_icon)
                                 .contained()
                                 .with_style(style.container)
-                                .with_padding_left(gutter_padding + scroll_x * em_width)
+                                .with_padding_left(padding)
+                                .with_padding_right(padding)
                                 .expanded()
                                 .named("path header block")
                         } else {
                             let text_style = self.style.text.clone();
-                            Label::new("…".to_string(), text_style)
+                            Flex::row()
+                                .with_child(Label::new("…".to_string(), text_style).boxed())
+                                .with_children(jump_icon)
                                 .contained()
-                                .with_padding_left(gutter_padding + scroll_x * em_width)
+                                .with_padding_left(padding)
+                                .with_padding_right(padding)
+                                .expanded()
                                 .named("collapsed context")
                         }
                     }

crates/language/src/buffer.rs 🔗

@@ -1980,8 +1980,8 @@ impl BufferSnapshot {
         self.selections_update_count
     }
 
-    pub fn file(&self) -> Option<&Arc<dyn File>> {
-        self.file.as_ref()
+    pub fn file(&self) -> Option<&dyn File> {
+        self.file.as_deref()
     }
 
     pub fn file_update_count(&self) -> usize {

crates/theme/src/theme.rs 🔗

@@ -454,6 +454,7 @@ pub struct Editor {
     pub code_actions_indicator: Color,
     pub unnecessary_code_fade: f32,
     pub hover_popover: HoverPopover,
+    pub jump_icon: Interactive<IconButton>,
 }
 
 #[derive(Clone, Deserialize, Default)]
@@ -473,7 +474,6 @@ pub struct DiagnosticHeader {
     pub code: ContainedText,
     pub text_scale_factor: f32,
     pub icon_width_factor: f32,
-    pub jump_icon: Interactive<IconButton>,
 }
 
 #[derive(Clone, Deserialize, Default)]

styles/src/styleTree/editor.ts 🔗

@@ -101,14 +101,6 @@ export default function editor(theme: Theme) {
       background: backgroundColor(theme, 300),
       iconWidthFactor: 1.5,
       textScaleFactor: 0.857, // NateQ: Will we need dynamic sizing for text? If so let's create tokens for these.
-      jumpIcon: {
-        color: iconColor(theme, "primary"),
-        iconWidth: 10,
-        buttonWidth: 10,
-        hover: {
-          color: iconColor(theme, "active")
-        }
-      },
       border: border(theme, "secondary", {
         bottom: true,
         top: true,
@@ -147,6 +139,14 @@ export default function editor(theme: Theme) {
     invalidInformationDiagnostic: diagnostic(theme, "muted"),
     invalidWarningDiagnostic: diagnostic(theme, "muted"),
     hover_popover: hoverPopover(theme),
+    jumpIcon: {
+      color: iconColor(theme, "primary"),
+      iconWidth: 10,
+      buttonWidth: 10,
+      hover: {
+        color: iconColor(theme, "active")
+      }
+    },
     syntax,
   };
 }