Render diagnostics view and such a bit more

Julia created

Change summary

Cargo.lock                                     |   1 
crates/command_palette2/src/command_palette.rs |   1 
crates/diagnostics2/src/diagnostics.rs         |  67 ++++++------
crates/diagnostics2/src/items.rs               |  43 ++++++-
crates/diagnostics2/src/toolbar_controls.rs    | 102 ++++---------------
crates/ui2/src/components/icon.rs              |   2 
crates/zed2/Cargo.toml                         |   2 
crates/zed2/src/main.rs                        |   1 
crates/zed2/src/zed2.rs                        |   8 
9 files changed, 100 insertions(+), 127 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -11474,6 +11474,7 @@ dependencies = [
  "copilot2",
  "ctor",
  "db2",
+ "diagnostics2",
  "editor2",
  "env_logger 0.9.3",
  "feature_flags2",

crates/command_palette2/src/command_palette.rs 🔗

@@ -116,6 +116,7 @@ impl Clone for Command {
         }
     }
 }
+
 /// Hit count for each command in the palette.
 /// We only account for commands triggered directly via command palette and not by e.g. keystrokes because
 /// if an user already knows a keystroke for a command, they are unlikely to use a command palette to look for it.

crates/diagnostics2/src/diagnostics.rs 🔗

