items.rs

  1use collections::HashSet;
  2use editor::{Editor, GoToDiagnostic};
  3use gpui::{
  4    div, serde_json, AppContext, CursorStyle, Div, Entity, EventEmitter, MouseButton, Render,
  5    Styled, Subscription, View, ViewContext, WeakView,
  6};
  7use language::Diagnostic;
  8use lsp::LanguageServerId;
  9use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace};
 10
 11use crate::ProjectDiagnosticsEditor;
 12
 13// todo!()
 14// pub fn init(cx: &mut AppContext) {
 15//     cx.add_action(DiagnosticIndicator::go_to_next_diagnostic);
 16// }
 17
 18pub struct DiagnosticIndicator {
 19    summary: project::DiagnosticSummary,
 20    active_editor: Option<WeakView<Editor>>,
 21    workspace: WeakView<Workspace>,
 22    current_diagnostic: Option<Diagnostic>,
 23    in_progress_checks: HashSet<LanguageServerId>,
 24    _observe_active_editor: Option<Subscription>,
 25}
 26
 27impl Render for DiagnosticIndicator {
 28    type Element = Div<Self>;
 29
 30    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
 31        div().size_full().bg(gpui::red())
 32    }
 33}
 34
 35impl DiagnosticIndicator {
 36    pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
 37        let project = workspace.project();
 38        cx.subscribe(project, |this, project, event, cx| match event {
 39            project::Event::DiskBasedDiagnosticsStarted { language_server_id } => {
 40                this.in_progress_checks.insert(*language_server_id);
 41                cx.notify();
 42            }
 43            project::Event::DiskBasedDiagnosticsFinished { language_server_id }
 44            | project::Event::LanguageServerRemoved(language_server_id) => {
 45                this.summary = project.read(cx).diagnostic_summary(cx);
 46                this.in_progress_checks.remove(language_server_id);
 47                cx.notify();
 48            }
 49            project::Event::DiagnosticsUpdated { .. } => {
 50                this.summary = project.read(cx).diagnostic_summary(cx);
 51                cx.notify();
 52            }
 53            _ => {}
 54        })
 55        .detach();
 56        Self {
 57            summary: project.read(cx).diagnostic_summary(cx),
 58            in_progress_checks: project
 59                .read(cx)
 60                .language_servers_running_disk_based_diagnostics()
 61                .collect(),
 62            active_editor: None,
 63            workspace: workspace.weak_handle(),
 64            current_diagnostic: None,
 65            _observe_active_editor: None,
 66        }
 67    }
 68
 69    fn go_to_next_diagnostic(&mut self, _: &GoToDiagnostic, cx: &mut ViewContext<Self>) {
 70        if let Some(editor) = self.active_editor.as_ref().and_then(|e| e.upgrade()) {
 71            editor.update(cx, |editor, cx| {
 72                editor.go_to_diagnostic_impl(editor::Direction::Next, cx);
 73            })
 74        }
 75    }
 76
 77    fn update(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
 78        let editor = editor.read(cx);
 79        let buffer = editor.buffer().read(cx);
 80        let cursor_position = editor.selections.newest::<usize>(cx).head();
 81        let new_diagnostic = buffer
 82            .snapshot(cx)
 83            .diagnostics_in_range::<_, usize>(cursor_position..cursor_position, false)
 84            .filter(|entry| !entry.range.is_empty())
 85            .min_by_key(|entry| (entry.diagnostic.severity, entry.range.len()))
 86            .map(|entry| entry.diagnostic);
 87        if new_diagnostic != self.current_diagnostic {
 88            self.current_diagnostic = new_diagnostic;
 89            cx.notify();
 90        }
 91    }
 92}
 93
 94impl EventEmitter<ToolbarItemEvent> for DiagnosticIndicator {}
 95
 96// impl View for DiagnosticIndicator {
 97//     fn ui_name() -> &'static str {
 98//         "DiagnosticIndicator"
 99//     }
