Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:58:45.497Z 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

diagnostics.rs

254 lines · 8.1 KB · rust
1use anyhow::Result;
2use gpui::{App, AppContext as _, Entity, Task};
3use language::{Anchor, BufferSnapshot, DiagnosticEntryRef, DiagnosticSeverity, ToOffset};
4use multi_buffer::MultiBuffer;
5use project::{DiagnosticSummary, Project};
6use rope::Point;
7use std::{fmt::Write, ops::RangeInclusive, path::Path};
8use text::OffsetRangeExt;
9use util::ResultExt;
10use util::paths::PathMatcher;
11
12pub fn codeblock_fence_for_path(
13    path: Option<&str>,
14    row_range: Option<RangeInclusive<u32>>,
15) -> String {
16    let mut text = String::new();
17    write!(text, "```").unwrap();
18
19    if let Some(path) = path {
20        if let Some(extension) = Path::new(path).extension().and_then(|ext| ext.to_str()) {
21            write!(text, "{} ", extension).unwrap();
22        }
23
24        write!(text, "{path}").unwrap();
25    } else {
26        write!(text, "{}", MultiBuffer::DEFAULT_TITLE).unwrap();
27    }
28
29    if let Some(row_range) = row_range {
30        write!(text, ":{}-{}", row_range.start() + 1, row_range.end() + 1).unwrap();
31    }
32
33    text.push('\n');
34    text
35}
36
37pub struct DiagnosticsOptions {
38    pub include_errors: bool,
39    pub include_warnings: bool,
40    pub path_matcher: Option<PathMatcher>,
41}
42
43/// Collects project diagnostics into a formatted string.
44///
45/// Returns `None` if no matching diagnostics were found.
46pub fn collect_diagnostics(
47    project: Entity<Project>,
48    options: DiagnosticsOptions,
49    cx: &mut App,
50) -> Task<Result<Option<String>>> {
51    let path_style = project.read(cx).path_style(cx);
52    let glob_is_exact_file_match = if let Some(path) = options
53        .path_matcher
54        .as_ref()
55        .and_then(|pm| pm.sources().next())
56    {
57        project
58            .read(cx)
59            .find_project_path(Path::new(path), cx)
60            .is_some()
61    } else {
62        false
63    };
64
65    let project_handle = project.downgrade();
66    let diagnostic_summaries: Vec<_> = project
67        .read(cx)
68        .diagnostic_summaries(false, cx)
69        .flat_map(|(path, _, summary)| {
70            let worktree = project.read(cx).worktree_for_id(path.worktree_id, cx)?;
71            let full_path = worktree.read(cx).root_name().join(&path.path);
72            Some((path, full_path, summary))
73        })
74        .collect();
75
76    cx.spawn(async move |cx| {
77        let error_source = if let Some(path_matcher) = &options.path_matcher {
78            debug_assert_eq!(path_matcher.sources().count(), 1);
79            Some(path_matcher.sources().next().unwrap_or_default())
80        } else {
81            None
82        };
83
84        let mut text = String::new();
85        if let Some(error_source) = error_source.as_ref() {
86            writeln!(text, "diagnostics: {}", error_source).unwrap();
87        } else {
88            writeln!(text, "diagnostics").unwrap();
89        }
90
91        let mut found_any_diagnostics = false;
92        let mut project_summary = DiagnosticSummary::default();
93        for (project_path, path, summary) in diagnostic_summaries {
94            if let Some(path_matcher) = &options.path_matcher
95                && !path_matcher.is_match(&path)
96            {
97                continue;
98            }
99
100            let has_errors = options.include_errors && summary.error_count > 0;
101            let has_warnings = options.include_warnings && summary.warning_count > 0;
102            if !has_errors && !has_warnings {
103                continue;
104            }
105
106            if options.include_errors {
107                project_summary.error_count += summary.error_count;
108            }
109            if options.include_warnings {
110                project_summary.warning_count += summary.warning_count;
111            }
112
113            let file_path = path.display(path_style).to_string();
114            if !glob_is_exact_file_match {
115                writeln!(&mut text, "{file_path}").unwrap();
116            }
117
118            if let Some(buffer) = project_handle
119                .update(cx, |project, cx| project.open_buffer(project_path, cx))?
120                .await
121                .log_err()
122            {
123                let snapshot = cx.read_entity(&buffer, |buffer, _| buffer.snapshot());
124                if collect_buffer_diagnostics(
125                    &mut text,
126                    &snapshot,
127                    options.include_warnings,
128                    options.include_errors,
129                ) {
130                    found_any_diagnostics = true;
131                }
132            }
133        }
134
135        if !found_any_diagnostics {
136            return Ok(None);
137        }
138
139        let mut label = String::new();
140        label.push_str("Diagnostics");
141        if let Some(source) = error_source {
142            write!(label, " ({})", source).unwrap();
143        }
144
145        if project_summary.error_count > 0 || project_summary.warning_count > 0 {
146            label.push(':');
147
148            if project_summary.error_count > 0 {
149                write!(label, " {} errors", project_summary.error_count).unwrap();
150                if project_summary.warning_count > 0 {
151                    label.push(',');
152                }
153            }
154
155            if project_summary.warning_count > 0 {
156                write!(label, " {} warnings", project_summary.warning_count).unwrap();
157            }
158        }
159
160        // Prepend the summary label to the output.
161        text.insert_str(0, &format!("{label}\n"));
162
163        Ok(Some(text))
164    })
165}
166
167/// Collects diagnostics from a buffer snapshot into the text output.
168///
169/// Returns `true` if any diagnostics were written.
170fn collect_buffer_diagnostics(
171    text: &mut String,
172    snapshot: &BufferSnapshot,
173    include_warnings: bool,
174    include_errors: bool,
175) -> bool {
176    let mut found_any = false;
177    for (_, group) in snapshot.diagnostic_groups(None) {
178        let entry = &group.entries[group.primary_ix];
179        if collect_diagnostic(text, entry, snapshot, include_warnings, include_errors) {
180            found_any = true;
181        }
182    }
183    found_any
184}
185
186/// Formats a single diagnostic entry as a code excerpt with the diagnostic message.
187///
188/// Returns `true` if the diagnostic was written (i.e. it matched severity filters).
189fn collect_diagnostic(
190    text: &mut String,
191    entry: &DiagnosticEntryRef<'_, Anchor>,
192    snapshot: &BufferSnapshot,
193    include_warnings: bool,
194    include_errors: bool,
195) -> bool {
196    const EXCERPT_EXPANSION_SIZE: u32 = 2;
197    const MAX_MESSAGE_LENGTH: usize = 2000;
198
199    let ty = match entry.diagnostic.severity {
200        DiagnosticSeverity::WARNING => {
201            if !include_warnings {
202                return false;
203            }
204            "warning"
205        }
206        DiagnosticSeverity::ERROR => {
207            if !include_errors {
208                return false;
209            }
210            "error"
211        }
212        _ => return false,
213    };
214
215    let range = entry.range.to_point(snapshot);
216    let diagnostic_row_number = range.start.row + 1;
217
218    let start_row = range.start.row.saturating_sub(EXCERPT_EXPANSION_SIZE);
219    let end_row = (range.end.row + EXCERPT_EXPANSION_SIZE).min(snapshot.max_point().row) + 1;
220    let excerpt_range =
221        Point::new(start_row, 0).to_offset(snapshot)..Point::new(end_row, 0).to_offset(snapshot);
222
223    text.push_str("```");
224    if let Some(language_name) = snapshot.language().map(|l| l.code_fence_block_name()) {
225        text.push_str(&language_name);
226    }
227    text.push('\n');
228
229    let mut buffer_text = String::new();
230    for chunk in snapshot.text_for_range(excerpt_range) {
231        buffer_text.push_str(chunk);
232    }
233
234    for (i, line) in buffer_text.lines().enumerate() {
235        let line_number = start_row + i as u32 + 1;
236        writeln!(text, "{}", line).unwrap();
237
238        if line_number == diagnostic_row_number {
239            text.push_str("//");
240            let marker_start = text.len();
241            write!(text, " {}: ", ty).unwrap();
242            let padding = text.len() - marker_start;
243
244            let message = util::truncate(&entry.diagnostic.message, MAX_MESSAGE_LENGTH)
245                .replace('\n', format!("\n//{:padding$}", "").as_str());
246
247            writeln!(text, "{message}").unwrap();
248        }
249    }
250
251    writeln!(text, "```").unwrap();
252    true
253}
254
Served at tenant.openagents/omega Member data and write actions are omitted.