Closes https://github.com/zed-industries/zed/issues/21291 This PR also adds a small divider separating the panel-opening controls from the other items that appear on the left side of the status bar. The spacing was a bit bigger before because all three items on the left open panels, whereas each other item does different things (e.g., open the diagnostics tab, update the app, display language server status, etc.). Therefore, they needed to be separated somehow to communicate the difference in behavior. Hopefully, now, the border will help sort of figuring this out. | With error | Normal state | |--------|--------| | <img width="1179" alt="Screenshot 2024-11-28 at 18 52 58" src="https://github.com/user-attachments/assets/bf4bad19-5588-481a-9d08-91b2227e44e6"> | <img width="1234" alt="Screenshot 2024-11-28 at 18 53 03" src="https://github.com/user-attachments/assets/4443a16a-9982-44ce-9005-64d4df46f4f0"> | Release Notes: - N/A
178 lines
6.6 KiB
Rust
178 lines
6.6 KiB
Rust
use editor::Editor;
|
|
use gpui::{
|
|
EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, View, ViewContext,
|
|
WeakView,
|
|
};
|
|
use language::Diagnostic;
|
|
use ui::{h_flex, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip};
|
|
use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace};
|
|
|
|
use crate::{Deploy, ProjectDiagnosticsEditor};
|
|
|
|
pub struct DiagnosticIndicator {
|
|
summary: project::DiagnosticSummary,
|
|
active_editor: Option<WeakView<Editor>>,
|
|
workspace: WeakView<Workspace>,
|
|
current_diagnostic: Option<Diagnostic>,
|
|
_observe_active_editor: Option<Subscription>,
|
|
}
|
|
|
|
impl Render for DiagnosticIndicator {
|
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
|
let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) {
|
|
(0, 0) => h_flex().map(|this| {
|
|
this.child(
|
|
Icon::new(IconName::Check)
|
|
.size(IconSize::Small)
|
|
.color(Color::Default),
|
|
)
|
|
}),
|
|
(0, warning_count) => h_flex()
|
|
.gap_1()
|
|
.child(
|
|
Icon::new(IconName::Warning)
|
|
.size(IconSize::Small)
|
|
.color(Color::Warning),
|
|
)
|
|
.child(Label::new(warning_count.to_string()).size(LabelSize::Small)),
|
|
(error_count, 0) => h_flex()
|
|
.gap_1()
|
|
.child(
|
|
Icon::new(IconName::XCircle)
|
|
.size(IconSize::Small)
|
|
.color(Color::Error),
|
|
)
|
|
.child(Label::new(error_count.to_string()).size(LabelSize::Small)),
|
|
(error_count, warning_count) => h_flex()
|
|
.gap_1()
|
|
.child(
|
|
Icon::new(IconName::XCircle)
|
|
.size(IconSize::Small)
|
|
.color(Color::Error),
|
|
)
|
|
.child(Label::new(error_count.to_string()).size(LabelSize::Small))
|
|
.child(
|
|
Icon::new(IconName::Warning)
|
|
.size(IconSize::Small)
|
|
.color(Color::Warning),
|
|
)
|
|
.child(Label::new(warning_count.to_string()).size(LabelSize::Small)),
|
|
};
|
|
|
|
let status = if let Some(diagnostic) = &self.current_diagnostic {
|
|
let message = diagnostic.message.split('\n').next().unwrap().to_string();
|
|
Some(
|
|
Button::new("diagnostic_message", message)
|
|
.label_size(LabelSize::Small)
|
|
.tooltip(|cx| {
|
|
Tooltip::for_action("Next Diagnostic", &editor::actions::GoToDiagnostic, cx)
|
|
})
|
|
.on_click(cx.listener(|this, _, cx| {
|
|
this.go_to_next_diagnostic(cx);
|
|
}))
|
|
.into_any_element(),
|
|
)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
h_flex()
|
|
.gap_2()
|
|
.pl_1()
|
|
.border_l_1()
|
|
.border_color(cx.theme().colors().border)
|
|
.child(
|
|
ButtonLike::new("diagnostic-indicator")
|
|
.child(diagnostic_indicator)
|
|
.tooltip(|cx| Tooltip::for_action("Project Diagnostics", &Deploy, cx))
|
|
.on_click(cx.listener(|this, _, cx| {
|
|
if let Some(workspace) = this.workspace.upgrade() {
|
|
workspace.update(cx, |workspace, cx| {
|
|
ProjectDiagnosticsEditor::deploy(workspace, &Default::default(), cx)
|
|
})
|
|
}
|
|
})),
|
|
)
|
|
.children(status)
|
|
}
|
|
}
|
|
|
|
impl DiagnosticIndicator {
|
|
pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
|
|
let project = workspace.project();
|
|
cx.subscribe(project, |this, project, event, cx| match event {
|
|
project::Event::DiskBasedDiagnosticsStarted { .. } => {
|
|
cx.notify();
|
|
}
|
|
|
|
project::Event::DiskBasedDiagnosticsFinished { .. }
|
|
| project::Event::LanguageServerRemoved(_) => {
|
|
this.summary = project.read(cx).diagnostic_summary(false, cx);
|
|
cx.notify();
|
|
}
|
|
|
|
project::Event::DiagnosticsUpdated { .. } => {
|
|
this.summary = project.read(cx).diagnostic_summary(false, cx);
|
|
cx.notify();
|
|
}
|
|
|
|
_ => {}
|
|
})
|
|
.detach();
|
|
|
|
Self {
|
|
summary: project.read(cx).diagnostic_summary(false, cx),
|
|
active_editor: None,
|
|
workspace: workspace.weak_handle(),
|
|
current_diagnostic: None,
|
|
_observe_active_editor: None,
|
|
}
|
|
}
|
|
|
|
fn go_to_next_diagnostic(&mut self, cx: &mut ViewContext<Self>) {
|
|
if let Some(editor) = self.active_editor.as_ref().and_then(|e| e.upgrade()) {
|
|
editor.update(cx, |editor, cx| {
|
|
editor.go_to_diagnostic_impl(editor::Direction::Next, cx);
|
|
})
|
|
}
|
|
}
|
|
|
|
fn update(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
|
|
let (buffer, cursor_position) = editor.update(cx, |editor, cx| {
|
|
let buffer = editor.buffer().read(cx).snapshot(cx);
|
|
let cursor_position = editor.selections.newest::<usize>(cx).head();
|
|
(buffer, cursor_position)
|
|
});
|
|
let new_diagnostic = buffer
|
|
.diagnostics_in_range::<_, usize>(cursor_position..cursor_position, false)
|
|
.filter(|entry| !entry.range.is_empty())
|
|
.min_by_key(|entry| (entry.diagnostic.severity, entry.range.len()))
|
|
.map(|entry| entry.diagnostic);
|
|
if new_diagnostic != self.current_diagnostic {
|
|
self.current_diagnostic = new_diagnostic;
|
|
cx.notify();
|
|
}
|
|
}
|
|
}
|
|
|
|
impl EventEmitter<ToolbarItemEvent> for DiagnosticIndicator {}
|
|
|
|
impl StatusItemView for DiagnosticIndicator {
|
|
fn set_active_pane_item(
|
|
&mut self,
|
|
active_pane_item: Option<&dyn ItemHandle>,
|
|
cx: &mut ViewContext<Self>,
|
|
) {
|
|
if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
|
|
self.active_editor = Some(editor.downgrade());
|
|
self._observe_active_editor = Some(cx.observe(&editor, Self::update));
|
|
self.update(editor, cx);
|
|
} else {
|
|
self.active_editor = None;
|
|
self.current_diagnostic = None;
|
|
self._observe_active_editor = None;
|
|
}
|
|
cx.notify();
|
|
}
|
|
}
|