Make "go to definition" work in project diagnostics

Antonio Scandurra and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/diagnostics/src/diagnostics.rs | 22 +++++++++++++---
crates/editor/src/editor.rs           | 17 ++++++++----
crates/editor/src/items.rs            |  4 +-
crates/go_to_line/src/go_to_line.rs   | 18 +++++--------
crates/journal/src/journal.rs         |  2 
crates/outline/src/outline.rs         |  2 
crates/workspace/src/pane.rs          |  2 
crates/workspace/src/workspace.rs     | 38 +++++++++++++++++++++++++++-
crates/zed/src/zed.rs                 | 11 +------
9 files changed, 79 insertions(+), 37 deletions(-)

Detailed changes

crates/diagnostics/src/diagnostics.rs 🔗

@@ -9,13 +9,13 @@ use editor::{
     Autoscroll, BuildSettings, Editor, ExcerptId, ExcerptProperties, MultiBuffer, ToOffset,
 };
 use gpui::{
-    action, elements::*, keymap::Binding, AppContext, Entity, ModelHandle, MutableAppContext,
-    RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+    action, elements::*, keymap::Binding, AnyViewHandle, AppContext, Entity, ModelHandle,
+    MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
 };
 use language::{Bias, Buffer, Diagnostic, DiagnosticEntry, Point, Selection, SelectionGoal};
 use postage::watch;
 use project::{Project, ProjectPath};
-use std::{cmp::Ordering, mem, ops::Range, path::PathBuf, rc::Rc, sync::Arc};
+use std::{any::TypeId, cmp::Ordering, mem, ops::Range, path::PathBuf, rc::Rc, sync::Arc};
 use util::TryFutureExt;
 use workspace::{NavHistory, Workspace};
 
@@ -194,7 +194,6 @@ impl ProjectDiagnosticsEditor {
                     }
                     let editor = workspace
                         .open_item(buffer, cx)
-                        .to_any()
                         .downcast::<Editor>()
                         .unwrap();
                     editor.update(cx, |editor, cx| {
@@ -595,6 +594,21 @@ impl workspace::ItemView for ProjectDiagnosticsEditor {
             cx,
         ))
     }
