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
 94// todo: is this nessesary anymore?
 95impl EventEmitter<ToolbarItemEvent> for DiagnosticIndicator {}
 96
 97// impl View for DiagnosticIndicator {
 98//     fn ui_name() -> &'static str {
 99//         "DiagnosticIndicator"
100//     }
101
102//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
103//         enum Summary {}
104//         enum Message {}
105
106//         let tooltip_style = theme::current(cx).tooltip.clone();
107//         let in_progress = !self.in_progress_checks.is_empty();
108//         let mut element = Flex::row().with_child(
109//             MouseEventHandler::new::<Summary, _>(0, cx, |state, cx| {
110//                 let theme = theme::current(cx);
111//                 let style = theme
112//                     .workspace
113//                     .status_bar
114//                     .diagnostic_summary
115//                     .style_for(state);
116
117//                 let mut summary_row = Flex::row();
118//                 if self.summary.error_count > 0 {
119//                     summary_row.add_child(
120//                         Svg::new("icons/error.svg")
121//                             .with_color(style.icon_color_error)
122//                             .constrained()
123//                             .with_width(style.icon_width)
124//                             .aligned()
125//                             .contained()
126//                             .with_margin_right(style.icon_spacing),
127//                     );
128//                     summary_row.add_child(
129//                         Label::new(self.summary.error_count.to_string(), style.text.clone())
130//                             .aligned(),
131//                     );
132//                 }
133
134//                 if self.summary.warning_count > 0 {
135//                     summary_row.add_child(
136//                         Svg::new("icons/warning.svg")
137//                             .with_color(style.icon_color_warning)
138//                             .constrained()
139//                             .with_width(style.icon_width)
140//                             .aligned()
141//                             .contained()
142//                             .with_margin_right(style.icon_spacing)
143//                             .with_margin_left(if self.summary.error_count > 0 {
144//                                 style.summary_spacing
145//                             } else {
146//                                 0.
147//                             }),
148//                     );
149//                     summary_row.add_child(
150//                         Label::new(self.summary.warning_count.to_string(), style.text.clone())
151//                             .aligned(),
152//                     );
153//                 }
154
155//                 if self.summary.error_count == 0 && self.summary.warning_count == 0 {
156//                     summary_row.add_child(
157//                         Svg::new("icons/check_circle.svg")
158//                             .with_color(style.icon_color_ok)
159//                             .constrained()
160//                             .with_width(style.icon_width)
161//                             .aligned()
162//                             .into_any_named("ok-icon"),
163//                     );
164//                 }
165
166//                 summary_row
167//                     .constrained()
168//                     .with_height(style.height)
169//                     .contained()
170//                     .with_style(if self.summary.error_count > 0 {
171//                         style.container_error
172//                     } else if self.summary.warning_count > 0 {
173//                         style.container_warning
174//                     } else {
175//                         style.container_ok
176//                     })
177//             })
178//             .with_cursor_style(CursorStyle::PointingHand)
179//             .on_click(MouseButton::Left, |_, this, cx| {
180//                 if let Some(workspace) = this.workspace.upgrade(cx) {
181//                     workspace.update(cx, |workspace, cx| {
182//                         ProjectDiagnosticsEditor::deploy(workspace, &Default::default(), cx)
183//                     })
184//                 }
185//             })
186//             .with_tooltip::<Summary>(
187//                 0,
188//                 "Project Diagnostics",
189//                 Some(Box::new(crate::Deploy)),
190//                 tooltip_style,
191//                 cx,
192//             )
193//             .aligned()
194//             .into_any(),
195//         );
196
197//         let style = &theme::current(cx).workspace.status_bar;
198//         let item_spacing = style.item_spacing;
199
200//         if in_progress {
201//             element.add_child(
202//                 Label::new("Checking…", style.diagnostic_message.default.text.clone())
203//                     .aligned()
204//                     .contained()
205//                     .with_margin_left(item_spacing),
206//             );
207//         } else if let Some(diagnostic) = &self.current_diagnostic {
208//             let message_style = style.diagnostic_message.clone();
209//             element.add_child(
210//                 MouseEventHandler::new::<Message, _>(1, cx, |state, _| {
211//                     Label::new(
212//                         diagnostic.message.split('\n').next().unwrap().to_string(),
213//                         message_style.style_for(state).text.clone(),
214//                     )
215//                     .aligned()
216//                     .contained()
217//                     .with_margin_left(item_spacing)
218//                 })
219//                 .with_cursor_style(CursorStyle::PointingHand)
220//                 .on_click(MouseButton::Left, |_, this, cx| {
221//                     this.go_to_next_diagnostic(&Default::default(), cx)
222//                 }),
223//             );
224//         }
225
226//         element.into_any_named("diagnostic indicator")
227//     }
228
229//     fn debug_json(&self, _: &gpui::AppContext) -> serde_json::Value {
230//         serde_json::json!({ "summary": self.summary })
231//     }
232// }
233
234impl StatusItemView for DiagnosticIndicator {
235    fn set_active_pane_item(
236        &mut self,
237        active_pane_item: Option<&dyn ItemHandle>,
238        cx: &mut ViewContext<Self>,
239    ) {
240        if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
241            self.active_editor = Some(editor.downgrade());
242            self._observe_active_editor = Some(cx.observe(&editor, Self::update));
243            self.update(editor, cx);
244        } else {
245            self.active_editor = None;
246            self.current_diagnostic = None;
247            self._observe_active_editor = None;
248        }
249        cx.notify();
250    }
251}