Display a rudimentary project diagnostic view on `alt-shift-d`

Antonio Scandurra created

Change summary

Cargo.lock                            |   1 
crates/diagnostics/src/diagnostics.rs | 125 ++++++++++++++++++++++------
crates/zed/Cargo.toml                 |   1 
crates/zed/src/main.rs                |   1 
4 files changed, 101 insertions(+), 27 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -5716,6 +5716,7 @@ dependencies = [
  "contacts_panel",
  "crossbeam-channel",
  "ctor",
+ "diagnostics",
  "dirs",
  "easy-parallel",
  "editor",

crates/diagnostics/src/diagnostics.rs 🔗

@@ -1,23 +1,32 @@
 use collections::HashMap;
 use editor::{Editor, ExcerptProperties, MultiBuffer};
-use gpui::{elements::*, Entity, ModelHandle, RenderContext, View, ViewContext, ViewHandle};
+use gpui::{
+    action, elements::*, keymap::Binding, AppContext, Entity, ModelContext, ModelHandle,
+    MutableAppContext, RenderContext, View, ViewContext, ViewHandle,
+};
 use language::Point;
 use postage::watch;
 use project::Project;
+use workspace::Workspace;
+
+action!(Toggle);
+
+pub fn init(cx: &mut MutableAppContext) {
+    cx.add_bindings([Binding::new("alt-shift-D", Toggle, None)]);
+    cx.add_action(ProjectDiagnosticsEditor::toggle);
+}
 
 struct ProjectDiagnostics {
-    editor: ViewHandle<Editor>,
+    excerpts: ModelHandle<MultiBuffer>,
     project: ModelHandle<Project>,
 }
 
-impl ProjectDiagnostics {
-    fn new(
-        project: ModelHandle<Project>,
-        settings: watch::Receiver<workspace::Settings>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
-        let buffer = cx.add_model(|cx| MultiBuffer::new(project.read(cx).replica_id(cx)));
+struct ProjectDiagnosticsEditor {
+    editor: ViewHandle<Editor>,
+}
 
+impl ProjectDiagnostics {
+    fn new(project: ModelHandle<Project>, cx: &mut ModelContext<Self>) -> Self {
         let project_paths = project
             .read(cx)
             .diagnostic_summaries(cx)
@@ -27,7 +36,6 @@ impl ProjectDiagnostics {
         cx.spawn(|this, mut cx| {
             let project = project.clone();
             async move {
-                let mut excerpts = Vec::new();
                 for project_path in project_paths {
                     let buffer = project
                         .update(&mut cx, |project, cx| project.open_buffer(project_path, cx))
@@ -48,14 +56,20 @@ impl ProjectDiagnostics {
                         grouped_diagnostics.into_values().collect::<Vec<_>>();
                     sorted_diagnostic_groups.sort_by_key(|group| group.0);
 
-                    let mut prev_end_row = None;
-                    let mut pending_excerpt = None;
                     for diagnostic in snapshot.all_diagnostics::<Point>() {
-                        excerpts.push(ExcerptProperties {
-                            buffer: &buffer,
-                            range: todo!(),
-                            header_height: todo!(),
-                        });
+                        this.update(&mut cx, |this, cx| {
+                            this.excerpts.update(cx, |excerpts, cx| {
+                                excerpts.push_excerpt(
+                                    ExcerptProperties {
+                                        buffer: &buffer,
+                                        range: diagnostic.range,
+                                        header_height: 1,
+                                    },
+                                    cx,
+                                );
+                                cx.notify();
+                            });
+                        })
                     }
                 }
                 Result::Ok::<_, anyhow::Error>(())
@@ -64,13 +78,7 @@ impl ProjectDiagnostics {
         .detach();
 
         Self {
-            editor: cx.add_view(|cx| {
-                Editor::for_buffer(
-                    buffer.clone(),
-                    editor::settings_builder(buffer.downgrade(), settings),
-                    cx,
-                )
-            }),
+            excerpts: cx.add_model(|cx| MultiBuffer::new(project.read(cx).replica_id(cx))),
             project,
         }
     }
@@ -80,12 +88,75 @@ impl Entity for ProjectDiagnostics {
     type Event = ();
 }
 
-impl View for ProjectDiagnostics {
+impl Entity for ProjectDiagnosticsEditor {
+    type Event = ();
+}
+
+impl View for ProjectDiagnosticsEditor {
     fn ui_name() -> &'static str {
-        "ProjectDiagnostics"
+        "ProjectDiagnosticsEditor"
     }
 
-    fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
         ChildView::new(self.editor.id()).boxed()
     }
 }
+
+impl ProjectDiagnosticsEditor {
+    fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
+        dbg!("HEY!!!!");
+        let diagnostics =
+            cx.add_model(|cx| ProjectDiagnostics::new(workspace.project().clone(), cx));
+        workspace.add_item(diagnostics, cx);
+    }
+}
+
+impl workspace::Item for ProjectDiagnostics {
+    type View = ProjectDiagnosticsEditor;
+
+    fn build_view(
+        handle: ModelHandle<Self>,
+        settings: watch::Receiver<workspace::Settings>,
+        cx: &mut ViewContext<Self::View>,
+    ) -> Self::View {
+        let excerpts = handle.read(cx).excerpts.clone();
+        let editor = cx.add_view(|cx| {
+            Editor::for_buffer(
+                excerpts.clone(),
+                editor::settings_builder(excerpts.downgrade(), settings),
+                cx,
+            )
+        });
+        ProjectDiagnosticsEditor { editor }
+    }
+
+    fn project_path(&self) -> Option<project::ProjectPath> {
+        None
+    }
+}
+
+impl workspace::ItemView for ProjectDiagnosticsEditor {
+    fn title(&self, _: &AppContext) -> String {
+        "Project Diagnostics".to_string()
+    }
+
+    fn project_path(&self, cx: &AppContext) -> Option<project::ProjectPath> {
+        None
+    }
+
+    fn save(
+        &mut self,
+        cx: &mut ViewContext<Self>,
+    ) -> anyhow::Result<gpui::Task<anyhow::Result<()>>> {
+        todo!()
+    }
+
+    fn save_as(
+        &mut self,
+        worktree: ModelHandle<project::Worktree>,
+        path: &std::path::Path,
+        cx: &mut ViewContext<Self>,
+    ) -> gpui::Task<anyhow::Result<()>> {
+        todo!()
+    }
+}

crates/zed/Cargo.toml 🔗

@@ -32,6 +32,7 @@ text = { path = "../text" }
 chat_panel = { path = "../chat_panel" }
 client = { path = "../client" }
 clock = { path = "../clock" }
+diagnostics = { path = "../diagnostics" }
 fsevent = { path = "../fsevent" }
 fuzzy = { path = "../fuzzy" }
 editor = { path = "../editor" }

crates/zed/src/main.rs 🔗

@@ -59,6 +59,7 @@ fn main() {
         contacts_panel::init(cx);
         chat_panel::init(cx);
         project_panel::init(cx);
+        diagnostics::init(cx);
 
         let app_state = Arc::new(AppState {
             languages: languages.clone(),