Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T01:32:29.011Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

items.rs

271 lines · 10.5 KB · rust
1use std::time::Duration;
2
3use editor::{Editor, MultiBufferOffset};
4use gpui::{
5    App, Context, Entity, EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription,
6    Task, WeakEntity, Window,
7};
8use language::Diagnostic;
9use project::project_settings::{GoToDiagnosticSeverityFilter, ProjectSettings};
10use settings::Settings;
11use ui::{Button, ButtonLike, Color, Icon, IconName, Label, Tooltip, h_flex, prelude::*};
12use util::ResultExt;
13use workspace::{HideStatusItem, StatusItemView, ToolbarItemEvent, Workspace, item::ItemHandle};
14
15use crate::{Deploy, IncludeWarnings, ProjectDiagnosticsEditor};
16
17/// The status bar item that displays diagnostic counts.
18pub struct DiagnosticIndicator {
19    summary: project::DiagnosticSummary,
20    workspace: WeakEntity<Workspace>,
21    current_diagnostic: Option<Diagnostic>,
22    active_editor: Option<WeakEntity<Editor>>,
23    _observe_active_editor: Option<Subscription>,
24
25    diagnostics_update: Task<()>,
26    diagnostic_summary_update: Task<()>,
27}
28
29impl Render for DiagnosticIndicator {
30    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
31        let indicator = h_flex().gap_2().min_w_0().overflow_x_hidden();
32        if !ProjectSettings::get_global(cx).diagnostics.button {
33            return indicator.hidden();
34        }
35
36        let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) {
37            (0, 0) => h_flex().child(
38                Icon::new(IconName::Check)
39                    .size(IconSize::Small)
40                    .color(Color::Default),
41            ),
42            (error_count, warning_count) => h_flex()
43                .gap_1()
44                .when(error_count > 0, |this| {
45                    this.child(
46                        Icon::new(IconName::XCircle)
47                            .size(IconSize::Small)
48                            .color(Color::Error),
49                    )
50                    .child(Label::new(error_count.to_string()).size(LabelSize::Small))
51                })
52                .when(warning_count > 0, |this| {
53                    this.child(
54                        Icon::new(IconName::Warning)
55                            .size(IconSize::Small)
56                            .color(Color::Warning),
57                    )
58                    .child(Label::new(warning_count.to_string()).size(LabelSize::Small))
59                }),
60        };
61
62        let status = if let Some(diagnostic) = &self.current_diagnostic {
63            let message = diagnostic
64                .message
65                .split_once('\n')
66                .map_or(&*diagnostic.message, |(first, _)| first);
67            let diagnostics_already_active = self.any_active_diagnostics(cx);
68            let tooltip = if !diagnostics_already_active {
69                "Expand Diagnostics"
70            } else {
71                "Next Diagnostic"
72            };
73            Some(
74                Button::new("diagnostic_message", SharedString::new(message))
75                    .label_size(LabelSize::Small)
76                    .truncate(true)
77                    .tab_index(0isize)
78                    .tooltip(move |_window, cx| {
79                        Tooltip::for_action(
80                            tooltip,
81                            &editor::actions::GoToDiagnostic::default(),
82                            cx,
83                        )
84                    })
85                    .on_click(
86                        cx.listener(|this, _, window, cx| this.go_to_next_diagnostic(window, cx)),
87                    ),
88            )
89        } else {
90            None
91        };
92
93        let diagnostics_label = match (self.summary.error_count, self.summary.warning_count) {
94            (0, 0) => "Project diagnostics: no problems".to_string(),
95            (errors, warnings) => {
96                let mut parts = Vec::new();
97                if errors > 0 {
98                    parts.push(format!(
99                        "{errors} error{}",
100                        if errors == 1 { "" } else { "s" }
101                    ));
102                }
103                if warnings > 0 {
104                    parts.push(format!(
105                        "{warnings} warning{}",
106                        if warnings == 1 { "" } else { "s" }
107                    ));
108                }
109                format!("Project diagnostics: {}", parts.join(", "))
110            }
111        };
112
113        indicator
114            .child(
115                ButtonLike::new("diagnostic-indicator")
116                    .child(diagnostic_indicator)
117                    .tab_index(0isize)
118                    .aria_label(diagnostics_label)
119                    .tooltip(move |_window, cx| {
120                        Tooltip::for_action("Project Diagnostics", &Deploy, cx)
121                    })
122                    .on_click(cx.listener(|this, _, window, cx| {
123                        if let Some(workspace) = this.workspace.upgrade() {
124                            if this.summary.error_count == 0 && this.summary.warning_count > 0 {
125                                cx.update_default_global(
126                                    |show_warnings: &mut IncludeWarnings, _| show_warnings.0 = true,
127                                );
128                            }
129                            workspace.update(cx, |workspace, cx| {
130                                ProjectDiagnosticsEditor::deploy(
131                                    workspace,
132                                    &Default::default(),
133                                    window,
134                                    cx,
135                                )
136                            })
137                        }
138                    })),
139            )
140            .children(status)
141    }
142}
143
144impl DiagnosticIndicator {
145    pub fn new(workspace: &Workspace, cx: &mut Context<Self>) -> Self {
146        let project = workspace.project();
147        cx.subscribe(project, |this, project, event, cx| match event {
148            project::Event::DiskBasedDiagnosticsStarted { .. } => {
149                cx.notify();
150            }
151
152            project::Event::DiskBasedDiagnosticsFinished { .. }
153            | project::Event::LanguageServerRemoved(_) => {
154                this.summary = project.read(cx).diagnostic_summary(false, cx);
155                cx.notify();
156            }
157
158            project::Event::DiagnosticsUpdated { .. } => {
159                this.diagnostic_summary_update = cx.spawn(async move |this, cx| {
160                    cx.background_executor()
161                        .timer(Duration::from_millis(30))
162                        .await;
163                    this.update(cx, |this, cx| {
164                        this.summary = project.read(cx).diagnostic_summary(false, cx);
165                        cx.notify();
166                    })
167                    .log_err();
168                });
169            }
170
171            _ => {}
172        })
173        .detach();
174
175        Self {
176            summary: project.read(cx).diagnostic_summary(false, cx),
177            active_editor: None,
178            workspace: workspace.weak_handle(),
179            current_diagnostic: None,
180            _observe_active_editor: None,
181            diagnostics_update: Task::ready(()),
182            diagnostic_summary_update: Task::ready(()),
183        }
184    }
185
186    fn any_active_diagnostics(&self, cx: &mut Context<Self>) -> bool {
187        if let Some(editor) = self.active_editor.as_ref().and_then(|e| e.upgrade()) {
188            editor.read(cx).any_active_diagnostics()
189        } else {
190            false
191        }
192    }
193
194    fn go_to_next_diagnostic(&mut self, window: &mut Window, cx: &mut Context<Self>) {
195        if let Some(editor) = self.active_editor.as_ref().and_then(|e| e.upgrade()) {
196            editor.update(cx, |editor, cx| {
197                editor.go_to_diagnostic_at_cursor(
198                    editor::Direction::Next,
199                    GoToDiagnosticSeverityFilter::default(),
200                    window,
201                    cx,
202                );
203            })
204        }
205    }
206
207    fn update(&mut self, editor: Entity<Editor>, window: &mut Window, cx: &mut Context<Self>) {
208        let (buffer, cursor_position) = editor.update(cx, |editor, cx| {
209            let buffer = editor.buffer().read(cx).snapshot(cx);
210            let cursor_position = editor
211                .selections
212                .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
213                .head();
214            (buffer, cursor_position)
215        });
216        let new_diagnostic = buffer
217            .diagnostics_in_range::<MultiBufferOffset>(cursor_position..cursor_position)
218            .filter(|entry| !entry.range.is_empty())
219            .min_by_key(|entry| {
220                (
221                    entry.diagnostic.severity,
222                    entry.range.end - entry.range.start,
223                )
224            })
225            .map(|entry| entry.diagnostic);
226        if new_diagnostic != self.current_diagnostic.as_ref() {
227            let new_diagnostic = new_diagnostic.cloned();
228            self.diagnostics_update =
229                cx.spawn_in(window, async move |diagnostics_indicator, cx| {
230                    cx.background_executor()
231                        .timer(Duration::from_millis(50))
232                        .await;
233                    diagnostics_indicator
234                        .update(cx, |diagnostics_indicator, cx| {
235                            diagnostics_indicator.current_diagnostic = new_diagnostic;
236                            cx.notify();
237                        })
238                        .ok();
239                });
240        }
241    }
242}
243
244impl EventEmitter<ToolbarItemEvent> for DiagnosticIndicator {}
245
246impl StatusItemView for DiagnosticIndicator {
247    fn set_active_pane_item(
248        &mut self,
249        active_pane_item: Option<&dyn ItemHandle>,
250        window: &mut Window,
251        cx: &mut Context<Self>,
252    ) {
253        if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
254            self.active_editor = Some(editor.downgrade());
255            self._observe_active_editor = Some(cx.observe_in(&editor, window, Self::update));
256            self.update(editor, window, cx);
257        } else {
258            self.active_editor = None;
259            self.current_diagnostic = None;
260            self._observe_active_editor = None;
261        }
262        cx.notify();
263    }
264
265    fn hide_setting(&self, _: &App) -> Option<HideStatusItem> {
266        Some(HideStatusItem::new(|settings| {
267            settings.diagnostics.get_or_insert_default().button = Some(false);
268        }))
269    }
270}
271
Served at tenant.openagents/omega Member data and write actions are omitted.