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}