From d71fe4cc7f8a1f1015c821070b2a9aff03f5c23c Mon Sep 17 00:00:00 2001 From: Mikhail Pertsev Date: Tue, 20 Jan 2026 14:19:24 +0100 Subject: [PATCH] agent_ui: Add `@diagnostics` mention to the thread (#42270) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #31351 # Diagnostics Mention in New Threads ## Overview Adds the `@diagnostics` mention to the new Agent Panel threads so users can inject current LSP diagnostics (errors by default) without switching to a text thread. The diagnostics mention is fully integrated into ACP mention parsing, the context picker, and the message-editor pipeline so it round-trips cleanly and shows up in the standard `@` menu. ## Context - **Request:** bring `/diagnostics` parity to the “New Thread” assistant experience. - **Scope:** diagnostics only; `/terminal` mention would be implemented in a separate PR. - **Docs:** updated Agent Panel docs + changelog. ## Implementation Details 1. **Mention plumbing** - Added `MentionUri::Diagnostics` to `acp_thread`, including parsing (`zed:///agent/diagnostics?include_warnings=true`) and icon/name metadata. - Tests ensure diagnostics links round-trip via Markdown mention serialization. 2. **Context picker / completion** - New `ContextPickerMode::Diagnostics` exposes an `@diagnostics` entry in the mention menu. - Completions turn `@diagnostics` into a fully fledged mention, reusing the existing confirmation pipeline. 3. **Message editor + thread serialization** - Resolving the mention calls the existing diagnostics collector from `assistant_slash_commands`, embedding the tool output inline with other context blocks (``). - Thread-link handling ignores diagnostics backlinks so clicking them doesn’t try to reopen nonexistent resources. # How it looks image Release Notes: - Allow mentioning diagnostics in the agent panel via `@diagnostics` --------- Co-authored-by: Bennet Bo Fenner --- crates/acp_thread/src/mention.rs | 75 ++++++ crates/agent/src/thread.rs | 12 + crates/agent_ui/src/acp/message_editor.rs | 6 +- crates/agent_ui/src/acp/thread_view.rs | 1 + crates/agent_ui/src/completion_provider.rs | 247 +++++++++++++++++- crates/agent_ui/src/mention_set.rs | 37 ++- .../src/diagnostics_command.rs | 43 ++- docs/src/ai/agent-panel.md | 2 +- 8 files changed, 401 insertions(+), 22 deletions(-) diff --git a/crates/acp_thread/src/mention.rs b/crates/acp_thread/src/mention.rs index 2de3525cb84ec084cfc10cd612fa8a62000b6d56..2d34ffc98e2321e9feb9a0f43316da09f2403241 100644 --- a/crates/acp_thread/src/mention.rs +++ b/crates/acp_thread/src/mention.rs @@ -40,6 +40,12 @@ pub enum MentionUri { id: PromptId, name: String, }, + Diagnostics { + #[serde(default = "default_include_errors")] + include_errors: bool, + #[serde(default)] + include_warnings: bool, + }, Selection { #[serde(default, skip_serializing_if = "Option::is_none")] abs_path: Option, @@ -135,6 +141,20 @@ impl MentionUri { id: rule_id.into(), name, }) + } else if path == "/agent/diagnostics" { + let mut include_errors = default_include_errors(); + let mut include_warnings = false; + for (key, value) in url.query_pairs() { + match key.as_ref() { + "include_warnings" => include_warnings = value == "true", + "include_errors" => include_errors = value == "true", + _ => bail!("invalid query parameter"), + } + } + Ok(Self::Diagnostics { + include_errors, + include_warnings, + }) } else if path.starts_with("/agent/pasted-image") { Ok(Self::PastedImage) } else if path.starts_with("/agent/untitled-buffer") { @@ -200,6 +220,7 @@ impl MentionUri { MentionUri::Thread { name, .. } => name.clone(), MentionUri::TextThread { name, .. } => name.clone(), MentionUri::Rule { name, .. } => name.clone(), + MentionUri::Diagnostics { .. } => "Diagnostics".to_string(), MentionUri::Selection { abs_path: path, line_range, @@ -221,6 +242,7 @@ impl MentionUri { MentionUri::Thread { .. } => IconName::Thread.path().into(), MentionUri::TextThread { .. } => IconName::Thread.path().into(), MentionUri::Rule { .. } => IconName::Reader.path().into(), + MentionUri::Diagnostics { .. } => IconName::Warning.path().into(), MentionUri::Selection { .. } => IconName::Reader.path().into(), MentionUri::Fetch { .. } => IconName::ToolWeb.path().into(), } @@ -299,6 +321,21 @@ impl MentionUri { url.query_pairs_mut().append_pair("name", name); url } + MentionUri::Diagnostics { + include_errors, + include_warnings, + } => { + let mut url = Url::parse("zed:///").unwrap(); + url.set_path("/agent/diagnostics"); + if *include_warnings { + url.query_pairs_mut() + .append_pair("include_warnings", "true"); + } + if !include_errors { + url.query_pairs_mut().append_pair("include_errors", "false"); + } + url + } MentionUri::Fetch { url } => url.clone(), } } @@ -312,6 +349,10 @@ impl fmt::Display for MentionLink<'_> { } } +fn default_include_errors() -> bool { + true +} + fn single_query_param(url: &Url, name: &'static str) -> Result> { let pairs = url.query_pairs().collect::>(); match pairs.as_slice() { @@ -504,6 +545,40 @@ mod tests { assert_eq!(parsed.to_uri().to_string(), https_uri); } + #[test] + fn test_parse_diagnostics_uri() { + let uri = "zed:///agent/diagnostics?include_warnings=true"; + let parsed = MentionUri::parse(uri, PathStyle::local()).unwrap(); + match &parsed { + MentionUri::Diagnostics { + include_errors, + include_warnings, + } => { + assert!(include_errors); + assert!(include_warnings); + } + _ => panic!("Expected Diagnostics variant"), + } + assert_eq!(parsed.to_uri().to_string(), uri); + } + + #[test] + fn test_parse_diagnostics_uri_warnings_only() { + let uri = "zed:///agent/diagnostics?include_warnings=true&include_errors=false"; + let parsed = MentionUri::parse(uri, PathStyle::local()).unwrap(); + match &parsed { + MentionUri::Diagnostics { + include_errors, + include_warnings, + } => { + assert!(!include_errors); + assert!(include_warnings); + } + _ => panic!("Expected Diagnostics variant"), + } + assert_eq!(parsed.to_uri().to_string(), uri); + } + #[test] fn test_invalid_scheme() { assert!(MentionUri::parse("ftp://example.com", PathStyle::local()).is_err()); diff --git a/crates/agent/src/thread.rs b/crates/agent/src/thread.rs index c403258514febc147a11b25e1f82e80ad81c50bf..a052b4a6ae4eaf858ec859265845fac2be4ba971 100644 --- a/crates/agent/src/thread.rs +++ b/crates/agent/src/thread.rs @@ -223,6 +223,7 @@ impl UserMessage { const OPEN_FETCH_TAG: &str = ""; const OPEN_RULES_TAG: &str = "\nThe user has specified the following rules that should be applied:\n"; + const OPEN_DIAGNOSTICS_TAG: &str = ""; let mut file_context = OPEN_FILES_TAG.to_string(); let mut directory_context = OPEN_DIRECTORIES_TAG.to_string(); @@ -231,6 +232,7 @@ impl UserMessage { let mut thread_context = OPEN_THREADS_TAG.to_string(); let mut fetch_context = OPEN_FETCH_TAG.to_string(); let mut rules_context = OPEN_RULES_TAG.to_string(); + let mut diagnostics_context = OPEN_DIAGNOSTICS_TAG.to_string(); for chunk in &self.content { let chunk = match chunk { @@ -312,6 +314,9 @@ impl UserMessage { MentionUri::Fetch { url } => { write!(&mut fetch_context, "\nFetch: {}\n\n{}", url, content).ok(); } + MentionUri::Diagnostics { .. } => { + write!(&mut diagnostics_context, "\n{}\n", content).ok(); + } } language_model::MessageContent::Text(uri.as_link().to_string()) @@ -372,6 +377,13 @@ impl UserMessage { .push(language_model::MessageContent::Text(rules_context)); } + if diagnostics_context.len() > OPEN_DIAGNOSTICS_TAG.len() { + diagnostics_context.push_str("\n"); + message + .content + .push(language_model::MessageContent::Text(diagnostics_context)); + } + if message.content.len() > len_before_context { message.content.insert( len_before_context, diff --git a/crates/agent_ui/src/acp/message_editor.rs b/crates/agent_ui/src/acp/message_editor.rs index e37baaf07d464ea6a24541fd989e558d481f545d..c3a17a25986b80d7855af45a4dccc3dfe93fff39 100644 --- a/crates/agent_ui/src/acp/message_editor.rs +++ b/crates/agent_ui/src/acp/message_editor.rs @@ -74,7 +74,11 @@ impl PromptCompletionProviderDelegate for Entity { if self.read(cx).thread_store.is_some() { supported.push(PromptContextType::Thread); } - supported.extend(&[PromptContextType::Fetch, PromptContextType::Rules]); + supported.extend(&[ + PromptContextType::Diagnostics, + PromptContextType::Fetch, + PromptContextType::Rules, + ]); } supported } diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index f831083b8a2ad82b95d98635270b2f933ce96b44..0b4be20702f65b806d6084ea0d09e9740516d5f4 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -6574,6 +6574,7 @@ impl AcpThreadView { MentionUri::Fetch { url } => { cx.open_url(url.as_str()); } + MentionUri::Diagnostics { .. } => {} }) } else { cx.open_url(&url); diff --git a/crates/agent_ui/src/completion_provider.rs b/crates/agent_ui/src/completion_provider.rs index b5f08ec0aeee597f89654debe1dbc2838f8ac750..0e97afade70c2ca2346caad4abdeac27b197406e 100644 --- a/crates/agent_ui/src/completion_provider.rs +++ b/crates/agent_ui/src/completion_provider.rs @@ -17,7 +17,7 @@ use lsp::CompletionContext; use ordered_float::OrderedFloat; use project::lsp_store::{CompletionDocumentation, SymbolLocation}; use project::{ - Completion, CompletionDisplayOptions, CompletionIntent, CompletionResponse, + Completion, CompletionDisplayOptions, CompletionIntent, CompletionResponse, DiagnosticSummary, PathMatchCandidateSet, Project, ProjectPath, Symbol, WorktreeId, }; use prompt_store::{PromptStore, UserPromptId}; @@ -55,6 +55,7 @@ pub(crate) enum PromptContextType { Fetch, Thread, Rules, + Diagnostics, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -92,6 +93,7 @@ impl TryFrom<&str> for PromptContextType { "fetch" => Ok(Self::Fetch), "thread" => Ok(Self::Thread), "rule" => Ok(Self::Rules), + "diagnostics" => Ok(Self::Diagnostics), _ => Err(format!("Invalid context picker mode: {}", value)), } } @@ -105,6 +107,7 @@ impl PromptContextType { Self::Fetch => "fetch", Self::Thread => "thread", Self::Rules => "rule", + Self::Diagnostics => "diagnostics", } } @@ -115,6 +118,7 @@ impl PromptContextType { Self::Fetch => "Fetch", Self::Thread => "Threads", Self::Rules => "Rules", + Self::Diagnostics => "Diagnostics", } } @@ -125,6 +129,7 @@ impl PromptContextType { Self::Fetch => IconName::ToolWeb, Self::Thread => IconName::Thread, Self::Rules => IconName::Reader, + Self::Diagnostics => IconName::Warning, } } } @@ -583,6 +588,103 @@ impl PromptCompletionProvider { }) } + fn completion_for_diagnostics( + source_range: Range, + source: Arc, + editor: WeakEntity, + mention_set: WeakEntity, + workspace: Entity, + cx: &mut App, + ) -> Vec { + let summary = workspace + .read(cx) + .project() + .read(cx) + .diagnostic_summary(false, cx); + if summary.error_count == 0 && summary.warning_count == 0 { + return Vec::new(); + } + let icon_path = MentionUri::Diagnostics { + include_errors: true, + include_warnings: false, + } + .icon_path(cx); + + let mut completions = Vec::new(); + + let cases = [ + (summary.error_count > 0, true, false), + (summary.warning_count > 0, false, true), + ( + summary.error_count > 0 && summary.warning_count > 0, + true, + true, + ), + ]; + + for (condition, include_errors, include_warnings) in cases { + if condition { + completions.push(Self::build_diagnostics_completion( + diagnostics_submenu_label(summary, include_errors, include_warnings), + source_range.clone(), + source.clone(), + editor.clone(), + mention_set.clone(), + workspace.clone(), + icon_path.clone(), + include_errors, + include_warnings, + summary, + )); + } + } + + completions + } + + fn build_diagnostics_completion( + menu_label: String, + source_range: Range, + source: Arc, + editor: WeakEntity, + mention_set: WeakEntity, + workspace: Entity, + icon_path: SharedString, + include_errors: bool, + include_warnings: bool, + summary: DiagnosticSummary, + ) -> Completion { + let uri = MentionUri::Diagnostics { + include_errors, + include_warnings, + }; + let crease_text = diagnostics_crease_label(summary, include_errors, include_warnings); + let display_text = format!("@{}", crease_text); + let new_text = format!("[{}]({}) ", display_text, uri.to_uri()); + let new_text_len = new_text.len(); + Completion { + replace_range: source_range.clone(), + new_text, + label: CodeLabel::plain(menu_label, None), + documentation: None, + source: project::CompletionSource::Custom, + icon_path: Some(icon_path), + match_start: None, + snippet_deduplication_key: None, + insert_text_mode: None, + confirm: Some(confirm_completion_callback( + crease_text, + source_range.start, + new_text_len - 1, + uri, + source, + editor, + mention_set, + workspace, + )), + } + } + fn search_slash_commands(&self, query: String, cx: &mut App) -> Task> { let commands = self.source.available_commands(cx); if commands.is_empty() { @@ -684,6 +786,8 @@ impl PromptCompletionProvider { } } + Some(PromptContextType::Diagnostics) => Task::ready(Vec::new()), + None if query.is_empty() => { let recent_task = self.recent_context_picker_entries(&workspace, cx); let entries = self @@ -879,6 +983,20 @@ impl PromptCompletionProvider { entries.push(PromptContextEntry::Mode(PromptContextType::Fetch)); } + if self + .source + .supports_context(PromptContextType::Diagnostics, cx) + { + let summary = workspace + .read(cx) + .project() + .read(cx) + .diagnostic_summary(false, cx); + if summary.error_count > 0 || summary.warning_count > 0 { + entries.push(PromptContextEntry::Mode(PromptContextType::Diagnostics)); + } + } + entries } } @@ -982,6 +1100,28 @@ impl CompletionProvider for PromptCompletio }) } PromptCompletion::Mention(MentionCompletion { mode, argument, .. }) => { + if let Some(PromptContextType::Diagnostics) = mode { + if argument.is_some() { + return Task::ready(Ok(Vec::new())); + } + + let completions = Self::completion_for_diagnostics( + source_range.clone(), + source.clone(), + editor.clone(), + mention_set.clone(), + workspace.clone(), + cx, + ); + if !completions.is_empty() { + return Task::ready(Ok(vec![CompletionResponse { + completions, + display_options: CompletionDisplayOptions::default(), + is_incomplete: false, + }])); + } + } + let query = argument.unwrap_or_default(); let search_task = self.search_mentions(mode, query, Arc::::default(), cx); @@ -1051,7 +1191,6 @@ impl CompletionProvider for PromptCompletio cx, ) } - Match::Symbol(SymbolMatch { symbol, .. }) => { Self::completion_for_symbol( symbol, @@ -1064,7 +1203,6 @@ impl CompletionProvider for PromptCompletio cx, ) } - Match::Thread(thread) => Some(Self::completion_for_thread( thread, source_range.clone(), @@ -1075,7 +1213,6 @@ impl CompletionProvider for PromptCompletio workspace.clone(), cx, )), - Match::RecentThread(thread) => Some(Self::completion_for_thread( thread, source_range.clone(), @@ -1086,7 +1223,6 @@ impl CompletionProvider for PromptCompletio workspace.clone(), cx, )), - Match::Rules(user_rules) => Some(Self::completion_for_rules( user_rules, source_range.clone(), @@ -1096,7 +1232,6 @@ impl CompletionProvider for PromptCompletio workspace.clone(), cx, )), - Match::Fetch(url) => Self::completion_for_fetch( source_range.clone(), url, @@ -1106,7 +1241,6 @@ impl CompletionProvider for PromptCompletio workspace.clone(), cx, ), - Match::Entry(EntryMatch { entry, .. }) => { Self::completion_for_entry( entry, @@ -1380,6 +1514,87 @@ impl MentionCompletion { } } +fn diagnostics_label( + summary: DiagnosticSummary, + include_errors: bool, + include_warnings: bool, +) -> String { + let mut parts = Vec::new(); + + if include_errors && summary.error_count > 0 { + parts.push(format!( + "{} {}", + summary.error_count, + pluralize("error", summary.error_count) + )); + } + + if include_warnings && summary.warning_count > 0 { + parts.push(format!( + "{} {}", + summary.warning_count, + pluralize("warning", summary.warning_count) + )); + } + + if parts.is_empty() { + return "Diagnostics".into(); + } + + let body = if parts.len() == 2 { + format!("{} and {}", parts[0], parts[1]) + } else { + parts + .pop() + .expect("at least one part present after non-empty check") + }; + + format!("Diagnostics: {body}") +} + +fn diagnostics_submenu_label( + summary: DiagnosticSummary, + include_errors: bool, + include_warnings: bool, +) -> String { + match (include_errors, include_warnings) { + (true, true) => format!( + "{} {} & {} {}", + summary.error_count, + pluralize("error", summary.error_count), + summary.warning_count, + pluralize("warning", summary.warning_count) + ), + (true, _) => format!( + "{} {}", + summary.error_count, + pluralize("error", summary.error_count) + ), + (_, true) => format!( + "{} {}", + summary.warning_count, + pluralize("warning", summary.warning_count) + ), + _ => "Diagnostics".into(), + } +} + +fn diagnostics_crease_label( + summary: DiagnosticSummary, + include_errors: bool, + include_warnings: bool, +) -> SharedString { + diagnostics_label(summary, include_errors, include_warnings).into() +} + +fn pluralize(noun: &str, count: usize) -> String { + if count == 1 { + noun.to_string() + } else { + format!("{noun}s") + } +} + pub(crate) fn search_files( query: String, cancellation_flag: Arc, @@ -1833,6 +2048,11 @@ mod tests { #[test] fn test_mention_completion_parse() { let supported_modes = vec![PromptContextType::File, PromptContextType::Symbol]; + let supported_modes_with_diagnostics = vec![ + PromptContextType::File, + PromptContextType::Symbol, + PromptContextType::Diagnostics, + ]; assert_eq!( MentionCompletion::try_parse("Lorem Ipsum", 0, &supported_modes), @@ -1945,6 +2165,19 @@ mod tests { }) ); + assert_eq!( + MentionCompletion::try_parse( + "Lorem @diagnostics", + 0, + &supported_modes_with_diagnostics + ), + Some(MentionCompletion { + source_range: 6..18, + mode: Some(PromptContextType::Diagnostics), + argument: None, + }) + ); + // Disallowed non-file mentions assert_eq!( MentionCompletion::try_parse("Lorem @symbol main", 0, &[PromptContextType::File]), diff --git a/crates/agent_ui/src/mention_set.rs b/crates/agent_ui/src/mention_set.rs index ef229e0bb53e91703d75c59a928e999ca6708fb7..16df6fe93f46b53e0f5aa928bc5f457313a88670 100644 --- a/crates/agent_ui/src/mention_set.rs +++ b/crates/agent_ui/src/mention_set.rs @@ -3,7 +3,7 @@ use agent::{ThreadStore, outline}; use agent_client_protocol as acp; use agent_servers::{AgentServer, AgentServerDelegate}; use anyhow::{Context as _, Result, anyhow}; -use assistant_slash_commands::codeblock_fence_for_path; +use assistant_slash_commands::{codeblock_fence_for_path, collect_diagnostics_output}; use collections::{HashMap, HashSet}; use editor::{ Anchor, Editor, EditorSnapshot, ExcerptId, FoldPlaceholder, ToOffset, @@ -235,6 +235,10 @@ impl MentionSet { .. } => self.confirm_mention_for_symbol(abs_path, line_range, cx), MentionUri::Rule { id, .. } => self.confirm_mention_for_rule(id, cx), + MentionUri::Diagnostics { + include_errors, + include_warnings, + } => self.confirm_mention_for_diagnostics(include_errors, include_warnings, cx), MentionUri::PastedImage => { debug_panic!("pasted image URI should not be included in completions"); Task::ready(Err(anyhow!( @@ -510,6 +514,37 @@ impl MentionSet { }) }) } + + fn confirm_mention_for_diagnostics( + &self, + include_errors: bool, + include_warnings: bool, + cx: &mut Context, + ) -> Task> { + let Some(project) = self.project.upgrade() else { + return Task::ready(Err(anyhow!("project not found"))); + }; + + let diagnostics_task = collect_diagnostics_output( + project, + assistant_slash_commands::Options { + include_errors, + include_warnings, + path_matcher: None, + }, + cx, + ); + cx.spawn(async move |_, _| { + let output = diagnostics_task.await?; + let content = output + .map(|output| output.text) + .unwrap_or_else(|| "No diagnostics found.".into()); + Ok(Mention::Text { + content, + tracked_buffers: Vec::new(), + }) + }) + } } #[cfg(test)] diff --git a/crates/assistant_slash_commands/src/diagnostics_command.rs b/crates/assistant_slash_commands/src/diagnostics_command.rs index 60f6d7c1a6e559028fd211f12d7408721ffcdea7..fcede522d020340fa382b6a6baed9b6c983a3029 100644 --- a/crates/assistant_slash_commands/src/diagnostics_command.rs +++ b/crates/assistant_slash_commands/src/diagnostics_command.rs @@ -188,7 +188,7 @@ impl SlashCommand for DiagnosticsSlashCommand { let path_style = project.read(cx).path_style(cx); let options = Options::parse(arguments, path_style); - let task = collect_diagnostics(project.clone(), options, cx); + let task = collect_diagnostics_output(project.clone(), options, cx); window.spawn(cx, async move |_| { task.await? @@ -198,10 +198,10 @@ impl SlashCommand for DiagnosticsSlashCommand { } } -#[derive(Default)] -struct Options { - include_warnings: bool, - path_matcher: Option, +pub struct Options { + pub include_errors: bool, + pub include_warnings: bool, + pub path_matcher: Option, } const INCLUDE_WARNINGS_ARGUMENT: &str = "--include-warnings"; @@ -218,6 +218,7 @@ impl Options { } } Self { + include_errors: true, include_warnings, path_matcher, } @@ -228,7 +229,7 @@ impl Options { } } -fn collect_diagnostics( +pub fn collect_diagnostics_output( project: Entity, options: Options, cx: &mut App, @@ -282,11 +283,17 @@ fn collect_diagnostics( continue; } - project_summary.error_count += summary.error_count; + let has_errors = options.include_errors && summary.error_count > 0; + let has_warnings = options.include_warnings && summary.warning_count > 0; + if !has_errors && !has_warnings { + continue; + } + + if options.include_errors { + project_summary.error_count += summary.error_count; + } if options.include_warnings { project_summary.warning_count += summary.warning_count; - } else if summary.error_count == 0 { - continue; } let last_end = output.text.len(); @@ -301,7 +308,12 @@ fn collect_diagnostics( .log_err() { let snapshot = cx.read_entity(&buffer, |buffer, _| buffer.snapshot()); - collect_buffer_diagnostics(&mut output, &snapshot, options.include_warnings); + collect_buffer_diagnostics( + &mut output, + &snapshot, + options.include_warnings, + options.include_errors, + ); } if !glob_is_exact_file_match { @@ -358,10 +370,11 @@ pub fn collect_buffer_diagnostics( output: &mut SlashCommandOutput, snapshot: &BufferSnapshot, include_warnings: bool, + include_errors: bool, ) { for (_, group) in snapshot.diagnostic_groups(None) { let entry = &group.entries[group.primary_ix]; - collect_diagnostic(output, entry, snapshot, include_warnings) + collect_diagnostic(output, entry, snapshot, include_warnings, include_errors) } } @@ -370,6 +383,7 @@ fn collect_diagnostic( entry: &DiagnosticEntryRef<'_, Anchor>, snapshot: &BufferSnapshot, include_warnings: bool, + include_errors: bool, ) { const EXCERPT_EXPANSION_SIZE: u32 = 2; const MAX_MESSAGE_LENGTH: usize = 2000; @@ -381,7 +395,12 @@ fn collect_diagnostic( } ("warning", IconName::Warning) } - DiagnosticSeverity::ERROR => ("error", IconName::XCircle), + DiagnosticSeverity::ERROR => { + if !include_errors { + return; + } + ("error", IconName::XCircle) + } _ => return, }; let prev_len = output.text.len(); diff --git a/docs/src/ai/agent-panel.md b/docs/src/ai/agent-panel.md index 1f11992ef1312e5cb2b8156d114efb50a8efb475..f17d09891dd696699ef5c1687ed2233fdf596428 100644 --- a/docs/src/ai/agent-panel.md +++ b/docs/src/ai/agent-panel.md @@ -85,7 +85,7 @@ You can turn this off, though, through the `agent.single_file_review` setting. Although Zed's agent is very efficient at reading through your code base to autonomously pick up relevant context, manually adding whatever would be useful to fulfill your prompt is still encouraged as a way to not only improve the AI's response quality but also to speed up its response time. In Zed's Agent Panel, all pieces of context are added as mentions in the panel's message editor. -You can type `@` to mention files, directories, symbols, previous threads, and rules files. +You can type `@` to mention files, directories, symbols, previous threads, rules files, and diagnostics. Copying images and pasting them in the panel's message editor is also supported.