Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T01:34:20.640Z 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

diagnostic_renderer.rs

333 lines · 12.3 KB · rust
1use std::{ops::Range, sync::Arc};
2
3use editor::{
4    Anchor, Editor, EditorSnapshot, ToOffset,
5    display_map::{BlockContext, BlockPlacement, BlockProperties, BlockStyle},
6    hover_popover::diagnostics_markdown_style,
7};
8use gpui::{AppContext, Entity, Focusable, WeakEntity};
9use language::{BufferId, Diagnostic, DiagnosticEntryRef, LanguageRegistry};
10use lsp::DiagnosticSeverity;
11use markdown::{CopyButtonVisibility, Markdown, MarkdownElement};
12use settings::Settings;
13use text::Point;
14use theme_settings::ThemeSettings;
15use ui::{CopyButton, prelude::*};
16use util::maybe;
17
18use crate::toolbar_controls::DiagnosticsToolbarEditor;
19
20pub struct DiagnosticRenderer;
21
22impl DiagnosticRenderer {
23    pub fn diagnostic_blocks_for_group(
24        diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
25        buffer_id: BufferId,
26        diagnostics_editor: Option<Arc<dyn DiagnosticsToolbarEditor>>,
27        language_registry: Option<Arc<LanguageRegistry>>,
28        cx: &mut App,
29    ) -> Vec<DiagnosticBlock> {
30        let Some(primary_ix) = diagnostic_group
31            .iter()
32            .position(|d| d.diagnostic.is_primary)
33        else {
34            return Vec::new();
35        };
36        let primary = &diagnostic_group[primary_ix];
37        let group_id = primary.diagnostic.group_id;
38        let mut results = vec![];
39        for entry in diagnostic_group.iter() {
40            let mut markdown = Self::markdown(&entry.diagnostic);
41            if entry.diagnostic.is_primary {
42                let diagnostic = &primary.diagnostic;
43                append_source_and_code(&mut markdown, diagnostic);
44
45                for (ix, entry) in diagnostic_group.iter().enumerate() {
46                    if entry.range.start.row.abs_diff(primary.range.start.row) >= 5 {
47                        markdown.push_str("\n- hint: [");
48                        markdown.push_str(&Markdown::escape(&entry.diagnostic.message));
49                        markdown.push_str(&format!(
50                            "](file://#diagnostic-{buffer_id}-{group_id}-{ix})\n",
51                        ))
52                    }
53                }
54
55                results.push(DiagnosticBlock {
56                    initial_range: primary.range.clone(),
57                    severity: primary.diagnostic.severity,
58                    diagnostics_editor: diagnostics_editor.clone(),
59                    copy_message: primary.diagnostic.message.clone().into(),
60                    markdown: cx.new(|cx| {
61                        Markdown::new(markdown.into(), language_registry.clone(), None, cx)
62                    }),
63                });
64            } else {
65                append_source_and_code(&mut markdown, entry.diagnostic);
66
67                if entry.range.start.row.abs_diff(primary.range.start.row) >= 5 {
68                    markdown.push_str(&format!(
69                        " ([back](file://#diagnostic-{buffer_id}-{group_id}-{primary_ix}))"
70                    ));
71                }
72                results.push(DiagnosticBlock {
73                    initial_range: entry.range.clone(),
74                    severity: entry.diagnostic.severity,
75                    diagnostics_editor: diagnostics_editor.clone(),
76                    copy_message: entry.diagnostic.message.clone().into(),
77                    markdown: cx.new(|cx| {
78                        Markdown::new(markdown.into(), language_registry.clone(), None, cx)
79                    }),
80                });
81            }
82        }
83
84        results
85    }
86
87    fn markdown(diagnostic: &Diagnostic) -> String {
88        let mut markdown = String::new();
89
90        if let Some(md) = &diagnostic.markdown {
91            markdown.push_str(md);
92        } else {
93            markdown.push_str(&Markdown::escape(&diagnostic.message));
94        };
95        markdown
96    }
97}
98
99fn append_source_and_code(markdown: &mut String, diagnostic: &Diagnostic) {
100    if diagnostic.source.is_none() && diagnostic.code.is_none() {
101        return;
102    }
103    markdown.push_str(" (");
104    if let Some(source) = diagnostic.source.as_ref() {
105        markdown.push_str(&Markdown::escape(source));
106    }
107    if diagnostic.source.is_some() && diagnostic.code.is_some() {
108        markdown.push(' ');
109    }
110    if let Some(code) = diagnostic.code.as_ref() {
111        if let Some(description) = diagnostic.code_description.as_ref() {
112            markdown.push('[');
113            markdown.push_str(&Markdown::escape(&code.to_string()));
114            markdown.push_str("](");
115            markdown.push_str(&Markdown::escape(description.as_ref()));
116            markdown.push(')');
117        } else {
118            markdown.push_str(&Markdown::escape(&code.to_string()));
119        }
120    }
121    markdown.push(')');
122}
123
124impl editor::DiagnosticRenderer for DiagnosticRenderer {
125    fn render_group(
126        &self,
127        diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
128        buffer_id: BufferId,
129        snapshot: EditorSnapshot,
130        editor: WeakEntity<Editor>,
131        language_registry: Option<Arc<LanguageRegistry>>,
132        cx: &mut App,
133    ) -> Vec<BlockProperties<Anchor>> {
134        let blocks = Self::diagnostic_blocks_for_group(
135            diagnostic_group,
136            buffer_id,
137            None,
138            language_registry,
139            cx,
140        );
141
142        blocks
143            .into_iter()
144            .map(|block| {
145                let editor = editor.clone();
146                BlockProperties {
147                    placement: BlockPlacement::Near(
148                        snapshot
149                            .buffer_snapshot()
150                            .anchor_after(block.initial_range.start),
151                    ),
152                    height: Some(1),
153                    style: BlockStyle::Flex,
154                    render: Arc::new(move |bcx| block.render_block(editor.clone(), bcx)),
155                    priority: 1,
156                }
157            })
158            .collect()
159    }
160
161    fn render_hover(
162        &self,
163        diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
164        range: Range<Point>,
165        buffer_id: BufferId,
166        language_registry: Option<Arc<LanguageRegistry>>,
167        cx: &mut App,
168    ) -> Option<Entity<Markdown>> {
169        let blocks = Self::diagnostic_blocks_for_group(
170            diagnostic_group,
171            buffer_id,
172            None,
173            language_registry,
174            cx,
175        );
176        blocks
177            .into_iter()
178            .find_map(|block| (block.initial_range == range).then(|| block.markdown))
179    }
180
181    fn open_link(
182        &self,
183        editor: &mut Editor,
184        link: SharedString,
185        window: &mut Window,
186        cx: &mut Context<Editor>,
187    ) {
188        DiagnosticBlock::open_link(editor, &None, link, window, cx);
189    }
190}
191
192#[derive(Clone)]
193pub(crate) struct DiagnosticBlock {
194    pub(crate) initial_range: Range<Point>,
195    pub(crate) severity: DiagnosticSeverity,
196    pub(crate) markdown: Entity<Markdown>,
197    pub(crate) diagnostics_editor: Option<Arc<dyn DiagnosticsToolbarEditor>>,
198    pub(crate) copy_message: SharedString,
199}
200
201impl DiagnosticBlock {
202    pub fn render_block(&self, editor: WeakEntity<Editor>, bcx: &BlockContext) -> AnyElement {
203        let cx = &bcx.app;
204        let status_colors = cx.theme().status();
205
206        let max_width = bcx.em_width * 120.;
207
208        let (background_color, border_color) = match self.severity {
209            DiagnosticSeverity::ERROR => (status_colors.error_background, status_colors.error),
210            DiagnosticSeverity::WARNING => {
211                (status_colors.warning_background, status_colors.warning)
212            }
213            DiagnosticSeverity::INFORMATION => (status_colors.info_background, status_colors.info),
214            DiagnosticSeverity::HINT => (status_colors.hint_background, status_colors.hint),
215            _ => (status_colors.ignored_background, status_colors.ignored),
216        };
217        let settings = ThemeSettings::get_global(cx);
218        let editor_line_height = (settings.line_height() * settings.buffer_font_size(cx)).round();
219        let line_height = editor_line_height;
220        let diagnostics_editor = self.diagnostics_editor.clone();
221
222        let copy_button_id = format!(
223            "copy-diagnostic-{}-{}-{}-{}",
224            self.initial_range.start.row,
225            self.initial_range.start.column,
226            self.initial_range.end.row,
227            self.initial_range.end.column
228        );
229
230        h_flex()
231            .max_w(max_width)
232            .pl_1p5()
233            .pr_0p5()
234            .items_start()
235            .gap_1()
236            .border_l_2()
237            .line_height(line_height)
238            .bg(background_color)
239            .border_color(border_color)
240            .child(
241                div().flex_1().min_w_0().child(
242                    MarkdownElement::new(
243                        self.markdown.clone(),
244                        diagnostics_markdown_style(bcx.window, cx),
245                    )
246                    .code_block_renderer(markdown::CodeBlockRenderer::Default {
247                        copy_button_visibility: CopyButtonVisibility::Hidden,
248                        wrap_button_visibility: markdown::WrapButtonVisibility::Hidden,
249                        border: false,
250                    })
251                    .on_url_click({
252                        move |link, window, cx| {
253                            editor
254                                .update(cx, |editor, cx| {
255                                    Self::open_link(editor, &diagnostics_editor, link, window, cx)
256                                })
257                                .ok();
258                        }
259                    }),
260                ),
261            )
262            .child(
263                CopyButton::new(copy_button_id, self.copy_message.clone())
264                    .tooltip_label("Copy Diagnostic"),
265            )
266            .into_any_element()
267    }
268
269    pub fn open_link(
270        editor: &mut Editor,
271        diagnostics_editor: &Option<Arc<dyn DiagnosticsToolbarEditor>>,
272        link: SharedString,
273        window: &mut Window,
274        cx: &mut Context<Editor>,
275    ) {
276        let Some(diagnostic_link) = link.strip_prefix("file://#diagnostic-") else {
277            let workspace = editor.workspace();
278            editor::hover_popover::open_markdown_url(workspace, link, window, cx);
279            return;
280        };
281        let Some((buffer_id, group_id, ix)) = maybe!({
282            let mut parts = diagnostic_link.split('-');
283            let buffer_id: u64 = parts.next()?.parse().ok()?;
284            let group_id: usize = parts.next()?.parse().ok()?;
285            let ix: usize = parts.next()?.parse().ok()?;
286            Some((BufferId::new(buffer_id).ok()?, group_id, ix))
287        }) else {
288            return;
289        };
290
291        if let Some(diagnostics_editor) = diagnostics_editor {
292            if let Some(diagnostic) = diagnostics_editor
293                .get_diagnostics_for_buffer(buffer_id, cx)
294                .into_iter()
295                .filter(|d| d.diagnostic.group_id == group_id)
296                .nth(ix)
297            {
298                let multibuffer = editor.buffer().read(cx);
299                if let Some(anchor_range) = multibuffer
300                    .snapshot(cx)
301                    .buffer_anchor_range_to_anchor_range(diagnostic.range)
302                {
303                    Self::jump_to(editor, anchor_range, window, cx);
304                    return;
305                }
306            }
307        } else if let Some(diagnostic) = editor
308            .snapshot(window, cx)
309            .buffer_snapshot()
310            .diagnostic_group(buffer_id, group_id)
311            .nth(ix)
312        {
313            Self::jump_to(editor, diagnostic.range, window, cx)
314        };
315    }
316
317    fn jump_to<I: ToOffset>(
318        editor: &mut Editor,
319        range: Range<I>,
320        window: &mut Window,
321        cx: &mut Context<Editor>,
322    ) {
323        let snapshot = &editor.buffer().read(cx).snapshot(cx);
324        let range = range.start.to_offset(snapshot)..range.end.to_offset(snapshot);
325
326        editor.unfold_ranges(&[range.start..range.end], true, false, cx);
327        editor.change_selections(Default::default(), window, cx, |s| {
328            s.select_ranges([range.start..range.start]);
329        });
330        window.focus(&editor.focus_handle(cx), cx);
331    }
332}
333
Served at tenant.openagents/omega Member data and write actions are omitted.