Open multiple definitions in a multibuffer instead of opening the files directly

Kay Simmons created

Change summary

crates/editor/src/editor.rs | 147 +++++++++++++++++++++++++-------------
1 file changed, 97 insertions(+), 50 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -62,7 +62,7 @@ pub use multi_buffer::{
 };
 use multi_buffer::{MultiBufferChunks, ToOffsetUtf16};
 use ordered_float::OrderedFloat;
-use project::{FormatTrigger, LocationLink, Project, ProjectPath, ProjectTransaction};
+use project::{FormatTrigger, Location, LocationLink, Project, ProjectPath, ProjectTransaction};
 use scroll::{
     autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide,
 };
@@ -5012,13 +5012,15 @@ impl Editor {
         cx: &mut ViewContext<Workspace>,
     ) {
         let pane = workspace.active_pane().clone();
-        for definition in definitions {
+        // If there is one definition, just open it directly
+        if let [definition] = definitions.as_slice() {
             let range = definition
                 .target
                 .range
                 .to_offset(definition.target.buffer.read(cx));
 
-            let target_editor_handle = workspace.open_project_item(definition.target.buffer, cx);
+            let target_editor_handle =
+                workspace.open_project_item(definition.target.buffer.clone(), cx);
             target_editor_handle.update(cx, |target_editor, cx| {
                 // When selecting a definition in a different buffer, disable the nav history
                 // to avoid creating a history entry at the previous cursor location.
@@ -5031,6 +5033,28 @@ impl Editor {
 
                 pane.update(cx, |pane, _| pane.enable_history());
             });
+        } else {
+            let replica_id = editor_handle.read(cx).replica_id(cx);
+            let title = definitions
+                .iter()
+                .find(|definition| definition.origin.is_some())
+                .and_then(|definition| {
+                    definition.origin.as_ref().map(|origin| {
+                        let buffer = origin.buffer.read(cx);
+                        format!(
+                            "Definitions for {}",
+                            buffer
+                                .text_for_range(origin.range.clone())
+                                .collect::<String>()
+                        )
+                    })
+                })
+                .unwrap_or("Definitions".to_owned());
+            let locations = definitions
+                .into_iter()
+                .map(|definition| definition.target)
+                .collect();
+            Self::open_locations_in_multibuffer(workspace, locations, replica_id, title, cx)
         }
     }
 
@@ -5051,64 +5075,87 @@ impl Editor {
         let project = workspace.project().clone();
         let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
         Some(cx.spawn(|workspace, mut cx| async move {
-            let mut locations = references.await?;
+            let locations = references.await?;
             if locations.is_empty() {
                 return Ok(());
             }
 
-            locations.sort_by_key(|location| location.buffer.id());
-            let mut locations = locations.into_iter().peekable();
-            let mut ranges_to_highlight = Vec::new();
-
-            let excerpt_buffer = cx.add_model(|cx| {
-                let mut symbol_name = None;
-                let mut multibuffer = MultiBuffer::new(replica_id);
-                while let Some(location) = locations.next() {
-                    let buffer = location.buffer.read(cx);
-                    let mut ranges_for_buffer = Vec::new();
-                    let range = location.range.to_offset(buffer);
-                    ranges_for_buffer.push(range.clone());
-                    if symbol_name.is_none() {
-                        symbol_name = Some(buffer.text_for_range(range).collect::<String>());
-                    }
-
-                    while let Some(next_location) = locations.peek() {
-                        if next_location.buffer == location.buffer {
-                            ranges_for_buffer.push(next_location.range.to_offset(buffer));
-                            locations.next();
-                        } else {
-                            break;
-                        }
-                    }
-
-                    ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
-                    ranges_to_highlight.extend(multibuffer.push_excerpts_with_context_lines(
-                        location.buffer.clone(),
-                        ranges_for_buffer,
-                        1,
-                        cx,
-                    ));
-                }
-                multibuffer.with_title(format!("References to `{}`", symbol_name.unwrap()))
-            });
-
             workspace.update(&mut cx, |workspace, cx| {
-                let editor =
-                    cx.add_view(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), cx));
-                editor.update(cx, |editor, cx| {
-                    editor.highlight_background::<Self>(
-                        ranges_to_highlight,
-                        |theme| theme.editor.highlighted_line_background,
-                        cx,
-                    );
-                });
-                workspace.add_item(Box::new(editor), cx);
+                let title = locations
+                    .first()
+                    .as_ref()
+                    .map(|location| {
+                        let buffer = location.buffer.read(cx);
+                        format!(
+                            "References to `{}`",
+                            buffer
+                                .text_for_range(location.range.clone())
+                                .collect::<String>()
+                        )
+                    })
+                    .unwrap();
+                Self::open_locations_in_multibuffer(workspace, locations, replica_id, title, cx);
             });
 
             Ok(())
         }))
     }
 
+    /// Opens a multibuffer with the given project locations in it
+    pub fn open_locations_in_multibuffer(
+        workspace: &mut Workspace,
+        mut locations: Vec<Location>,
+        replica_id: ReplicaId,
+        title: String,
+        cx: &mut ViewContext<Workspace>,
+    ) {
+        // If there are multiple definitions, open them in a multibuffer
+        locations.sort_by_key(|location| location.buffer.id());
+        let mut locations = locations.into_iter().peekable();
+        let mut ranges_to_highlight = Vec::new();
+
+        let excerpt_buffer = cx.add_model(|cx| {
+            let mut multibuffer = MultiBuffer::new(replica_id);
+            while let Some(location) = locations.next() {
+                let buffer = location.buffer.read(cx);
+                let mut ranges_for_buffer = Vec::new();
+                let range = location.range.to_offset(buffer);
+                ranges_for_buffer.push(range.clone());
+
+                while let Some(next_location) = locations.peek() {
+                    if next_location.buffer == location.buffer {
+                        ranges_for_buffer.push(next_location.range.to_offset(buffer));
+                        locations.next();
+                    } else {
+                        break;
+                    }
+                }
+
+                ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
+                ranges_to_highlight.extend(multibuffer.push_excerpts_with_context_lines(
+                    location.buffer.clone(),
+                    ranges_for_buffer,
+                    1,
+                    cx,
+                ))
+            }
+
+            multibuffer.with_title(title)
+        });
+
+        let editor = cx.add_view(|cx| {
+            Editor::for_multibuffer(excerpt_buffer, Some(workspace.project().clone()), cx)
+        });
+        editor.update(cx, |editor, cx| {
+            editor.highlight_background::<Self>(
+                ranges_to_highlight,
+                |theme| theme.editor.highlighted_line_background,
+                cx,
+            );
+        });
+        workspace.add_item(Box::new(editor), cx);
+    }
+
     pub fn rename(&mut self, _: &Rename, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
         use language::ToOffset as _;