+
+    fn act_as_type(
+        &self,
+        type_id: TypeId,
+        self_handle: &ViewHandle<Self>,
+        _: &AppContext,
+    ) -> Option<AnyViewHandle> {
+        if type_id == TypeId::of::<Self>() {
+            Some(self_handle.into())
+        } else if type_id == TypeId::of::<Editor>() {
+            Some((&self.editor).into())
+        } else {
+            None
+        }
+    }
 }
 
 fn path_header_renderer(buffer: ModelHandle<Buffer>, build_settings: BuildSettings) -> RenderBlock {

crates/editor/src/editor.rs 🔗

@@ -2992,11 +2992,17 @@ impl Editor {
         _: &GoToDefinition,
         cx: &mut ViewContext<Workspace>,
     ) {
-        let editor = workspace
-            .active_item(cx)
-            .and_then(|item| item.to_any().downcast::<Self>())
-            .unwrap()
-            .read(cx);
+        let active_item = workspace.active_item(cx);
+        let editor = if let Some(editor) = active_item
+            .as_ref()
+            .and_then(|item| item.act_as::<Self>(cx))
+        {
+            editor
+        } else {
+            return;
+        };
+
+        let editor = editor.read(cx);
         let buffer = editor.buffer.read(cx);
         let head = editor.newest_selection::<usize>(&buffer.read(cx)).head();
         let (buffer, head) = editor.buffer.read(cx).text_anchor_for_position(head, cx);
@@ -3012,7 +3018,6 @@ impl Editor {
                         .to_offset(definition.target_buffer.read(cx));
                     let target_editor = workspace
                         .open_item(BufferItemHandle(definition.target_buffer), cx)
-                        .to_any()
                         .downcast::<Self>()
                         .unwrap();
                     target_editor.update(cx, |target_editor, cx| {

crates/editor/src/items.rs 🔗

@@ -279,7 +279,7 @@ impl StatusItemView for CursorPosition {
         active_pane_item: Option<&dyn ItemViewHandle>,
         cx: &mut ViewContext<Self>,
     ) {
-        if let Some(editor) = active_pane_item.and_then(|item| item.to_any().downcast::<Editor>()) {
+        if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
             self._observe_active_editor = Some(cx.observe(&editor, Self::update_position));
             self.update_position(editor, cx);
         } else {
@@ -365,7 +365,7 @@ impl StatusItemView for DiagnosticMessage {
         active_pane_item: Option<&dyn ItemViewHandle>,
         cx: &mut ViewContext<Self>,
     ) {
-        if let Some(editor) = active_pane_item.and_then(|item| item.to_any().downcast::<Editor>()) {
+        if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
             self._observe_active_editor = Some(cx.observe(&editor, Self::update));
             self.update(editor, cx);
         } else {

crates/go_to_line/src/go_to_line.rs 🔗

@@ -80,17 +80,13 @@ impl GoToLine {
     }
 
     fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
-        workspace.toggle_modal(cx, |cx, workspace| {
-            let editor = workspace
-                .active_item(cx)
-                .unwrap()
-                .to_any()
-                .downcast::<Editor>()
-                .unwrap();
-            let view = cx.add_view(|cx| GoToLine::new(editor, workspace.settings.clone(), cx));
-            cx.subscribe(&view, Self::on_event).detach();
-            view
-        });
+        if let Some(editor) = workspace.active_item(cx).unwrap().downcast::<Editor>() {
+            workspace.toggle_modal(cx, |cx, workspace| {
+                let view = cx.add_view(|cx| GoToLine::new(editor, workspace.settings.clone(), cx));
+                cx.subscribe(&view, Self::on_event).detach();
+                view
+            });
+        }
     }
 
     fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {

crates/journal/src/journal.rs 🔗

@@ -55,7 +55,7 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
                 .await;
 
             if let Some(Some(Ok(item))) = opened.first() {
-                if let Some(editor) = item.to_any().downcast::<Editor>() {
+                if let Some(editor) = item.downcast::<Editor>() {
                     editor.update(&mut cx, |editor, cx| {
                         let len = editor.buffer().read(cx).read(cx).len();
                         editor.select_ranges([len..len], Some(Autoscroll::Center), cx);

crates/outline/src/outline.rs 🔗

@@ -144,7 +144,7 @@ impl OutlineView {
     fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
         if let Some(editor) = workspace
             .active_item(cx)
-            .and_then(|item| item.to_any().downcast::<Editor>())
+            .and_then(|item| item.downcast::<Editor>())
         {
             let settings = workspace.settings();
             let buffer = editor

crates/workspace/src/pane.rs 🔗

@@ -351,7 +351,7 @@ impl Pane {
 
     fn focus_active_item(&mut self, cx: &mut ViewContext<Self>) {
         if let Some(active_item) = self.active_item() {
-            cx.focus(active_item.to_any());
+            cx.focus(active_item);
         }
     }
 

crates/workspace/src/workspace.rs 🔗

@@ -33,7 +33,7 @@ use sidebar::{Side, Sidebar, SidebarItemId, ToggleSidebarItem, ToggleSidebarItem
 use status_bar::StatusBar;
 pub use status_bar::StatusItemView;
 use std::{
-    any::Any,
+    any::{Any, TypeId},
     future::Future,
     hash::{Hash, Hasher},
     path::{Path, PathBuf},
@@ -185,6 +185,18 @@ pub trait ItemView: View {
     fn should_update_tab_on_event(_: &Self::Event) -> bool {
         false
     }
+    fn act_as_type(
+        &self,
+        type_id: TypeId,
+        self_handle: &ViewHandle<Self>,
+        _: &AppContext,
+    ) -> Option<AnyViewHandle> {
+        if TypeId::of::<Self>() == type_id {
+            Some(self_handle.into())
+        } else {
+            None
+        }
+    }
 }
 
 pub trait ItemHandle: Send + Sync {
@@ -207,7 +219,7 @@ pub trait WeakItemHandle {
     fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
 }
 
-pub trait ItemViewHandle {
+pub trait ItemViewHandle: 'static {
     fn item_handle(&self, cx: &AppContext) -> Box<dyn ItemHandle>;
     fn title(&self, cx: &AppContext) -> String;
     fn project_entry(&self, cx: &AppContext) -> Option<ProjectEntry>;
@@ -229,6 +241,7 @@ pub trait ItemViewHandle {
         abs_path: PathBuf,
         cx: &mut MutableAppContext,
     ) -> Task<Result<()>>;
+    fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle>;
 }
 
 pub trait WeakItemViewHandle {
@@ -326,6 +339,17 @@ impl PartialEq for Box<dyn WeakItemHandle> {
 
 impl Eq for Box<dyn WeakItemHandle> {}
 
+impl dyn ItemViewHandle {
+    pub fn downcast<T: View>(&self) -> Option<ViewHandle<T>> {
+        self.to_any().downcast()
+    }
+
+    pub fn act_as<T: View>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
+        self.act_as_type(TypeId::of::<T>(), cx)
+            .and_then(|t| t.downcast())
+    }
+}
+
 impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
     fn item_handle(&self, cx: &AppContext) -> Box<dyn ItemHandle> {
         Box::new(self.read(cx).item_handle(cx))
@@ -413,6 +437,16 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
     fn can_save_as(&self, cx: &AppContext) -> bool {
         self.read(cx).can_save_as(cx)
     }
+
+    fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle> {
+        self.read(cx).act_as_type(type_id, self, cx)
+    }
+}
+
+impl Into<AnyViewHandle> for Box<dyn ItemViewHandle> {
+    fn into(self) -> AnyViewHandle {
+        self.to_any()
+    }
 }
 
 impl Clone for Box<dyn ItemViewHandle> {

crates/zed/src/zed.rs 🔗

@@ -205,7 +205,6 @@ mod tests {
             workspace
                 .active_item(cx)
                 .unwrap()
-                .to_any()
                 .downcast::<editor::Editor>()
                 .unwrap()
         });
@@ -451,7 +450,7 @@ mod tests {
         let editor = cx.read(|cx| {
             let pane = workspace.read(cx).active_pane().read(cx);
             let item = pane.active_item().unwrap();
-            item.to_any().downcast::<Editor>().unwrap()
+            item.downcast::<Editor>().unwrap()
         });
 
         cx.update(|cx| {
@@ -504,7 +503,6 @@ mod tests {
             workspace
                 .active_item(cx)
                 .unwrap()
-                .to_any()
                 .downcast::<Editor>()
                 .unwrap()
         });
@@ -573,7 +571,6 @@ mod tests {
             workspace
                 .active_item(cx)
                 .unwrap()
-                .to_any()
                 .downcast::<Editor>()
                 .unwrap()
         });
@@ -598,7 +595,6 @@ mod tests {
             workspace
                 .active_item(cx)
                 .unwrap()
-                .to_any()
                 .downcast::<Editor>()
                 .unwrap()
         });
@@ -738,7 +734,6 @@ mod tests {
             .update(&mut cx, |w, cx| w.open_path(file1.clone(), cx))
             .await
             .unwrap()
-            .to_any()
             .downcast::<Editor>()
             .unwrap();
         editor1.update(&mut cx, |editor, cx| {
@@ -748,14 +743,12 @@ mod tests {
             .update(&mut cx, |w, cx| w.open_path(file2.clone(), cx))
             .await
             .unwrap()
-            .to_any()
             .downcast::<Editor>()
             .unwrap();
         let editor3 = workspace
             .update(&mut cx, |w, cx| w.open_path(file3.clone(), cx))
             .await
             .unwrap()
-            .to_any()
             .downcast::<Editor>()
             .unwrap();
         editor3.update(&mut cx, |editor, cx| {
@@ -871,7 +864,7 @@ mod tests {
         ) -> (ProjectPath, DisplayPoint) {
             workspace.update(cx, |workspace, cx| {
                 let item = workspace.active_item(cx).unwrap();
-                let editor = item.to_any().downcast::<Editor>().unwrap();
+                let editor = item.downcast::<Editor>().unwrap();
                 let selections = editor.update(cx, |editor, cx| editor.selected_display_ranges(cx));
                 let path = workspace
                     .project()