@@ -14,8 +14,9 @@ use editor::{
 use futures::future::try_join_all;
 use gpui::{
     actions, div, AnyElement, AnyView, AppContext, Component, Context, Div, EventEmitter,
-    FocusHandle, InteractiveComponent, Model, ParentComponent, Render, SharedString, Styled,
-    Subscription, Task, View, ViewContext, VisualContext, WeakView,
+    FocusEvent, FocusHandle, Focusable, FocusableComponent, InteractiveComponent, Model,
+    ParentComponent, Render, SharedString, Styled, Subscription, Task, View, ViewContext,
+    VisualContext, WeakView,
 };
 use language::{
     Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
@@ -48,9 +49,8 @@ const CONTEXT_LINE_COUNT: u32 = 1;
 
 pub fn init(cx: &mut AppContext) {
     ProjectDiagnosticsSettings::register(cx);
-    // todo!()
-    // cx.add_action(ProjectDiagnosticsEditor::deploy);
-    // cx.add_action(ProjectDiagnosticsEditor::toggle_warnings);
+    cx.observe_new_views(ProjectDiagnosticsEditor::register)
+        .detach();
 }
 
 struct ProjectDiagnosticsEditor {
@@ -91,39 +91,34 @@ struct DiagnosticGroupState {
 impl EventEmitter<ItemEvent> for ProjectDiagnosticsEditor {}
 
 impl Render for ProjectDiagnosticsEditor {
-    type Element = Div<Self>;
+    type Element = Focusable<Self, Div<Self>>;
+
+    fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
+        let child = if self.path_states.is_empty() {
+            div()
+                .flex()
+                .items_center()
+                .justify_center()
+                .size_full()
+                .child(Label::new("No problems in workspace"))
+        } else {
+            div().size_full().child(self.editor.clone())
+        };
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-        div().size_full().bg(gpui::red())
+        div()
+            .track_focus(&self.focus_handle)
+            .size_full()
+            .on_focus_in(Self::focus_in)
+            .on_action(Self::toggle_warnings)
+            .child(child)
     }
 }
 
-// impl View for ProjectDiagnosticsEditor {
-//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-//         if self.path_states.is_empty() {
-//             let theme = &theme::current(cx).project_diagnostics;
-//             PaneBackdrop::new(
-//                 cx.view_id(),
-//                 Label::new("No problems in workspace", theme.empty_message.clone())
-//                     .aligned()
-//                     .contained()
-//                     .with_style(theme.container)
-//                     .into_any(),
-//             )
-//             .into_any()
-//         } else {
-//             ChildView::new(&self.editor, cx).into_any()
-//         }
-//     }
-
-//     fn focus_in(&mut self, _: AnyView, cx: &mut ViewContext<Self>) {
-//         if cx.is_self_focused() && !self.path_states.is_empty() {
-//             cx.focus(&self.editor);
-//         }
-//     }
-// }
-
 impl ProjectDiagnosticsEditor {
+    fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
+        workspace.register_action(Self::deploy);
+    }
+
     fn new(
         project_handle: Model<Project>,
         workspace: WeakView<Workspace>,
@@ -240,6 +235,12 @@ impl ProjectDiagnosticsEditor {
         cx.notify();
     }
 
+    fn focus_in(&mut self, _: &FocusEvent, cx: &mut ViewContext<Self>) {
+        if self.focus_handle.is_focused(cx) && !self.path_states.is_empty() {
+            self.editor.focus_handle(cx).focus(cx)
+        }
+    }
+
     fn update_excerpts(
         &mut self,
         language_server_id: Option<LanguageServerId>,

crates/diagnostics2/src/items.rs 🔗

@@ -1,20 +1,18 @@
 use collections::HashSet;
 use editor::{Editor, GoToDiagnostic};
 use gpui::{
-    div, serde_json, AppContext, CursorStyle, Div, Entity, EventEmitter, MouseButton, Render,
-    Styled, Subscription, View, ViewContext, WeakView,
+    div, serde_json, svg, AppContext, CursorStyle, Div, Entity, EventEmitter, InteractiveComponent,
+    MouseButton, ParentComponent, Render, Stateful, Styled, Subscription, Svg, View, ViewContext,
+    WeakView,
 };
 use language::Diagnostic;
 use lsp::LanguageServerId;
+use theme::ActiveTheme;
+use ui::{Icon, IconElement, Label, TextColor};
 use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace};
 
 use crate::ProjectDiagnosticsEditor;
 
-// todo!()
-// pub fn init(cx: &mut AppContext) {
-//     cx.add_action(DiagnosticIndicator::go_to_next_diagnostic);
-// }
-
 pub struct DiagnosticIndicator {
     summary: project::DiagnosticSummary,
     active_editor: Option<WeakView<Editor>>,
@@ -25,10 +23,33 @@ pub struct DiagnosticIndicator {
 }
 
 impl Render for DiagnosticIndicator {
-    type Element = Div<Self>;
+    type Element = Stateful<Self, Div<Self>>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-        div().size_full().bg(gpui::red())
+        let mut summary_row = div().flex().flex_row().size_full();
+
+        if self.summary.error_count > 0 {
+            summary_row =
+                summary_row.child(IconElement::new(Icon::XCircle).color(TextColor::Error));
+            summary_row = summary_row.child(Label::new(self.summary.error_count.to_string()));
+        }
+
+        if self.summary.warning_count > 0 {
+            summary_row = summary_row
+                .child(IconElement::new(Icon::ExclamationTriangle).color(TextColor::Warning));
+            summary_row = summary_row.child(Label::new(self.summary.warning_count.to_string()));
+        }
+
+        if self.summary.error_count == 0 && self.summary.warning_count == 0 {
+            summary_row =
+                summary_row.child(IconElement::new(Icon::Check).color(TextColor::Success));
+        }
+
+        div()
+            .id(cx.entity_id())
+            .on_action(Self::go_to_next_diagnostic)
+            .size_full()
+            .child(summary_row)
     }
 }
 
@@ -40,19 +61,23 @@ impl DiagnosticIndicator {
                 this.in_progress_checks.insert(*language_server_id);
                 cx.notify();
             }
+
             project::Event::DiskBasedDiagnosticsFinished { language_server_id }
             | project::Event::LanguageServerRemoved(language_server_id) => {
                 this.summary = project.read(cx).diagnostic_summary(cx);
                 this.in_progress_checks.remove(language_server_id);
                 cx.notify();
             }
+
             project::Event::DiagnosticsUpdated { .. } => {
                 this.summary = project.read(cx).diagnostic_summary(cx);
                 cx.notify();
             }
+
             _ => {}
         })
         .detach();
+
         Self {
             summary: project.read(cx).diagnostic_summary(cx),
             in_progress_checks: project

crates/diagnostics2/src/toolbar_controls.rs 🔗

@@ -14,50 +14,35 @@ impl Render for ToolbarControls {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-        div()
-            .h_flex()
-            .child(IconButton::new("toggle-warnings", Icon::Warning).on_click(|view, cx| todo!()))
+        let include_warnings = self
+            .editor
+            .as_ref()
+            .and_then(|editor| editor.upgrade())
+            .map(|editor| editor.read(cx).include_warnings)
+            .unwrap_or(false);
+
+        let tooltip = if include_warnings {
+            "Exclude Warnings"
+        } else {
+            "Include Warnings"
+        };
+
+        div().child(
+            IconButton::new("toggle-warnings", Icon::ExclamationTriangle)
+                .tooltip(tooltip)
+                .on_click(|this: &mut Self, cx| {
+                    if let Some(editor) = this.editor.as_ref().and_then(|editor| editor.upgrade()) {
+                        editor.update(cx, |editor, cx| {
+                            editor.toggle_warnings(&Default::default(), cx);
+                        });
+                    }
+                }),
+        )
     }
 }
 
 impl EventEmitter<ToolbarItemEvent> for ToolbarControls {}
 
-// impl View for ToolbarControls {
-//     fn ui_name() -> &'static str {
-//         "ToolbarControls"
-//     }
-
-//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-//         let include_warnings = self
-//             .editor
-//             .as_ref()
-//             .and_then(|editor| editor.upgrade(cx))
-//             .map(|editor| editor.read(cx).include_warnings)
-//             .unwrap_or(false);
-//         let tooltip = if include_warnings {
-//             "Exclude Warnings".into()
-//         } else {
-//             "Include Warnings".into()
-//         };
-//         Flex::row()
-//             .with_child(render_toggle_button(
-//                 0,
-//                 "icons/warning.svg",
-//                 include_warnings,
-//                 (tooltip, Some(Box::new(ToggleWarnings))),
-//                 cx,
-//                 move |this, cx| {
-//                     if let Some(editor) = this.editor.and_then(|editor| editor.upgrade(cx)) {
-//                         editor.update(cx, |editor, cx| {
-//                             editor.toggle_warnings(&Default::default(), cx)
-//                         });
-//                     }
-//                 },
-//             ))
-//             .into_any()
-//     }
-// }
-
 impl ToolbarItemView for ToolbarControls {
     fn set_active_pane_item(
         &mut self,
@@ -82,42 +67,3 @@ impl ToolbarControls {
         ToolbarControls { editor: None }
     }
 }
-
-// fn render_toggle_button<
-//     F: 'static + Fn(&mut ToolbarControls, &mut EventContext<ToolbarControls>),
-// >(
-//     index: usize,
-//     icon: &'static str,
-//     toggled: bool,
-//     tooltip: (String, Option<Box<dyn Action>>),
-//     cx: &mut ViewContext<ToolbarControls>,
-//     on_click: F,
-// ) -> AnyElement<ToolbarControls> {
-//     enum Button {}
-
-//     let theme = theme::current(cx);
-//     let (tooltip_text, action) = tooltip;
-
-//     MouseEventHandler::new::<Button, _>(index, cx, |mouse_state, _| {
-//         let style = theme
-//             .workspace
-//             .toolbar
-//             .toggleable_tool
-//             .in_state(toggled)
-//             .style_for(mouse_state);
-//         Svg::new(icon)
-//             .with_color(style.color)
-//             .constrained()
-//             .with_width(style.icon_width)
-//             .aligned()
-//             .constrained()
-//             .with_width(style.button_width)
-//             .with_height(style.button_width)
-//             .contained()
-//             .with_style(style.container)
-//     })
-//     .with_cursor_style(CursorStyle::PointingHand)
-//     .on_click(MouseButton::Left, move |_, view, cx| on_click(view, cx))
-//     .with_tooltip::<Button>(index, tooltip_text, action, theme.tooltip.clone(), cx)
-//     .into_any_named("quick action bar button")
-// }

crates/ui2/src/components/icon.rs 🔗

@@ -64,7 +64,6 @@ pub enum Icon {
     Split,
     SplitMessage,
     Terminal,
-    Warning,
     XCircle,
 }
 
@@ -123,7 +122,6 @@ impl Icon {
             Icon::Split => "icons/split.svg",
             Icon::SplitMessage => "icons/split_message.svg",
             Icon::Terminal => "icons/terminal.svg",
-            Icon::Warning => "icons/warning.svg",
             Icon::XCircle => "icons/error.svg",
         }
     }

crates/zed2/Cargo.toml 🔗

@@ -32,7 +32,7 @@ client = { package = "client2", path = "../client2" }
 # clock = { path = "../clock" }
 copilot = { package = "copilot2", path = "../copilot2" }
 # copilot_button = { path = "../copilot_button" }
-# diagnostics = { path = "../diagnostics" }
+diagnostics = { package = "diagnostics2", path = "../diagnostics2" }
 db = { package = "db2", path = "../db2" }
 editor = { package="editor2", path = "../editor2" }
 # feedback = { path = "../feedback" }

crates/zed2/src/main.rs 🔗

@@ -147,6 +147,7 @@ fn main() {
         command_palette::init(cx);
         language::init(cx);
         editor::init(cx);
+        diagnostics::init(cx);
         copilot::init(
             copilot_language_server_id,
             http.clone(),

crates/zed2/src/zed2.rs 🔗

@@ -314,8 +314,8 @@ pub fn initialize_workspace(
                                 //         QuickActionBar::new(buffer_search_bar, workspace)
                                 //     });
                                 //     toolbar.add_item(quick_action_bar, cx);
-                                //     let diagnostic_editor_controls =
-                                //         cx.add_view(|_| diagnostics2::ToolbarControls::new());
+                                let diagnostic_editor_controls =
+                                    cx.build_view(|_| diagnostics::ToolbarControls::new());
                                 //     toolbar.add_item(diagnostic_editor_controls, cx);
                                 //     let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
                                 //     toolbar.add_item(project_search_bar, cx);
@@ -347,8 +347,8 @@ pub fn initialize_workspace(
 
             //     let copilot =
             //         cx.add_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx));
-            //     let diagnostic_summary =
-            //         cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
+            let diagnostic_summary =
+                cx.build_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
             //     let activity_indicator = activity_indicator::ActivityIndicator::new(
             //         workspace,
             //         app_state.languages.clone(),