100
101//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
102//         enum Summary {}
103//         enum Message {}
104
105//         let tooltip_style = theme::current(cx).tooltip.clone();
106//         let in_progress = !self.in_progress_checks.is_empty();
107//         let mut element = Flex::row().with_child(
108//             MouseEventHandler::new::<Summary, _>(0, cx, |state, cx| {
109//                 let theme = theme::current(cx);
110//                 let style = theme
111//                     .workspace
112//                     .status_bar
113//                     .diagnostic_summary
114//                     .style_for(state);
115
116//                 let mut summary_row = Flex::row();
117//                 if self.summary.error_count > 0 {
118//                     summary_row.add_child(
119//                         Svg::new("icons/error.svg")
120//                             .with_color(style.icon_color_error)
121//                             .constrained()
122//                             .with_width(style.icon_width)
123//                             .aligned()
124//                             .contained()
125//                             .with_margin_right(style.icon_spacing),
126//                     );
127//                     summary_row.add_child(
128//                         Label::new(self.summary.error_count.to_string(), style.text.clone())
129//                             .aligned(),
130//                     );
131//                 }
132
133//                 if self.summary.warning_count > 0 {
134//                     summary_row.add_child(
135//                         Svg::new("icons/warning.svg")
136//                             .with_color(style.icon_color_warning)
137//                             .constrained()
138//                             .with_width(style.icon_width)
139//                             .aligned()
140//                             .contained()
141//                             .with_margin_right(style.icon_spacing)
142//                             .with_margin_left(if self.summary.error_count > 0 {
143//                                 style.summary_spacing
144//                             } else {
145//                                 0.
146//                             }),
147//                     );
148//                     summary_row.add_child(
149//                         Label::new(self.summary.warning_count.to_string(), style.text.clone())
150//                             .aligned(),
151//                     );
152//                 }
153
154//                 if self.summary.error_count == 0 && self.summary.warning_count == 0 {
155//                     summary_row.add_child(
156//                         Svg::new("icons/check_circle.svg")
157//                             .with_color(style.icon_color_ok)
158//                             .constrained()
159//                             .with_width(style.icon_width)
160//                             .aligned()
161//                             .into_any_named("ok-icon"),
162//                     );
163//                 }
164
165//                 summary_row
166//                     .constrained()
167//                     .with_height(style.height)
168//                     .contained()
169//                     .with_style(if self.summary.error_count > 0 {
170//                         style.container_error
171//                     } else if self.summary.warning_count > 0 {
172//                         style.container_warning
173//                     } else {
174//                         style.container_ok
175//                     })
176//             })
177//             .with_cursor_style(CursorStyle::PointingHand)
178//             .on_click(MouseButton::Left, |_, this, cx| {
179//                 if let Some(workspace) = this.workspace.upgrade(cx) {
180//                     workspace.update(cx, |workspace, cx| {
181//                         ProjectDiagnosticsEditor::deploy(workspace, &Default::default(), cx)
182//                     })
183//                 }
184//             })
185//             .with_tooltip::<Summary>(
186//                 0,
187//                 "Project Diagnostics",
188//                 Some(Box::new(crate::Deploy)),
189//                 tooltip_style,
190//                 cx,
191//             )
192//             .aligned()
193//             .into_any(),
194//         );
195
196//         let style = &theme::current(cx).workspace.status_bar;
197//         let item_spacing = style.item_spacing;
198
199//         if in_progress {
200//             element.add_child(
201//                 Label::new("Checking…", style.diagnostic_message.default.text.clone())
202//                     .aligned()
203//                     .contained()
204//                     .with_margin_left(item_spacing),
205//             );
206//         } else if let Some(diagnostic) = &self.current_diagnostic {
207//             let message_style = style.diagnostic_message.clone();
208//             element.add_child(
209//                 MouseEventHandler::new::<Message, _>(1, cx, |state, _| {
210//                     Label::new(
211//                         diagnostic.message.split('\n').next().unwrap().to_string(),
212//                         message_style.style_for(state).text.clone(),
213//                     )
214//                     .aligned()
215//                     .contained()
216//                     .with_margin_left(item_spacing)
217//                 })
218//                 .with_cursor_style(CursorStyle::PointingHand)
219//                 .on_click(MouseButton::Left, |_, this, cx| {
220//                     this.go_to_next_diagnostic(&Default::default(), cx)
221//                 }),
222//             );
223//         }
224
225//         element.into_any_named("diagnostic indicator")
226//     }
227
228//     fn debug_json(&self, _: &gpui::AppContext) -> serde_json::Value {
229//         serde_json::json!({ "summary": self.summary })
230//     }
231// }
232
233impl StatusItemView for DiagnosticIndicator {
234    fn set_active_pane_item(
235        &mut self,
236        active_pane_item: Option<&dyn ItemHandle>,
237        cx: &mut ViewContext<Self>,
238    ) {
239        if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
240            self.active_editor = Some(editor.downgrade());
241            self._observe_active_editor = Some(cx.observe(&editor, Self::update));
242            self.update(editor, cx);
243        } else {
244            self.active_editor = None;
245            self.current_diagnostic = None;
246            self._observe_active_editor = None;
247        }
248        cx.notify();
249    }
250}