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}