From 03e0581ee87d58cf77b9ecdc6523900d1ca256b1 Mon Sep 17 00:00:00 2001 From: Charles McLaughlin Date: Sat, 1 Nov 2025 07:14:12 -0700 Subject: [PATCH 01/16] agent_ui: Show notifications also when the panel is hidden (#40942) Currently Zed only displays agent notifications (e.g. when the agent completes a task) if the user has switched apps and Zed is not in the foreground. This adds PR supports the scenario where the agent finishes a long-running task and the user is busy coding within Zed on something else. Releases Note: - If agent notifications are turned on, they will now also be displayed when the agent panel is hidden, in complement to them showing when the Zed window is in the background. --------- Co-authored-by: Danilo Leal --- crates/agent_ui/src/acp/thread_view.rs | 120 ++++++++++++++++++++++++- crates/agent_ui/src/agent_panel.rs | 19 ++++ 2 files changed, 137 insertions(+), 2 deletions(-) diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 3638faf9336f79d692f820df39266ab7b85360a8..0ae60ebe0df91c61eb5c968d5ee23ec18ef87187 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -4571,14 +4571,29 @@ impl AcpThreadView { window: &mut Window, cx: &mut Context, ) { - if window.is_window_active() || !self.notifications.is_empty() { + if !self.notifications.is_empty() { + return; + } + + let settings = AgentSettings::get_global(cx); + + let window_is_inactive = !window.is_window_active(); + let panel_is_hidden = self + .workspace + .upgrade() + .map(|workspace| AgentPanel::is_hidden(&workspace, cx)) + .unwrap_or(true); + + let should_notify = window_is_inactive || panel_is_hidden; + + if !should_notify { return; } // TODO: Change this once we have title summarization for external agents. let title = self.agent.name(); - match AgentSettings::get_global(cx).notify_when_agent_waiting { + match settings.notify_when_agent_waiting { NotifyWhenAgentWaiting::PrimaryScreen => { if let Some(primary) = cx.primary_display() { self.pop_up(icon, caption.into(), title, window, primary, cx); @@ -5892,6 +5907,107 @@ pub(crate) mod tests { ); } + #[gpui::test] + async fn test_notification_when_panel_hidden(cx: &mut TestAppContext) { + init_test(cx); + + let (thread_view, cx) = setup_thread_view(StubAgentServer::default_response(), cx).await; + + add_to_workspace(thread_view.clone(), cx); + + let message_editor = cx.read(|cx| thread_view.read(cx).message_editor.clone()); + + message_editor.update_in(cx, |editor, window, cx| { + editor.set_text("Hello", window, cx); + }); + + // Window is active (don't deactivate), but panel will be hidden + // Note: In the test environment, the panel is not actually added to the dock, + // so is_agent_panel_hidden will return true + + thread_view.update_in(cx, |thread_view, window, cx| { + thread_view.send(window, cx); + }); + + cx.run_until_parked(); + + // Should show notification because window is active but panel is hidden + assert!( + cx.windows() + .iter() + .any(|window| window.downcast::().is_some()), + "Expected notification when panel is hidden" + ); + } + + #[gpui::test] + async fn test_notification_still_works_when_window_inactive(cx: &mut TestAppContext) { + init_test(cx); + + let (thread_view, cx) = setup_thread_view(StubAgentServer::default_response(), cx).await; + + let message_editor = cx.read(|cx| thread_view.read(cx).message_editor.clone()); + message_editor.update_in(cx, |editor, window, cx| { + editor.set_text("Hello", window, cx); + }); + + // Deactivate window - should show notification regardless of setting + cx.deactivate_window(); + + thread_view.update_in(cx, |thread_view, window, cx| { + thread_view.send(window, cx); + }); + + cx.run_until_parked(); + + // Should still show notification when window is inactive (existing behavior) + assert!( + cx.windows() + .iter() + .any(|window| window.downcast::().is_some()), + "Expected notification when window is inactive" + ); + } + + #[gpui::test] + async fn test_notification_respects_never_setting(cx: &mut TestAppContext) { + init_test(cx); + + // Set notify_when_agent_waiting to Never + cx.update(|cx| { + AgentSettings::override_global( + AgentSettings { + notify_when_agent_waiting: NotifyWhenAgentWaiting::Never, + ..AgentSettings::get_global(cx).clone() + }, + cx, + ); + }); + + let (thread_view, cx) = setup_thread_view(StubAgentServer::default_response(), cx).await; + + let message_editor = cx.read(|cx| thread_view.read(cx).message_editor.clone()); + message_editor.update_in(cx, |editor, window, cx| { + editor.set_text("Hello", window, cx); + }); + + // Window is active + + thread_view.update_in(cx, |thread_view, window, cx| { + thread_view.send(window, cx); + }); + + cx.run_until_parked(); + + // Should NOT show notification because notify_when_agent_waiting is Never + assert!( + !cx.windows() + .iter() + .any(|window| window.downcast::().is_some()), + "Expected no notification when notify_when_agent_waiting is Never" + ); + } + async fn setup_thread_view( agent: impl AgentServer + 'static, cx: &mut TestAppContext, diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 997a2bec09aa2a0ae39909c909c7de80771c5055..173059ee535d4417cd0ff493842d889559b85ef4 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -729,6 +729,25 @@ impl AgentPanel { &self.context_server_registry } + pub fn is_hidden(workspace: &Entity, cx: &App) -> bool { + let workspace_read = workspace.read(cx); + + workspace_read + .panel::(cx) + .map(|panel| { + let panel_id = Entity::entity_id(&panel); + + let is_visible = workspace_read.all_docks().iter().any(|dock| { + dock.read(cx) + .visible_panel() + .is_some_and(|visible_panel| visible_panel.panel_id() == panel_id) + }); + + !is_visible + }) + .unwrap_or(true) + } + fn active_thread_view(&self) -> Option<&Entity> { match &self.active_view { ActiveView::ExternalAgentThread { thread_view, .. } => Some(thread_view), From d6b58bb948fdcd92b22abb65ebfcae07fd40f94b Mon Sep 17 00:00:00 2001 From: Mark Christiansen Date: Sun, 2 Nov 2025 01:14:21 +1100 Subject: [PATCH 02/16] agent_ui: Use agent font size tokens for thread markdown rendering (#41610) Release Notes: - N/A --- Previously, agent markdown rendering used hardcoded font sizes (TextSize::Default and TextSize::Small) which ignored the agent_ui_font_size and agent_buffer_font_size settings. This updates the markdown style to respect these settings. This pull request adds support for customizing the font size of code blocks in agent responses, making it possible to set a distinct font size for code within the agent panel. The changes ensure that if the new setting is not specified, the font size will fall back to the agent UI font size, maintaining consistent appearance. (I am a frontend developer without any Rust knowledge so this is co-authored with Claude Code) **Theme settings extension:** * Added a new `agent_buffer_code_font_size` setting to `ThemeSettingsContent`, `ThemeSettings`, and the default settings JSON, allowing users to specify the font size for code blocks in agent responses. [[1]](diffhunk://#diff-a3bba02a485aba48e8e9a9d85485332378aa4fe29a0c50d11ae801ecfa0a56a4R69-R72) [[2]](diffhunk://#diff-aed3a9217587d27844c57ac8aff4a749f1fb1fc5d54926ef5065bf85f8fd633aR118-R119) [[3]](diffhunk://#diff-42e01d7aacb60673842554e30970b4ddbbaee7a2ec2c6f2be1c0b08b0dd89631R82-R83) * Updated the VSCode import logic to recognize and import the new `agent_buffer_code_font_size` setting. **Font size application in agent UI:** * Modified the agent UI rendering logic in `thread_view.rs` to use the new `agent_buffer_code_font_size` for code blocks, and to fall back to the agent UI font size if unset. [[1]](diffhunk://#diff-f73942e8d4f8c4d4d173d57d7c58bb653c4bb6ae7079533ee501750cdca27d98L5584-R5584) [[2]](diffhunk://#diff-f73942e8d4f8c4d4d173d57d7c58bb653c4bb6ae7079533ee501750cdca27d98L5596-R5598) * Implemented a helper method in `ThemeSettings` to retrieve the code block font size, with fallback logic to ensure a value is always used. * Updated the settings application logic to propagate the new code block font size setting throughout the theme system. ### Example Screenshots ![Screenshot 2025-10-31 at 12 38 28](https://github.com/user-attachments/assets/cbc34232-ab1f-40bf-a006-689678380e47) ![Screenshot 2025-10-31 at 12 37 45](https://github.com/user-attachments/assets/372b5cf8-2df8-425a-b052-12136de7c6bd) --------- Co-authored-by: Danilo Leal --- crates/agent_ui/src/acp/thread_view.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 0ae60ebe0df91c61eb5c968d5ee23ec18ef87187..a4b3106fa9d9ded053ff2f33b720ec3b10512d01 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -5596,7 +5596,7 @@ fn default_markdown_style( let theme_settings = ThemeSettings::get_global(cx); let colors = cx.theme().colors(); - let buffer_font_size = TextSize::Small.rems(cx); + let buffer_font_size = theme_settings.agent_buffer_font_size(cx); let mut text_style = window.text_style(); let line_height = buffer_font_size * 1.75; @@ -5608,9 +5608,9 @@ fn default_markdown_style( }; let font_size = if buffer_font { - TextSize::Small.rems(cx) + theme_settings.agent_buffer_font_size(cx) } else { - TextSize::Default.rems(cx) + theme_settings.agent_ui_font_size(cx) }; let text_color = if muted_text { From 06bdb28517d085d5c7635dacc15d4731a1b97d66 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Sat, 1 Nov 2025 16:35:04 -0300 Subject: [PATCH 03/16] zeta cli: Add convert-example command (#41608) Adds a `convert-example` subcommand to the zeta cli that converts eval examples from/to `json`, `toml`, and `md` formats. Release Notes: - N/A --------- Co-authored-by: Max Brunsfeld --- Cargo.lock | 2 + .../src/cloud_zeta2_prompt.rs | 2 +- crates/zeta2/src/related_excerpts.rs | 2 +- crates/zeta_cli/Cargo.toml | 2 + crates/zeta_cli/src/example.rs | 355 ++++++++++++++++++ crates/zeta_cli/src/main.rs | 17 + 6 files changed, 378 insertions(+), 2 deletions(-) create mode 100644 crates/zeta_cli/src/example.rs diff --git a/Cargo.lock b/Cargo.lock index ec55e4af77f78a9476b147744a9973d758d0e630..c0eea670a77f03c4dbb5afdb7d1197b6d9b76159 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21757,6 +21757,7 @@ dependencies = [ "polars", "project", "prompt_store", + "pulldown-cmark 0.12.2", "release_channel", "reqwest_client", "serde", @@ -21766,6 +21767,7 @@ dependencies = [ "smol", "soa-rs", "terminal_view", + "toml 0.8.23", "util", "watch", "zeta", diff --git a/crates/cloud_zeta2_prompt/src/cloud_zeta2_prompt.rs b/crates/cloud_zeta2_prompt/src/cloud_zeta2_prompt.rs index a0df39b50eb6753397f5afd37aa30b71b853b9c5..6caf9941845146dc0c30c4606f677e5ec816c137 100644 --- a/crates/cloud_zeta2_prompt/src/cloud_zeta2_prompt.rs +++ b/crates/cloud_zeta2_prompt/src/cloud_zeta2_prompt.rs @@ -212,7 +212,7 @@ pub fn write_codeblock<'a>( include_line_numbers: bool, output: &'a mut String, ) { - writeln!(output, "`````path={}", path.display()).unwrap(); + writeln!(output, "`````{}", path.display()).unwrap(); write_excerpts( excerpts, sorted_insertions, diff --git a/crates/zeta2/src/related_excerpts.rs b/crates/zeta2/src/related_excerpts.rs index dd27992274ae2b25ec07e2a47dc8a60b46f5f3f2..44388251e32678ff8d1b3ce594ab35996b235759 100644 --- a/crates/zeta2/src/related_excerpts.rs +++ b/crates/zeta2/src/related_excerpts.rs @@ -64,7 +64,7 @@ const SEARCH_PROMPT: &str = indoc! {r#" ## Current cursor context - `````path={current_file_path} + `````{current_file_path} {cursor_excerpt} ````` diff --git a/crates/zeta_cli/Cargo.toml b/crates/zeta_cli/Cargo.toml index 19dafefbdcf8ed577a54e686b6b0c4ed90cf4512..a54298366614c3633cf527cc5746480e66c6caae 100644 --- a/crates/zeta_cli/Cargo.toml +++ b/crates/zeta_cli/Cargo.toml @@ -39,8 +39,10 @@ paths.workspace = true polars = { version = "0.51", features = ["lazy", "dtype-struct", "parquet"] } project.workspace = true prompt_store.workspace = true +pulldown-cmark.workspace = true release_channel.workspace = true reqwest_client.workspace = true +toml.workspace = true serde.workspace = true serde_json.workspace = true settings.workspace = true diff --git a/crates/zeta_cli/src/example.rs b/crates/zeta_cli/src/example.rs new file mode 100644 index 0000000000000000000000000000000000000000..de95bbe8d0c97df7c12ce04f75de35ed41a660e4 --- /dev/null +++ b/crates/zeta_cli/src/example.rs @@ -0,0 +1,355 @@ +use std::{ + borrow::Cow, + env, + fmt::{self, Display}, + fs, + io::Write, + mem, + path::{Path, PathBuf}, +}; + +use anyhow::{Context as _, Result}; +use clap::ValueEnum; +use gpui::http_client::Url; +use pulldown_cmark::CowStr; +use serde::{Deserialize, Serialize}; + +const CURSOR_POSITION_HEADING: &str = "Cursor Position"; +const EDIT_HISTORY_HEADING: &str = "Edit History"; +const EXPECTED_PATCH_HEADING: &str = "Expected Patch"; +const EXPECTED_EXCERPTS_HEADING: &str = "Expected Excerpts"; +const REPOSITORY_URL_FIELD: &str = "repository_url"; +const REVISION_FIELD: &str = "revision"; + +#[derive(Debug)] +pub struct NamedExample { + pub name: String, + pub example: Example, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Example { + pub repository_url: String, + pub revision: String, + pub cursor_path: PathBuf, + pub cursor_position: String, + pub edit_history: Vec, + pub expected_patch: String, + pub expected_excerpts: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ExpectedExcerpt { + path: PathBuf, + text: String, +} + +#[derive(ValueEnum, Debug, Clone)] +pub enum ExampleFormat { + Json, + Toml, + Md, +} + +impl NamedExample { + pub fn load(path: impl AsRef) -> Result { + let path = path.as_ref(); + let content = std::fs::read_to_string(path)?; + let ext = path.extension(); + + match ext.and_then(|s| s.to_str()) { + Some("json") => Ok(Self { + name: path.file_name().unwrap_or_default().display().to_string(), + example: serde_json::from_str(&content)?, + }), + Some("toml") => Ok(Self { + name: path.file_name().unwrap_or_default().display().to_string(), + example: toml::from_str(&content)?, + }), + Some("md") => Self::parse_md(&content), + Some(_) => { + anyhow::bail!("Unrecognized example extension: {}", ext.unwrap().display()); + } + None => { + anyhow::bail!( + "Failed to determine example type since the file does not have an extension." + ); + } + } + } + + pub fn parse_md(input: &str) -> Result { + use pulldown_cmark::{CodeBlockKind, Event, HeadingLevel, Parser, Tag, TagEnd}; + + let parser = Parser::new(input); + + let mut named = NamedExample { + name: String::new(), + example: Example { + repository_url: String::new(), + revision: String::new(), + cursor_path: PathBuf::new(), + cursor_position: String::new(), + edit_history: Vec::new(), + expected_patch: String::new(), + expected_excerpts: Vec::new(), + }, + }; + + let mut text = String::new(); + let mut current_section = String::new(); + let mut block_info: CowStr = "".into(); + + for event in parser { + match event { + Event::Text(line) => { + text.push_str(&line); + + if !named.name.is_empty() + && current_section.is_empty() + // in h1 section + && let Some((field, value)) = line.split_once('=') + { + match field.trim() { + REPOSITORY_URL_FIELD => { + named.example.repository_url = value.trim().to_string(); + } + REVISION_FIELD => { + named.example.revision = value.trim().to_string(); + } + _ => { + eprintln!("Warning: Unrecognized field `{field}`"); + } + } + } + } + Event::End(TagEnd::Heading(HeadingLevel::H1)) => { + if !named.name.is_empty() { + anyhow::bail!( + "Found multiple H1 headings. There should only be one with the name of the example." + ); + } + named.name = mem::take(&mut text); + } + Event::End(TagEnd::Heading(HeadingLevel::H2)) => { + current_section = mem::take(&mut text); + } + Event::End(TagEnd::Heading(level)) => { + anyhow::bail!("Unexpected heading level: {level}"); + } + Event::Start(Tag::CodeBlock(kind)) => { + match kind { + CodeBlockKind::Fenced(info) => { + block_info = info; + } + CodeBlockKind::Indented => { + anyhow::bail!("Unexpected indented codeblock"); + } + }; + } + Event::Start(_) => { + text.clear(); + block_info = "".into(); + } + Event::End(TagEnd::CodeBlock) => { + if current_section.eq_ignore_ascii_case(EDIT_HISTORY_HEADING) { + named.example.edit_history.push(mem::take(&mut text)); + } else if current_section.eq_ignore_ascii_case(CURSOR_POSITION_HEADING) { + let path = PathBuf::from(block_info.trim()); + named.example.cursor_path = path; + named.example.cursor_position = mem::take(&mut text); + } else if current_section.eq_ignore_ascii_case(EXPECTED_PATCH_HEADING) { + named.example.expected_patch = mem::take(&mut text); + } else if current_section.eq_ignore_ascii_case(EXPECTED_EXCERPTS_HEADING) { + let path = PathBuf::from(block_info.trim()); + named.example.expected_excerpts.push(ExpectedExcerpt { + path, + text: mem::take(&mut text), + }); + } else { + eprintln!("Warning: Unrecognized section `{current_section:?}`") + } + } + _ => {} + } + } + + if named.example.cursor_path.as_path() == Path::new("") + || named.example.cursor_position.is_empty() + { + anyhow::bail!("Missing cursor position codeblock"); + } + + Ok(named) + } + + pub fn write(&self, format: ExampleFormat, mut out: impl Write) -> Result<()> { + match format { + ExampleFormat::Json => Ok(serde_json::to_writer(out, &self.example)?), + ExampleFormat::Toml => { + Ok(out.write_all(toml::to_string_pretty(&self.example)?.as_bytes())?) + } + ExampleFormat::Md => Ok(write!(out, "{}", self)?), + } + } + + #[allow(unused)] + pub async fn setup_worktree(&self) -> Result { + let worktrees_dir = env::current_dir()?.join("target").join("zeta-worktrees"); + let repos_dir = env::current_dir()?.join("target").join("zeta-repos"); + fs::create_dir_all(&repos_dir)?; + fs::create_dir_all(&worktrees_dir)?; + + let (repo_owner, repo_name) = self.repo_name()?; + + let repo_dir = repos_dir.join(repo_owner.as_ref()).join(repo_name.as_ref()); + if !repo_dir.is_dir() { + fs::create_dir_all(&repo_dir)?; + run_git(&repo_dir, &["init"]).await?; + run_git( + &repo_dir, + &["remote", "add", "origin", &self.example.repository_url], + ) + .await?; + } + + run_git( + &repo_dir, + &["fetch", "--depth", "1", "origin", &self.example.revision], + ) + .await?; + + let worktree_path = worktrees_dir.join(&self.name); + + if worktree_path.is_dir() { + run_git(&worktree_path, &["clean", "--force", "-d"]).await?; + run_git(&worktree_path, &["reset", "--hard", "HEAD"]).await?; + run_git(&worktree_path, &["checkout", &self.example.revision]).await?; + } else { + let worktree_path_string = worktree_path.to_string_lossy(); + run_git( + &repo_dir, + &[ + "worktree", + "add", + "-f", + &worktree_path_string, + &self.example.revision, + ], + ) + .await?; + } + + Ok(worktree_path) + } + + #[allow(unused)] + fn repo_name(&self) -> Result<(Cow<'_, str>, Cow<'_, str>)> { + // git@github.com:owner/repo.git + if self.example.repository_url.contains('@') { + let (owner, repo) = self + .example + .repository_url + .split_once(':') + .context("expected : in git url")? + .1 + .split_once('/') + .context("expected / in git url")?; + Ok(( + Cow::Borrowed(owner), + Cow::Borrowed(repo.trim_end_matches(".git")), + )) + // http://github.com/owner/repo.git + } else { + let url = Url::parse(&self.example.repository_url)?; + let mut segments = url.path_segments().context("empty http url")?; + let owner = segments + .next() + .context("expected owner path segment")? + .to_string(); + let repo = segments + .next() + .context("expected repo path segment")? + .trim_end_matches(".git") + .to_string(); + assert!(segments.next().is_none()); + + Ok((owner.into(), repo.into())) + } + } +} + +async fn run_git(repo_path: &Path, args: &[&str]) -> Result { + let output = smol::process::Command::new("git") + .current_dir(repo_path) + .args(args) + .output() + .await?; + + anyhow::ensure!( + output.status.success(), + "`git {}` within `{}` failed with status: {}\nstderr:\n{}\nstdout:\n{}", + args.join(" "), + repo_path.display(), + output.status, + String::from_utf8_lossy(&output.stderr), + String::from_utf8_lossy(&output.stdout), + ); + Ok(String::from_utf8(output.stdout)?.trim().to_string()) +} + +impl Display for NamedExample { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "# {}\n\n", self.name)?; + write!( + f, + "{REPOSITORY_URL_FIELD} = {}\n", + self.example.repository_url + )?; + write!(f, "{REVISION_FIELD} = {}\n\n", self.example.revision)?; + + write!( + f, + "## {CURSOR_POSITION_HEADING}\n\n`````{}\n{}`````\n", + self.example.cursor_path.display(), + self.example.cursor_position + )?; + write!(f, "## {EDIT_HISTORY_HEADING}\n\n")?; + + if !self.example.edit_history.is_empty() { + write!(f, "`````diff\n")?; + for item in &self.example.edit_history { + write!(f, "{item}")?; + } + write!(f, "`````\n")?; + } + + if !self.example.expected_patch.is_empty() { + write!( + f, + "\n## {EXPECTED_PATCH_HEADING}\n\n`````diff\n{}`````\n", + self.example.expected_patch + )?; + } + + if !self.example.expected_excerpts.is_empty() { + write!(f, "\n## {EXPECTED_EXCERPTS_HEADING}\n\n")?; + + for excerpt in &self.example.expected_excerpts { + write!( + f, + "`````{}{}\n{}`````\n\n", + excerpt + .path + .extension() + .map(|ext| format!("{} ", ext.to_string_lossy())) + .unwrap_or_default(), + excerpt.path.display(), + excerpt.text + )?; + } + } + + Ok(()) + } +} diff --git a/crates/zeta_cli/src/main.rs b/crates/zeta_cli/src/main.rs index 7a6d4b26dc87cd9db7d40fe2745520ee5f574ea6..8f19287744697e9f0d2ffd520be8a814790b8345 100644 --- a/crates/zeta_cli/src/main.rs +++ b/crates/zeta_cli/src/main.rs @@ -1,8 +1,10 @@ +mod example; mod headless; mod source_location; mod syntax_retrieval_stats; mod util; +use crate::example::{ExampleFormat, NamedExample}; use crate::syntax_retrieval_stats::retrieval_stats; use ::serde::Serialize; use ::util::paths::PathStyle; @@ -22,6 +24,7 @@ use language_model::LanguageModelRegistry; use project::{Project, Worktree}; use reqwest_client::ReqwestClient; use serde_json::json; +use std::io; use std::{collections::HashSet, path::PathBuf, process::exit, str::FromStr, sync::Arc}; use zeta2::{ContextMode, LlmContextOptions, SearchToolQuery}; @@ -48,6 +51,11 @@ enum Command { #[command(subcommand)] command: Zeta2Command, }, + ConvertExample { + path: PathBuf, + #[arg(long, value_enum, default_value_t = ExampleFormat::Md)] + output_format: ExampleFormat, + }, } #[derive(Subcommand, Debug)] @@ -641,6 +649,15 @@ fn main() { } }, }, + Command::ConvertExample { + path, + output_format, + } => { + let example = NamedExample::load(path).unwrap(); + example.write(output_format, io::stdout()).unwrap(); + let _ = cx.update(|cx| cx.quit()); + return; + } }; match result { From 07dcb8f2bb0a28290a2a05d57995cb9ca2a5bf97 Mon Sep 17 00:00:00 2001 From: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Date: Sat, 1 Nov 2025 17:30:13 -0400 Subject: [PATCH 04/16] debugger: Add program and module path fallbacks for debugpy toolchain (#40975) Fixes the Debugpy toolchain detection bug in #40324 When detecting what toolchain (venv) to use in the Debugpy configuration stage, we used to only base it off of the current working directory argument passed to the config. This is wrong behavior for cases like mono repos, where the correct virtual environment to use is nested in another folder. This PR fixes this issue by adding the program and module fields as fallbacks to check for virtual environments. We also added support for program/module relative paths as well when cwd is not None. Release Notes: - debugger: Improve mono repo virtual environment detection with Debugpy --------- Co-authored-by: Remco Smits --- crates/dap_adapters/src/dap_adapters.rs | 86 +++++++++++++------------ crates/dap_adapters/src/python.rs | 75 ++++++++++++++------- 2 files changed, 96 insertions(+), 65 deletions(-) diff --git a/crates/dap_adapters/src/dap_adapters.rs b/crates/dap_adapters/src/dap_adapters.rs index d8a706ba414af2c9e0beb1cffe8357bcece1dc52..2ab9cabc198c4b036301cb92e1f544ae640b898d 100644 --- a/crates/dap_adapters/src/dap_adapters.rs +++ b/crates/dap_adapters/src/dap_adapters.rs @@ -42,61 +42,63 @@ pub fn init(cx: &mut App) { } #[cfg(test)] -struct MockDelegate { - worktree_root: PathBuf, -} +mod test_mocks { + use super::*; -#[cfg(test)] -impl MockDelegate { - fn new() -> Arc { - Arc::new(Self { - worktree_root: PathBuf::from("/tmp/test"), - }) + pub(crate) struct MockDelegate { + worktree_root: PathBuf, } -} -#[cfg(test)] -#[async_trait::async_trait] -impl adapters::DapDelegate for MockDelegate { - fn worktree_id(&self) -> settings::WorktreeId { - settings::WorktreeId::from_usize(0) + impl MockDelegate { + pub(crate) fn new() -> Arc { + Arc::new(Self { + worktree_root: PathBuf::from("/tmp/test"), + }) + } } - fn worktree_root_path(&self) -> &std::path::Path { - &self.worktree_root - } + #[async_trait::async_trait] + impl adapters::DapDelegate for MockDelegate { + fn worktree_id(&self) -> settings::WorktreeId { + settings::WorktreeId::from_usize(0) + } - fn http_client(&self) -> Arc { - unimplemented!("Not needed for tests") - } + fn worktree_root_path(&self) -> &std::path::Path { + &self.worktree_root + } - fn node_runtime(&self) -> node_runtime::NodeRuntime { - unimplemented!("Not needed for tests") - } + fn http_client(&self) -> Arc { + unimplemented!("Not needed for tests") + } - fn toolchain_store(&self) -> Arc { - unimplemented!("Not needed for tests") - } + fn node_runtime(&self) -> node_runtime::NodeRuntime { + unimplemented!("Not needed for tests") + } - fn fs(&self) -> Arc { - unimplemented!("Not needed for tests") - } + fn toolchain_store(&self) -> Arc { + unimplemented!("Not needed for tests") + } - fn output_to_console(&self, _msg: String) {} + fn fs(&self) -> Arc { + unimplemented!("Not needed for tests") + } - async fn which(&self, _command: &std::ffi::OsStr) -> Option { - None - } + fn output_to_console(&self, _msg: String) {} - async fn read_text_file(&self, _path: &util::rel_path::RelPath) -> Result { - Ok(String::new()) - } + async fn which(&self, _command: &std::ffi::OsStr) -> Option { + None + } - async fn shell_env(&self) -> collections::HashMap { - collections::HashMap::default() - } + async fn read_text_file(&self, _path: &util::rel_path::RelPath) -> Result { + Ok(String::new()) + } - fn is_headless(&self) -> bool { - false + async fn shell_env(&self) -> collections::HashMap { + collections::HashMap::default() + } + + fn is_headless(&self) -> bool { + false + } } } diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index e718f66c78099044baed837da0ddc7bfa96ffa1c..4d81e5ba851305ae3adc2ee0a6ab6a29f43edd62 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -824,29 +824,58 @@ impl DebugAdapter for PythonDebugAdapter { .await; } - let base_path = config - .config - .get("cwd") - .and_then(|cwd| { - RelPath::new( - cwd.as_str() - .map(Path::new)? - .strip_prefix(delegate.worktree_root_path()) - .ok()?, - PathStyle::local(), - ) - .ok() + let base_paths = ["cwd", "program", "module"] + .into_iter() + .filter_map(|key| { + config.config.get(key).and_then(|cwd| { + RelPath::new( + cwd.as_str() + .map(Path::new)? + .strip_prefix(delegate.worktree_root_path()) + .ok()?, + PathStyle::local(), + ) + .ok() + }) }) - .unwrap_or_else(|| RelPath::empty().into()); - let toolchain = delegate - .toolchain_store() - .active_toolchain( - delegate.worktree_id(), - base_path.into_arc(), - language::LanguageName::new(Self::LANGUAGE_NAME), - cx, + .chain( + // While Debugpy's wiki saids absolute paths are required, but it actually supports relative paths when cwd is passed in. + // (Which should always be the case because Zed defaults to the cwd worktree root) + // So we want to check that these relative paths find toolchains as well. Otherwise, they won't be checked + // because the strip prefix in the iteration above will return an error + config + .config + .get("cwd") + .map(|_| { + ["program", "module"].into_iter().filter_map(|key| { + config.config.get(key).and_then(|value| { + let path = Path::new(value.as_str()?); + RelPath::new(path, PathStyle::local()).ok() + }) + }) + }) + .into_iter() + .flatten(), ) - .await; + .chain([RelPath::empty().into()]); + + let mut toolchain = None; + + for base_path in base_paths { + if let Some(found_toolchain) = delegate + .toolchain_store() + .active_toolchain( + delegate.worktree_id(), + base_path.into_arc(), + language::LanguageName::new(Self::LANGUAGE_NAME), + cx, + ) + .await + { + toolchain = Some(found_toolchain); + break; + } + } self.fetch_debugpy_whl(toolchain.clone(), delegate) .await @@ -914,7 +943,7 @@ mod tests { let result = adapter .get_installed_binary( - &MockDelegate::new(), + &test_mocks::MockDelegate::new(), &task_def, None, None, @@ -955,7 +984,7 @@ mod tests { let result_host = adapter .get_installed_binary( - &MockDelegate::new(), + &test_mocks::MockDelegate::new(), &task_def_host, None, None, From df15d2d2feada0bd474d814fcd4514e8000be62c Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Sun, 2 Nov 2025 00:52:32 +0100 Subject: [PATCH 05/16] Fix doc typos (#41727) Release Notes: - N/A --- docs/src/extensions/icon-themes.md | 2 +- docs/src/extensions/languages.md | 2 +- docs/src/languages/rego.md | 2 +- docs/src/snippets.md | 2 +- docs/src/visual-customization.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/extensions/icon-themes.md b/docs/src/extensions/icon-themes.md index 697723a59677c25dd14982a1c7f7cf92d1950a70..676cae59cd343a3f73ce5e0504e370e92c699d2b 100644 --- a/docs/src/extensions/icon-themes.md +++ b/docs/src/extensions/icon-themes.md @@ -11,7 +11,7 @@ The [Material Icon Theme](https://github.com/zed-extensions/material-icon-theme) There are two important directories for an icon theme extension: - `icon_themes`: This directory will contain one or more JSON files containing the icon theme definitions. -- `icons`: This directory contains the icons assets that will be distributed with the extension. You can created subdirectories in this directory, if so desired. +- `icons`: This directory contains the icon assets that will be distributed with the extension. You can created subdirectories in this directory, if so desired. Each icon theme file should adhere to the JSON schema specified at [`https://zed.dev/schema/icon_themes/v0.3.0.json`](https://zed.dev/schema/icon_themes/v0.3.0.json). diff --git a/docs/src/extensions/languages.md b/docs/src/extensions/languages.md index 5c63b880c875701e1721b8d6298dc49da6b45a98..7eb6a355dbfcafaa01ca885789d41e28c474d2f4 100644 --- a/docs/src/extensions/languages.md +++ b/docs/src/extensions/languages.md @@ -324,7 +324,7 @@ This query marks number and string values in key-value pairs and arrays for reda The `runnables.scm` file defines rules for detecting runnable code. -Here's an example from an `runnables.scm` file for JSON: +Here's an example from a `runnables.scm` file for JSON: ```scheme ( diff --git a/docs/src/languages/rego.md b/docs/src/languages/rego.md index 21192a5c53a2e05a34754eb80421d60fc77467ac..14231c65620ee2c88ac3bb100d6ac91b941c80f4 100644 --- a/docs/src/languages/rego.md +++ b/docs/src/languages/rego.md @@ -7,7 +7,7 @@ Rego language support in Zed is provided by the community-maintained [Rego exten ## Installation -The extensions is largely based on the [Regal](https://docs.styra.com/regal/language-server) language server which should be installed to make use of the extension. Read the [getting started](https://docs.styra.com/regal#getting-started) instructions for more information. +The extension is largely based on the [Regal](https://docs.styra.com/regal/language-server) language server which should be installed to make use of the extension. Read the [getting started](https://docs.styra.com/regal#getting-started) instructions for more information. ## Configuration diff --git a/docs/src/snippets.md b/docs/src/snippets.md index 21aed43452318863b735a9b46cd5399a8bfca1c6..29ecd9bc850b919dbc63a87e2f1bf9477901a33d 100644 --- a/docs/src/snippets.md +++ b/docs/src/snippets.md @@ -1,6 +1,6 @@ # Snippets -Use the {#action snippets::ConfigureSnippets} action to create a new snippets file or edit a existing snippets file for a specified [scope](#scopes). +Use the {#action snippets::ConfigureSnippets} action to create a new snippets file or edit an existing snippets file for a specified [scope](#scopes). The snippets are located in `~/.config/zed/snippets` directory to which you can navigate to with the {#action snippets::OpenFolder} action. diff --git a/docs/src/visual-customization.md b/docs/src/visual-customization.md index b353377dd764d2506abd4cce46352df3ca47dfcb..dc50588cde659b4e580822ddfd7eaf8951f63ea7 100644 --- a/docs/src/visual-customization.md +++ b/docs/src/visual-customization.md @@ -6,7 +6,7 @@ See [Configuring Zed](./configuring-zed.md) for additional information and other ## Themes -Use may install zed extensions providing [Themes](./themes.md) and [Icon Themes](./icon-themes.md) via {#action zed::Extensions} from the command palette or menu. +User may install zed extensions providing [Themes](./themes.md) and [Icon Themes](./icon-themes.md) via {#action zed::Extensions} from the command palette or menu. You can preview/choose amongst your installed themes and icon themes with {#action theme_selector::Toggle} ({#kb theme_selector::Toggle}) and ({#action icon_theme_selector::Toggle}) which will modify the following settings: From 2408f767f48973aed1a4fbc7e51a352416d2b79c Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Sat, 1 Nov 2025 19:45:44 -0700 Subject: [PATCH 06/16] gh-workflow unit evals (#41637) Closes #ISSUE Release Notes: - N/A *or* Added/Fixed/Improved ... --- .github/workflows/eval.yml | 71 ----------- .github/workflows/run_agent_evals.yml | 62 ++++++++++ .github/workflows/run_unit_evals.yml | 63 ++++++++++ .github/workflows/unit_evals.yml | 86 ------------- script/run-unit-evals | 5 + tooling/xtask/src/tasks/workflows.rs | 3 + .../src/tasks/workflows/run_agent_evals.rs | 113 ++++++++++++++++++ 7 files changed, 246 insertions(+), 157 deletions(-) delete mode 100644 .github/workflows/eval.yml create mode 100644 .github/workflows/run_agent_evals.yml create mode 100644 .github/workflows/run_unit_evals.yml delete mode 100644 .github/workflows/unit_evals.yml create mode 100755 script/run-unit-evals create mode 100644 tooling/xtask/src/tasks/workflows/run_agent_evals.rs diff --git a/.github/workflows/eval.yml b/.github/workflows/eval.yml deleted file mode 100644 index b5da9e7b7c8e293fb565f4de269a1ae266c19692..0000000000000000000000000000000000000000 --- a/.github/workflows/eval.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: Run Agent Eval - -on: - schedule: - - cron: "0 0 * * *" - - pull_request: - branches: - - "**" - types: [synchronize, reopened, labeled] - - workflow_dispatch: - -concurrency: - # Allow only one workflow per any non-`main` branch. - group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }} - cancel-in-progress: true - -env: - CARGO_TERM_COLOR: always - CARGO_INCREMENTAL: 0 - RUST_BACKTRACE: 1 - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }} - ZED_EVAL_TELEMETRY: 1 - -jobs: - run_eval: - timeout-minutes: 60 - name: Run Agent Eval - if: > - github.repository_owner == 'zed-industries' && - (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-eval')) - runs-on: - - namespace-profile-16x32-ubuntu-2204 - steps: - - name: Add Rust to the PATH - run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - - - name: Checkout repo - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - with: - clean: false - - - name: Cache dependencies - uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2 - with: - save-if: ${{ github.ref == 'refs/heads/main' }} - # cache-provider: "buildjet" - - - name: Install Linux dependencies - run: ./script/linux - - - name: Configure CI - run: | - mkdir -p ./../.cargo - cp ./.cargo/ci-config.toml ./../.cargo/config.toml - - - name: Compile eval - run: cargo build --package=eval - - - name: Run eval - run: cargo run --package=eval -- --repetitions=8 --concurrency=1 - - # Even the Linux runner is not stateful, in theory there is no need to do this cleanup. - # But, to avoid potential issues in the future if we choose to use a stateful Linux runner and forget to add code - # to clean up the config file, I’ve included the cleanup code here as a precaution. - # While it’s not strictly necessary at this moment, I believe it’s better to err on the side of caution. - - name: Clean CI config file - if: always() - run: rm -rf ./../.cargo diff --git a/.github/workflows/run_agent_evals.yml b/.github/workflows/run_agent_evals.yml new file mode 100644 index 0000000000000000000000000000000000000000..67a050cd59c973ecd674fc3f6fe7ea4da436428f --- /dev/null +++ b/.github/workflows/run_agent_evals.yml @@ -0,0 +1,62 @@ +# Generated from xtask::workflows::run_agent_evals +# Rebuild with `cargo xtask workflows`. +name: run_agent_evals +env: + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: '0' + RUST_BACKTRACE: '1' + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }} + ZED_EVAL_TELEMETRY: '1' +on: + pull_request: + types: + - synchronize + - reopened + - labeled + branches: + - '**' + schedule: + - cron: 0 0 * * * + workflow_dispatch: {} +jobs: + agent_evals: + if: | + github.repository_owner == 'zed-industries' && + (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-eval')) + runs-on: namespace-profile-16x32-ubuntu-2204 + steps: + - name: steps::checkout_repo + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + clean: false + - name: steps::cache_rust_dependencies + uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 + with: + save-if: ${{ github.ref == 'refs/heads/main' }} + - name: steps::setup_linux + run: ./script/linux + shell: bash -euxo pipefail {0} + - name: steps::install_mold + run: ./script/install-mold + shell: bash -euxo pipefail {0} + - name: steps::setup_cargo_config + run: | + mkdir -p ./../.cargo + cp ./.cargo/ci-config.toml ./../.cargo/config.toml + shell: bash -euxo pipefail {0} + - name: cargo build --package=eval + run: cargo build --package=eval + shell: bash -euxo pipefail {0} + - name: run_agent_evals::agent_evals::run_eval + run: cargo run --package=eval -- --repetitions=8 --concurrency=1 + shell: bash -euxo pipefail {0} + - name: steps::cleanup_cargo_config + if: always() + run: | + rm -rf ./../.cargo + shell: bash -euxo pipefail {0} + timeout-minutes: 60 +concurrency: + group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }} + cancel-in-progress: true diff --git a/.github/workflows/run_unit_evals.yml b/.github/workflows/run_unit_evals.yml new file mode 100644 index 0000000000000000000000000000000000000000..b94d54e1639c0255dbfcf9921c85ff48b8d5a476 --- /dev/null +++ b/.github/workflows/run_unit_evals.yml @@ -0,0 +1,63 @@ +# Generated from xtask::workflows::run_agent_evals +# Rebuild with `cargo xtask workflows`. +name: run_agent_evals +env: + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: '0' + RUST_BACKTRACE: '1' + ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }} +on: + schedule: + - cron: 47 1 * * 2 + workflow_dispatch: {} +jobs: + unit_evals: + runs-on: namespace-profile-16x32-ubuntu-2204 + steps: + - name: steps::checkout_repo + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + clean: false + - name: steps::setup_cargo_config + run: | + mkdir -p ./../.cargo + cp ./.cargo/ci-config.toml ./../.cargo/config.toml + shell: bash -euxo pipefail {0} + - name: steps::cache_rust_dependencies + uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 + with: + save-if: ${{ github.ref == 'refs/heads/main' }} + - name: steps::setup_linux + run: ./script/linux + shell: bash -euxo pipefail {0} + - name: steps::install_mold + run: ./script/install-mold + shell: bash -euxo pipefail {0} + - name: steps::cargo_install_nextest + run: cargo install cargo-nextest --locked + shell: bash -euxo pipefail {0} + - name: steps::clear_target_dir_if_large + run: ./script/clear-target-dir-if-larger-than 100 + shell: bash -euxo pipefail {0} + - name: ./script/run-unit-evals + run: ./script/run-unit-evals + shell: bash -euxo pipefail {0} + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + - name: run_agent_evals::unit_evals::send_failure_to_slack + if: ${{ failure() }} + uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 + with: + method: chat.postMessage + token: ${{ secrets.SLACK_APP_ZED_UNIT_EVALS_BOT_TOKEN }} + payload: | + channel: C04UDRNNJFQ + text: "Unit Evals Failed: https://github.com/zed-industries/zed/actions/runs/${{ github.run_id }}" + - name: steps::cleanup_cargo_config + if: always() + run: | + rm -rf ./../.cargo + shell: bash -euxo pipefail {0} +concurrency: + group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }} + cancel-in-progress: true diff --git a/.github/workflows/unit_evals.yml b/.github/workflows/unit_evals.yml deleted file mode 100644 index 53ed33a1af300d6b641b3b9430de0bb6846b27cc..0000000000000000000000000000000000000000 --- a/.github/workflows/unit_evals.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: Run Unit Evals - -on: - schedule: - # GitHub might drop jobs at busy times, so we choose a random time in the middle of the night. - - cron: "47 1 * * 2" - workflow_dispatch: - -concurrency: - # Allow only one workflow per any non-`main` branch. - group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }} - cancel-in-progress: true - -env: - CARGO_TERM_COLOR: always - CARGO_INCREMENTAL: 0 - RUST_BACKTRACE: 1 - ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }} - -jobs: - unit_evals: - if: github.repository_owner == 'zed-industries' - timeout-minutes: 60 - name: Run unit evals - runs-on: - - namespace-profile-16x32-ubuntu-2204 - steps: - - name: Add Rust to the PATH - run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - - - name: Checkout repo - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - with: - clean: false - - - name: Cache dependencies - uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2 - with: - save-if: ${{ github.ref == 'refs/heads/main' }} - # cache-provider: "buildjet" - - - name: Install Linux dependencies - run: ./script/linux - - - name: Configure CI - run: | - mkdir -p ./../.cargo - cp ./.cargo/ci-config.toml ./../.cargo/config.toml - - - name: Install Rust - shell: bash -euxo pipefail {0} - run: | - cargo install cargo-nextest --locked - - - name: Install Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: "18" - - - name: Limit target directory size - shell: bash -euxo pipefail {0} - run: script/clear-target-dir-if-larger-than 100 - - - name: Run unit evals - shell: bash -euxo pipefail {0} - run: cargo nextest run --workspace --no-fail-fast --features unit-eval --no-capture -E 'test(::eval_)' - env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - - - name: Send failure message to Slack channel if needed - if: ${{ failure() }} - uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 - with: - method: chat.postMessage - token: ${{ secrets.SLACK_APP_ZED_UNIT_EVALS_BOT_TOKEN }} - payload: | - channel: C04UDRNNJFQ - text: "Unit Evals Failed: https://github.com/zed-industries/zed/actions/runs/${{ github.run_id }}" - - # Even the Linux runner is not stateful, in theory there is no need to do this cleanup. - # But, to avoid potential issues in the future if we choose to use a stateful Linux runner and forget to add code - # to clean up the config file, I’ve included the cleanup code here as a precaution. - # While it’s not strictly necessary at this moment, I believe it’s better to err on the side of caution. - - name: Clean CI config file - if: always() - run: rm -rf ./../.cargo diff --git a/script/run-unit-evals b/script/run-unit-evals new file mode 100755 index 0000000000000000000000000000000000000000..02481e1ce9dde7d2cbde9603f663093bf7a2ee38 --- /dev/null +++ b/script/run-unit-evals @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -euxo pipefail + +cargo nextest run --workspace --no-fail-fast --features unit-eval --no-capture -E 'test(::eval_)' diff --git a/tooling/xtask/src/tasks/workflows.rs b/tooling/xtask/src/tasks/workflows.rs index a8472606ffd6aea48775f3fca28f9c30b2223cc5..538724bcd9648b89d303a6eff834d08ffb3bf18a 100644 --- a/tooling/xtask/src/tasks/workflows.rs +++ b/tooling/xtask/src/tasks/workflows.rs @@ -10,6 +10,7 @@ mod release_nightly; mod run_bundling; mod release; +mod run_agent_evals; mod run_tests; mod runners; mod steps; @@ -28,6 +29,8 @@ pub fn run_workflows(_: GenerateWorkflowArgs) -> Result<()> { ("run_tests.yml", run_tests::run_tests()), ("release.yml", release::release()), ("compare_perf.yml", compare_perf::compare_perf()), + ("run_unit_evals.yml", run_agent_evals::run_unit_evals()), + ("run_agent_evals.yml", run_agent_evals::run_agent_evals()), ]; fs::create_dir_all(dir) .with_context(|| format!("Failed to create directory: {}", dir.display()))?; diff --git a/tooling/xtask/src/tasks/workflows/run_agent_evals.rs b/tooling/xtask/src/tasks/workflows/run_agent_evals.rs new file mode 100644 index 0000000000000000000000000000000000000000..b83aee8457ef61c7430431c6de6f654d9559423e --- /dev/null +++ b/tooling/xtask/src/tasks/workflows/run_agent_evals.rs @@ -0,0 +1,113 @@ +use gh_workflow::{ + Event, Expression, Job, PullRequest, PullRequestType, Run, Schedule, Step, Use, Workflow, + WorkflowDispatch, +}; + +use crate::tasks::workflows::{ + runners::{self, Platform}, + steps::{self, FluentBuilder as _, NamedJob, named, setup_cargo_config}, + vars, +}; + +pub(crate) fn run_agent_evals() -> Workflow { + let agent_evals = agent_evals(); + + named::workflow() + .on(Event::default() + .schedule([Schedule::default().cron("0 0 * * *")]) + .pull_request(PullRequest::default().add_branch("**").types([ + PullRequestType::Synchronize, + PullRequestType::Reopened, + PullRequestType::Labeled, + ])) + .workflow_dispatch(WorkflowDispatch::default())) + .concurrency(vars::one_workflow_per_non_main_branch()) + .add_env(("CARGO_TERM_COLOR", "always")) + .add_env(("CARGO_INCREMENTAL", 0)) + .add_env(("RUST_BACKTRACE", 1)) + .add_env(("ANTHROPIC_API_KEY", "${{ secrets.ANTHROPIC_API_KEY }}")) + .add_env(( + "ZED_CLIENT_CHECKSUM_SEED", + "${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}", + )) + .add_env(("ZED_EVAL_TELEMETRY", 1)) + .add_job(agent_evals.name, agent_evals.job) +} + +fn agent_evals() -> NamedJob { + fn run_eval() -> Step { + named::bash("cargo run --package=eval -- --repetitions=8 --concurrency=1") + } + + named::job( + Job::default() + .cond(Expression::new(indoc::indoc!{r#" + github.repository_owner == 'zed-industries' && + (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-eval')) + "#})) + .runs_on(runners::LINUX_DEFAULT) + .timeout_minutes(60_u32) + .add_step(steps::checkout_repo()) + .add_step(steps::cache_rust_dependencies()) + .map(steps::install_linux_dependencies) + .add_step(setup_cargo_config(Platform::Linux)) + .add_step(steps::script("cargo build --package=eval")) + .add_step(run_eval()) + .add_step(steps::cleanup_cargo_config(Platform::Linux)) + ) +} + +pub(crate) fn run_unit_evals() -> Workflow { + let unit_evals = unit_evals(); + + named::workflow() + .on(Event::default() + .schedule([ + // GitHub might drop jobs at busy times, so we choose a random time in the middle of the night. + Schedule::default().cron("47 1 * * 2"), + ]) + .workflow_dispatch(WorkflowDispatch::default())) + .concurrency(vars::one_workflow_per_non_main_branch()) + .add_env(("CARGO_TERM_COLOR", "always")) + .add_env(("CARGO_INCREMENTAL", 0)) + .add_env(("RUST_BACKTRACE", 1)) + .add_env(( + "ZED_CLIENT_CHECKSUM_SEED", + "${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}", + )) + .add_job(unit_evals.name, unit_evals.job) +} + +fn unit_evals() -> NamedJob { + fn send_failure_to_slack() -> Step { + named::uses( + "slackapi", + "slack-github-action", + "b0fa283ad8fea605de13dc3f449259339835fc52", + ) + .if_condition(Expression::new("${{ failure() }}")) + .add_with(("method", "chat.postMessage")) + .add_with(("token", "${{ secrets.SLACK_APP_ZED_UNIT_EVALS_BOT_TOKEN }}")) + .add_with(("payload", indoc::indoc!{r#" + channel: C04UDRNNJFQ + text: "Unit Evals Failed: https://github.com/zed-industries/zed/actions/runs/${{ github.run_id }}" + "#})) + } + + named::job( + Job::default() + .runs_on(runners::LINUX_DEFAULT) + .add_step(steps::checkout_repo()) + .add_step(steps::setup_cargo_config(Platform::Linux)) + .add_step(steps::cache_rust_dependencies()) + .map(steps::install_linux_dependencies) + .add_step(steps::cargo_install_nextest(Platform::Linux)) + .add_step(steps::clear_target_dir_if_large(Platform::Linux)) + .add_step( + steps::script("./script/run-unit-evals") + .add_env(("ANTHROPIC_API_KEY", "${{ secrets.ANTHROPIC_API_KEY }}")), + ) + .add_step(send_failure_to_slack()) + .add_step(steps::cleanup_cargo_config(Platform::Linux)), + ) +} From 548cdfde3a3343825b9f92c76ddcb8c582b9a1d0 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Sun, 2 Nov 2025 00:37:02 -0400 Subject: [PATCH 07/16] Delete release process docs (#41733) These have been migrated to the README.md [here](https://github.com/zed-industries/release_notes). These don't need to be public. Putting them in the same repo where we draft (`release_notes`) means less jumping around and allows us to include additional information we might not want to make public. Release Notes: - N/A --- docs/src/SUMMARY.md | 1 - docs/src/development.md | 1 - docs/src/development/releases.md | 147 ------------------------------- 3 files changed, 149 deletions(-) delete mode 100644 docs/src/development/releases.md diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 9e5bbb1413fec9b021d73dce0f002c1e039c5da9..1a4783cdf5342c0ab92d4eea45260c416fc68cd8 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -165,6 +165,5 @@ - [Local Collaboration](./development/local-collaboration.md) - [Using Debuggers](./development/debuggers.md) - [Glossary](./development/glossary.md) -- [Release Process](./development/releases.md) - [Release Notes](./development/release-notes.md) - [Debugging Crashes](./development/debugging-crashes.md) diff --git a/docs/src/development.md b/docs/src/development.md index 6cb5f0b8271ab0347d33ee0cf634b60e790f3ba0..31bb245ac42f80c830a0faba405323d1097e3f51 100644 --- a/docs/src/development.md +++ b/docs/src/development.md @@ -88,7 +88,6 @@ in-depth examples and explanations. ## Contributor links - [CONTRIBUTING.md](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md) -- [Releases](./development/releases.md) - [Debugging Crashes](./development/debugging-crashes.md) - [Code of Conduct](https://zed.dev/code-of-conduct) - [Zed Contributor License](https://zed.dev/cla) diff --git a/docs/src/development/releases.md b/docs/src/development/releases.md deleted file mode 100644 index 6cb3deb31680f8c038195c93ebf12fe6699354e2..0000000000000000000000000000000000000000 --- a/docs/src/development/releases.md +++ /dev/null @@ -1,147 +0,0 @@ -# Zed Releases - -Read about Zed's [release channels here](https://zed.dev/faq#what-are-the-release-channels). - -## Wednesday Release Process - -You will need write access to the Zed repository to do this. - -Credentials for various services used in this process can be found in 1Password. - -Use the `releases` Slack channel to notify the team that releases will be starting. -This is mostly a formality on Wednesday's minor update releases, but can be beneficial when doing patch releases, as other devs may have landed fixes they'd like to cherry pick. - -### Starting the Builds - -1. Checkout `main` and ensure your working copy is clean. - -1. Run `git fetch && git pull` to ensure you have the latest commits locally. - -1. Run `git fetch --tags --force` to forcibly ensure your local tags are in sync with the remote. - -1. Run `./script/get-stable-channel-release-notes` and store output locally. - -1. Run `./script/bump-zed-minor-versions`. - - - Push the tags and branches as instructed. - -1. Run `./script/get-preview-channel-changes` and store output locally. - -> **Note:** Always prioritize the stable release. -> If you've completed aggregating stable release notes, you can move on to working on aggregating preview release notes, but once the stable build has finished, work through the rest of the stable steps to fully publish. -> Preview can be finished up after. - -### Stable Release - -1. Aggregate stable release notes. - - - Follow the instructions at the end of the script and aggregate the release notes into one structure. - -1. Once the stable release draft is up on [GitHub Releases](https://github.com/zed-industries/zed/releases), paste the stable release notes into it and **save**. - - - **Do not publish the draft!** - -1. Check the stable release assets. - - - Ensure the stable release job has finished without error. - - Ensure the draft has the proper number of assets—releases currently have 12 assets each (as of v0.211). - - Download the artifacts for the stable release draft and test that you can run them locally. - -1. Publish the stable draft on [GitHub Releases](https://github.com/zed-industries/zed/releases). - - - Use [Vercel](https://vercel.com/zed-industries/zed-dev) to check the progress of the website rebuild. - The release will be public once the rebuild has completed. - -1. Post the stable release notes to social media. - - - Bluesky and X posts will already be built as drafts in [Buffer](https://buffer.com). - - Double-check links. - - Publish both, one at a time, ensuring both are posted to each respective platform. - -1. Send the stable release notes email. - - - The email broadcast will already be built as a draft in [Kit](https://kit.com). - - Double-check links. - - Publish the email. - -### Preview Release - -1. Aggregate preview release notes. - - - Take the script's output and build release notes by organizing each release note line into a category. - - Use a prior release for the initial outline. - - Make sure to append the `Credit` line, if present, to the end of each release note line. - -1. Once the preview release draft is up on [GitHub Releases](https://github.com/zed-industries/zed/releases), paste the preview release notes into it and **save**. - - - **Do not publish the draft!** - -1. Check the preview release assets. - - - Ensure the preview release job has finished without error. - - Ensure the draft has the proper number of assets—releases currently have 12 assets each (as of v0.211). - - Download the artifacts for the preview release draft and test that you can run them locally. - -1. Publish the preview draft on [GitHub Releases](https://github.com/zed-industries/zed/releases). - - Use [Vercel](https://vercel.com/zed-industries/zed-dev) to check the progress of the website rebuild. - The release will be public once the rebuild has completed. - -### Prep Content for Next Week's Stable Release - -1. Build social media posts based on the popular items in preview. - - - Draft the copy in the [tweets](https://zed.dev/channel/tweets-23331) channel. - - Create the preview media (videos, screenshots). - - For features that you film videos around, try to create alternative photo-only versions to be used in the email, as videos and GIFs aren't great for email. - - Store all created media in `Feature Media` in our Google Drive. - - Build X and Bluesky post drafts (copy and media) in [Buffer](https://buffer.com), to be sent for next week's stable release. - - **Note: These are preview items and you may discover bugs.** - **This is a very good time to report these findings to the team!** - -1. Build email based on the popular items in preview. - - - You can reuse the copy and photo media from the preview social media posts. - - Create a draft email in [Kit](https://kit.com), to be sent for next week's stable release. - -## Patch Release Process - -If your PR fixes a panic or a crash, you should cherry-pick it to the current stable and preview branches. -If your PR fixes a regression in recently released code, you should cherry-pick it to preview. - -You will need write access to the Zed repository to do this: - ---- - -1. Send a PR containing your change to `main` as normal. - -1. Once it is merged, cherry-pick the commit locally to either of the release branches (`v0.XXX.x`). - - - In some cases, you may have to handle a merge conflict. - More often than not, this will happen when cherry-picking to stable, as the stable branch is more "stale" than the preview branch. - -1. After the commit is cherry-picked, run `./script/trigger-release {preview|stable}`. - This will bump the version numbers, create a new release tag, and kick off a release build. - - - This can also be run from the [GitHub Actions UI](https://github.com/zed-industries/zed/actions/workflows/bump_patch_version.yml): - ![](https://github.com/zed-industries/zed/assets/1486634/9e31ae95-09e1-4c7f-9591-944f4f5b63ea) - -1. Once release drafts are up on [GitHub Releases](https://github.com/zed-industries/zed/releases), proofread and edit the release notes as needed and **save**. - - - **Do not publish the drafts, yet.** - -1. Check the release assets. - - - Ensure the stable / preview release jobs have finished without error. - - Ensure each draft has the proper number of assets—releases currently have 10 assets each. - - Download the artifacts for each release draft and test that you can run them locally. - -1. Publish stable / preview drafts, one at a time. - - Use [Vercel](https://vercel.com/zed-industries/zed-dev) to check the progress of the website rebuild. - The release will be public once the rebuild has completed. - -## Nightly release process - -In addition to the public releases, we also have a nightly build that we encourage employees to use. -Nightly is released by cron once a day, and can be shipped as often as you'd like. -There are no release notes or announcements, so you can just merge your changes to main and run `./script/trigger-release nightly`. From d5421ba1a8b880d6baf911c6e38c7c322dc267df Mon Sep 17 00:00:00 2001 From: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Date: Sun, 2 Nov 2025 02:35:11 -0500 Subject: [PATCH 08/16] windows: Fix click bleeding through collab follow (#41726) On Windows, clicking on a collab user icon in the title bar would minimize/expand Zed because the click would bleed through to the title bar. This PR fixes this by stopping propagation. #### Before (On MacOS with double clicks to mimic the same behavior) https://github.com/user-attachments/assets/5a91f7ff-265a-4575-aa23-00b8d30daeed #### After (On MacOS with double clicks to mimic the same behavior) https://github.com/user-attachments/assets/e9fcb98f-4855-4f21-8926-2d306d256f1c Release Notes: - Windows: Fix clicking on user icon in title bar to follow minimizing/expanding Zed Co-authored-by: Remco Smits --- crates/title_bar/src/collab.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/title_bar/src/collab.rs b/crates/title_bar/src/collab.rs index 070952d1cec346e4ec41e26f69895b65cd74f082..16a0389efa46429d91c79f4eb1e99f62d01753b5 100644 --- a/crates/title_bar/src/collab.rs +++ b/crates/title_bar/src/collab.rs @@ -220,6 +220,8 @@ impl TitleBar { .on_click({ let peer_id = collaborator.peer_id; cx.listener(move |this, _, window, cx| { + cx.stop_propagation(); + this.workspace .update(cx, |workspace, cx| { if is_following { From d887e2050fc90af3d3210e6bd1eee5989431b811 Mon Sep 17 00:00:00 2001 From: Xiaobo Liu Date: Sun, 2 Nov 2025 16:15:01 +0800 Subject: [PATCH 09/16] windows: Hide background helpers behind CREATE_NO_WINDOW (#41737) Close https://github.com/zed-industries/zed/issues/41538 Release Notes: - Fixed some processes on windows not spawning with CREATE_NO_WINDOW --------- Signed-off-by: Xiaobo Liu --- Cargo.lock | 1 + crates/auto_update/Cargo.toml | 1 + crates/auto_update/src/auto_update.rs | 2 +- crates/fs/src/fs.rs | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c0eea670a77f03c4dbb5afdb7d1197b6d9b76159..3dc7b2337edcb1d155a56f241b517db5a2ad8045 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1339,6 +1339,7 @@ dependencies = [ "settings", "smol", "tempfile", + "util", "which 6.0.3", "workspace", ] diff --git a/crates/auto_update/Cargo.toml b/crates/auto_update/Cargo.toml index 08db9f8a97bb0783da987f84991ad1aaa62c2141..630be043dca120ca76b2552f0a729a03a684f934 100644 --- a/crates/auto_update/Cargo.toml +++ b/crates/auto_update/Cargo.toml @@ -26,6 +26,7 @@ serde_json.workspace = true settings.workspace = true smol.workspace = true tempfile.workspace = true +util.workspace = true workspace.workspace = true [target.'cfg(not(target_os = "windows"))'.dependencies] diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 9f93dd27900e4b90de8c6d61d41b3b6c287eaaf0..331a58414958a48feaad70babee2dc2ea3b730e0 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -962,7 +962,7 @@ pub async fn finalize_auto_update_on_quit() { .parent() .map(|p| p.join("tools").join("auto_update_helper.exe")) { - let mut command = smol::process::Command::new(helper); + let mut command = util::command::new_smol_command(helper); command.arg("--launch"); command.arg("false"); if let Ok(mut cmd) = command.spawn() { diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index c794303ef71232d5a162b51ec8db7d472328b767..0202b2134f4fd0d3f983b2c67e97414a44457143 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -377,7 +377,7 @@ impl Fs for RealFs { #[cfg(windows)] if smol::fs::metadata(&target).await?.is_dir() { - let status = smol::process::Command::new("cmd") + let status = new_smol_command("cmd") .args(["/C", "mklink", "/J"]) .args([path, target.as_path()]) .status() From a9bc890497f1edaf4f177385cf96785de60e910c Mon Sep 17 00:00:00 2001 From: Mayank Verma Date: Sun, 2 Nov 2025 21:26:58 +0530 Subject: [PATCH 10/16] ui: Fix popover menu not restoring focus to the previously focused element (#41751) Closes #26548 Here's a before/after comparison: https://github.com/user-attachments/assets/21d49db7-28bb-4fe2-bdaf-e86b6400ae7a Release Notes: - Fixed popover menus not restoring focus to the previously focused element --- crates/ui/src/components/popover_menu.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ui/src/components/popover_menu.rs b/crates/ui/src/components/popover_menu.rs index 439b53f0388114aa37adcf5277e87744e6f4f9e4..b1a52bec8fdf1f7030b5b321bed7702d602ff212 100644 --- a/crates/ui/src/components/popover_menu.rs +++ b/crates/ui/src/components/popover_menu.rs @@ -270,11 +270,11 @@ fn show_menu( window: &mut Window, cx: &mut App, ) { + let previous_focus_handle = window.focused(cx); let Some(new_menu) = (builder)(window, cx) else { return; }; let menu2 = menu.clone(); - let previous_focus_handle = window.focused(cx); window .subscribe(&new_menu, cx, move |modal, _: &DismissEvent, window, cx| { From 12fe12b5acfaabfd6d93912f82408b026708ad65 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Sun, 2 Nov 2025 14:30:37 -0300 Subject: [PATCH 11/16] docs: Update theme, icon theme, and visual customization pages (#41761) Some housekeeping updates: - Update hardcoded actions/keybindings so they're pulled from the repo - Mention settings window when useful - Add more info about agent panel's font size - Break sentences in individual lines Release Notes: - N/A --- docs/src/icon-themes.md | 10 ++++++---- docs/src/themes.md | 23 +++++++++++++---------- docs/src/visual-customization.md | 24 +++++++++++++++++------- 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/docs/src/icon-themes.md b/docs/src/icon-themes.md index e035c7171ef84d77f3d18ae704af8b369c23947e..72fc51b834acc7f4cd03eee83246f9d7b1f9b756 100644 --- a/docs/src/icon-themes.md +++ b/docs/src/icon-themes.md @@ -4,19 +4,21 @@ Zed comes with a built-in icon theme, with more icon themes available as extensi ## Selecting an Icon Theme -See what icon themes are installed and preview them via the Icon Theme Selector, which you can open from the command palette with "icon theme selector: toggle". +See what icon themes are installed and preview them via the Icon Theme Selector, which you can open from the command palette with `icon theme selector: toggle`. Navigating through the icon theme list by moving up and down will change the icon theme in real time and hitting enter will save it to your settings file. ## Installing more Icon Themes -More icon themes are available from the Extensions page, which you can access via the command palette with `zed: extensions` or the [Zed website](https://zed.dev/extensions). +More icon themes are available from the Extensions page, which you can access via the command palette with `zed: extensions` or the [Zed website](https://zed.dev/extensions?filter=icon-themes). ## Configuring Icon Themes -Your selected icon theme is stored in your settings file. You can open your settings file from the command palette with `zed: open settings file` (bound to `cmd-alt-,` on macOS and `ctrl-alt-,` on Linux). +Your selected icon theme is stored in your settings file. +You can open your settings file from the command palette with {#action zed::OpenSettingsFile} (bound to {#kb zed::OpenSettingsFile}). -Just like with themes, Zed allows for configuring different icon themes for light and dark mode. You can set the mode to `"light"` or `"dark"` to ignore the current system mode. +Just like with themes, Zed allows for configuring different icon themes for light and dark mode. +You can set the mode to `"light"` or `"dark"` to ignore the current system mode. ```json [settings] { diff --git a/docs/src/themes.md b/docs/src/themes.md index 00c2a9571c82c044864d181f8547f2d28ef1a489..0bbea57ebfd7c9d55031c2ca9ff31b67b360bcdd 100644 --- a/docs/src/themes.md +++ b/docs/src/themes.md @@ -4,21 +4,23 @@ Zed comes with a number of built-in themes, with more themes available as extens ## Selecting a Theme -See what themes are installed and preview them via the Theme Selector, which you can open from the command palette with "theme selector: Toggle" (bound to `cmd-k cmd-t` on macOS and `ctrl-k ctrl-t` on Linux). +See what themes are installed and preview them via the Theme Selector, which you can open from the command palette with `theme selector: toggle` (bound to {#kb theme_selector::Toggle}). Navigating through the theme list by moving up and down will change the theme in real time and hitting enter will save it to your settings file. ## Installing more Themes -More themes are available from the Extensions page, which you can access via the command palette with `zed: extensions` or the [Zed website](https://zed.dev/extensions). +More themes are available from the Extensions page, which you can access via the command palette with `zed: extensions` or the [Zed website](https://zed.dev/extensions?filter=themes). Many popular themes have been ported to Zed, and if you're struggling to choose one, visit [zed-themes.com](https://zed-themes.com), a third-party gallery with visible previews for many of them. ## Configuring a Theme -Your selected theme is stored in your settings file. You can open your settings file from the command palette with `zed: open settings file` (bound to `cmd-alt-,` on macOS and `ctrl-alt-,` on Linux). +Your selected theme is stored in your settings file. +You can open your settings file from the command palette with {#action zed::OpenSettingsFile} (bound to {#kb zed::OpenSettingsFile}). -By default, Zed maintains two themes: one for light mode and one for dark mode. You can set the mode to `"dark"` or `"light"` to ignore the current system mode. +By default, Zed maintains two themes: one for light mode and one for dark mode. +You can set the mode to `"dark"` or `"light"` to ignore the current system mode. ```json [settings] { @@ -32,7 +34,8 @@ By default, Zed maintains two themes: one for light mode and one for dark mode. ## Theme Overrides -To override specific attributes of a theme, use the `theme_overrides` setting. This setting can be used to configure theme-specific overrides. +To override specific attributes of a theme, use the `theme_overrides` setting. +This setting can be used to configure theme-specific overrides. For example, add the following to your `settings.json` if you wish to override the background color of the editor and display comments and doc comments as italics: @@ -54,17 +57,17 @@ For example, add the following to your `settings.json` if you wish to override t } ``` -To see a comprehensive list of list of captures (like `comment` and `comment.doc`) see: [Language Extensions: Syntax highlighting](./extensions/languages.md#syntax-highlighting). +To see a comprehensive list of list of captures (like `comment` and `comment.doc`) see [Language Extensions: Syntax highlighting](./extensions/languages.md#syntax-highlighting). -To see a list of available theme attributes look at the JSON file for your theme. For example, [assets/themes/one/one.json](https://github.com/zed-industries/zed/blob/main/assets/themes/one/one.json) for the default One Dark and One Light themes. +To see a list of available theme attributes look at the JSON file for your theme. +For example, [assets/themes/one/one.json](https://github.com/zed-industries/zed/blob/main/assets/themes/one/one.json) for the default One Dark and One Light themes. ## Local Themes Store new themes locally by placing them in the `~/.config/zed/themes` directory (macOS and Linux) or `%USERPROFILE%\AppData\Roaming\Zed\themes\` (Windows). -For example, to create a new theme called `my-cool-theme`, create a file called `my-cool-theme.json` in that directory. It will be available in the theme selector the next time Zed loads. - -Find more themes at [zed-themes.com](https://zed-themes.com). +For example, to create a new theme called `my-cool-theme`, create a file called `my-cool-theme.json` in that directory. +It will be available in the theme selector the next time Zed loads. ## Theme Development diff --git a/docs/src/visual-customization.md b/docs/src/visual-customization.md index dc50588cde659b4e580822ddfd7eaf8951f63ea7..509e47863357fa71081d8c70e34fa68d841e09f8 100644 --- a/docs/src/visual-customization.md +++ b/docs/src/visual-customization.md @@ -1,14 +1,14 @@ # Visual Customization -Various aspects of Zed's visual layout can be configured via Zed settings.json which you can access via {#action zed::OpenSettings} ({#kb zed::OpenSettings}). +Various aspects of Zed's visual layout can be configured via either the settings window or the `settings.json` file, which you can access via {#action zed::OpenSettings} ({#kb zed::OpenSettings}) and {#action zed::OpenSettingsFile} ({#kb zed::OpenSettingsFile}) respectively. See [Configuring Zed](./configuring-zed.md) for additional information and other non-visual settings. ## Themes -User may install zed extensions providing [Themes](./themes.md) and [Icon Themes](./icon-themes.md) via {#action zed::Extensions} from the command palette or menu. +You can install many [themes](./themes.md) and [icon themes](./icon-themes.md) in form of extensions by running {#action zed::Extensions} from the command palette. -You can preview/choose amongst your installed themes and icon themes with {#action theme_selector::Toggle} ({#kb theme_selector::Toggle}) and ({#action icon_theme_selector::Toggle}) which will modify the following settings: +You can preview/choose amongst your installed themes and icon themes with {#action theme_selector::Toggle} ({#kb theme_selector::Toggle}) and {#action icon_theme_selector::Toggle} ({#kb icon_theme_selector::Toggle}) which will modify the following settings: ```json [settings] { @@ -61,15 +61,20 @@ If you would like to use distinct themes for light mode/dark mode that can be se "line_height": "standard", }, - // Agent Panel Font Settings - "agent_font_size": 15 + // Controls the font size for agent responses in the agent panel. + // If not specified, it falls back to the UI font size. + "agent_ui_font_size": 15, + // Controls the font size for the agent panel's message editor, user message, + // and any other snippet of code. + "agent_buffer_font_size": 12 ``` ### Font ligatures By default Zed enable font ligatures which will visually combines certain adjacent characters. -For example `=>` will be displayed as `→` and `!=` will be `≠`. This is purely cosmetic and the individual characters remain unchanged. +For example `=>` will be displayed as `→` and `!=` will be `≠`. +This is purely cosmetic and the individual characters remain unchanged. To disable this behavior use: @@ -464,7 +469,12 @@ Project panel can be shown/hidden with {#action project_panel::ToggleFocus} ({#k "default_width": 640, // Default width (left/right docked) "default_height": 320, // Default height (bottom docked) }, - "agent_font_size": 16 + // Controls the font size for agent responses in the agent panel. + // If not specified, it falls back to the UI font size. + "agent_ui_font_size": 15, + // Controls the font size for the agent panel's message editor, user message, + // and any other snippet of code. + "agent_buffer_font_size": 12 ``` See [Zed AI Documentation](./ai/overview.md) for additional non-visual AI settings. From 00ff89f00fc771c41c36bb577c6e50893e4a9625 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Sun, 2 Nov 2025 14:30:50 -0300 Subject: [PATCH 12/16] agent_ui: Make single file review actions match panel (#41718) When we introduced the ACP-based agent panel, the condition that the "review" | "reject" | "keep" buttons observed to be displayed got mismatched between the panel and the pane (when in the single file review scenario). In the panel, the buttons appear as soon as there are changed buffers, whereas in the pane, they appear when response generation is done. I believe that making them appear at the same time, observing the same condition, is the desired behavior. Thus, I think the panel behavior is more correct, because there are loads of times where agent response generation isn't technically done (e.g., when there's a command waiting for permission to be run) but the _file edit_ has already been performed and is in a good state to be already accepted or rejected. So, this is what this PR is doing; effectively removing the "generating" state from the agent diff, and switching to `EditorState::Reviewing` when there are changed buffers. Release Notes: - Improved agent edit single file reviews by making the "reject" and "accept" buttons appear at the same time. --- crates/agent_ui/src/agent_diff.rs | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/crates/agent_ui/src/agent_diff.rs b/crates/agent_ui/src/agent_diff.rs index a0f117b0bf30abee9d2182cf8c3fadd10099b1f0..63eb2ac49731a5e57b4eae5bf33b821b2e223c25 100644 --- a/crates/agent_ui/src/agent_diff.rs +++ b/crates/agent_ui/src/agent_diff.rs @@ -70,14 +70,6 @@ impl AgentDiffThread { } } - fn is_generating(&self, cx: &App) -> bool { - match self { - AgentDiffThread::AcpThread(thread) => { - thread.read(cx).status() == acp_thread::ThreadStatus::Generating - } - } - } - fn has_pending_edit_tool_uses(&self, cx: &App) -> bool { match self { AgentDiffThread::AcpThread(thread) => thread.read(cx).has_pending_edit_tool_calls(), @@ -970,9 +962,7 @@ impl AgentDiffToolbar { None => ToolbarItemLocation::Hidden, Some(AgentDiffToolbarItem::Pane(_)) => ToolbarItemLocation::PrimaryRight, Some(AgentDiffToolbarItem::Editor { state, .. }) => match state { - EditorState::Generating | EditorState::Reviewing => { - ToolbarItemLocation::PrimaryRight - } + EditorState::Reviewing => ToolbarItemLocation::PrimaryRight, EditorState::Idle => ToolbarItemLocation::Hidden, }, } @@ -1050,7 +1040,6 @@ impl Render for AgentDiffToolbar { let content = match state { EditorState::Idle => return Empty.into_any(), - EditorState::Generating => vec![spinner_icon], EditorState::Reviewing => vec![ h_flex() .child( @@ -1222,7 +1211,6 @@ pub struct AgentDiff { pub enum EditorState { Idle, Reviewing, - Generating, } struct WorkspaceThread { @@ -1545,15 +1533,11 @@ impl AgentDiff { multibuffer.add_diff(diff_handle.clone(), cx); }); - let new_state = if thread.is_generating(cx) { - EditorState::Generating - } else { - EditorState::Reviewing - }; + let reviewing_state = EditorState::Reviewing; let previous_state = self .reviewing_editors - .insert(weak_editor.clone(), new_state.clone()); + .insert(weak_editor.clone(), reviewing_state.clone()); if previous_state.is_none() { editor.update(cx, |editor, cx| { @@ -1566,7 +1550,9 @@ impl AgentDiff { unaffected.remove(weak_editor); } - if new_state == EditorState::Reviewing && previous_state != Some(new_state) { + if reviewing_state == EditorState::Reviewing + && previous_state != Some(reviewing_state) + { // Jump to first hunk when we enter review mode editor.update(cx, |editor, cx| { let snapshot = multibuffer.read(cx).snapshot(cx); From 9909b59bd03388b5e99e4ddc2de3b7409ab667ed Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Sun, 2 Nov 2025 14:56:23 -0300 Subject: [PATCH 13/16] agent_ui: Improve the "go to file" affordance in the edit bar (#41762) This PR makes it clearer that you can click on the file path to open the corresponding file in the agent panel's "edit bar", which is the element that shows up in the panel as soon as agent-made edits happen. Release Notes: - agent panel: Improved the "go to file" affordance in the edit bar. --- crates/agent_ui/src/acp/thread_view.rs | 34 +++++++++++++++++--------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index a4b3106fa9d9ded053ff2f33b720ec3b10512d01..5c575de401daf26bd7815bc49d923072243ee980 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -3631,6 +3631,7 @@ impl AcpThreadView { .child( h_flex() .id("edits-container") + .cursor_pointer() .gap_1() .child(Disclosure::new("edits-disclosure", expanded)) .map(|this| { @@ -3770,6 +3771,7 @@ impl AcpThreadView { Label::new(name.to_string()) .size(LabelSize::XSmall) .buffer_font(cx) + .ml_1p5() }); let file_icon = FileIcons::get_icon(path.as_std_path(), cx) @@ -3801,14 +3803,30 @@ impl AcpThreadView { }) .child( h_flex() + .id(("file-name-row", index)) .relative() - .id(("file-name", index)) .pr_8() - .gap_1p5() .w_full() .overflow_x_scroll() - .child(file_icon) - .child(h_flex().gap_0p5().children(file_name).children(file_path)) + .child( + h_flex() + .id(("file-name-path", index)) + .cursor_pointer() + .pr_0p5() + .gap_0p5() + .hover(|s| s.bg(cx.theme().colors().element_hover)) + .rounded_xs() + .child(file_icon) + .children(file_name) + .children(file_path) + .tooltip(Tooltip::text("Go to File")) + .on_click({ + let buffer = buffer.clone(); + cx.listener(move |this, _, window, cx| { + this.open_edited_buffer(&buffer, window, cx); + }) + }), + ) .child( div() .absolute() @@ -3818,13 +3836,7 @@ impl AcpThreadView { .bottom_0() .right_0() .bg(overlay_gradient), - ) - .on_click({ - let buffer = buffer.clone(); - cx.listener(move |this, _, window, cx| { - this.open_edited_buffer(&buffer, window, cx); - }) - }), + ), ) .child( h_flex() From 4e7ba8e6807e2493ee118c03063545c61722c4d3 Mon Sep 17 00:00:00 2001 From: Xiaobo Liu Date: Mon, 3 Nov 2025 02:11:21 +0800 Subject: [PATCH 14/16] acp_tools: Add vertical scrollbar to ACP logs (#41740) Release Notes: - N/A --------- Signed-off-by: Xiaobo Liu Co-authored-by: Danilo Leal --- crates/acp_tools/src/acp_tools.rs | 51 ++++++++++++++++++------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/crates/acp_tools/src/acp_tools.rs b/crates/acp_tools/src/acp_tools.rs index a40bcbd93c878a85c85d7edd312e713988234966..7615784676c7d9ff1782a6e9537e608cb927154d 100644 --- a/crates/acp_tools/src/acp_tools.rs +++ b/crates/acp_tools/src/acp_tools.rs @@ -19,7 +19,7 @@ use markdown::{CodeBlockRenderer, Markdown, MarkdownElement, MarkdownStyle}; use project::Project; use settings::Settings; use theme::ThemeSettings; -use ui::{Tooltip, prelude::*}; +use ui::{Tooltip, WithScrollbar, prelude::*}; use util::ResultExt as _; use workspace::{ Item, ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, @@ -291,17 +291,19 @@ impl AcpTools { let expanded = self.expanded.contains(&index); v_flex() + .id(index) + .group("message") + .cursor_pointer() + .font_buffer(cx) .w_full() - .px_4() .py_3() - .border_color(colors.border) - .border_b_1() + .pl_4() + .pr_5() .gap_2() .items_start() - .font_buffer(cx) .text_size(base_size) - .id(index) - .group("message") + .border_color(colors.border) + .border_b_1() .hover(|this| this.bg(colors.element_background.opacity(0.5))) .on_click(cx.listener(move |this, _, _, cx| { if this.expanded.contains(&index) { @@ -323,15 +325,14 @@ impl AcpTools { h_flex() .w_full() .gap_2() - .items_center() .flex_shrink_0() .child(match message.direction { - acp::StreamMessageDirection::Incoming => { - ui::Icon::new(ui::IconName::ArrowDown).color(Color::Error) - } - acp::StreamMessageDirection::Outgoing => { - ui::Icon::new(ui::IconName::ArrowUp).color(Color::Success) - } + acp::StreamMessageDirection::Incoming => Icon::new(IconName::ArrowDown) + .color(Color::Error) + .size(IconSize::Small), + acp::StreamMessageDirection::Outgoing => Icon::new(IconName::ArrowUp) + .color(Color::Success) + .size(IconSize::Small), }) .child( Label::new(message.name.clone()) @@ -501,7 +502,7 @@ impl Focusable for AcpTools { } impl Render for AcpTools { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { v_flex() .track_focus(&self.focus_handle) .size_full() @@ -516,13 +517,19 @@ impl Render for AcpTools { .child("No messages recorded yet") .into_any() } else { - list( - connection.list_state.clone(), - cx.processor(Self::render_message), - ) - .with_sizing_behavior(gpui::ListSizingBehavior::Auto) - .flex_grow() - .into_any() + div() + .size_full() + .flex_grow() + .child( + list( + connection.list_state.clone(), + cx.processor(Self::render_message), + ) + .with_sizing_behavior(gpui::ListSizingBehavior::Auto) + .size_full(), + ) + .vertical_scrollbar_for(connection.list_state.clone(), window, cx) + .into_any() } } None => h_flex() From f7153bbe8a869d0e2b25efed64fd7e4217899b63 Mon Sep 17 00:00:00 2001 From: Aero Date: Mon, 3 Nov 2025 03:05:29 +0800 Subject: [PATCH 15/16] agent_ui: Add delete button for compatible API-based LLM providers (#41739) Discussion: https://github.com/zed-industries/zed/discussions/41736 Release Notes: - agent panel: Added the ability to remove OpenAI-compatible LLM providers directly from the UI. --------- Co-authored-by: Danilo Leal --- crates/agent_ui/src/agent_configuration.rs | 87 +++++++++++++++++++++- 1 file changed, 83 insertions(+), 4 deletions(-) diff --git a/crates/agent_ui/src/agent_configuration.rs b/crates/agent_ui/src/agent_configuration.rs index 61f8ee60a794cbd6622759a89efb6f40c8f1503d..7781bdc17bfd57996b196c3f7d684c2d11493776 100644 --- a/crates/agent_ui/src/agent_configuration.rs +++ b/crates/agent_ui/src/agent_configuration.rs @@ -23,16 +23,18 @@ use language::LanguageRegistry; use language_model::{ LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID, }; +use language_models::AllLanguageModelSettings; use notifications::status_toast::{StatusToast, ToastIcon}; use project::{ agent_server_store::{AgentServerStore, CLAUDE_CODE_NAME, CODEX_NAME, GEMINI_NAME}, context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore}, }; use rope::Rope; -use settings::{SettingsStore, update_settings_file}; +use settings::{Settings, SettingsStore, update_settings_file}; use ui::{ - Chip, CommonAnimationExt, ContextMenu, Disclosure, Divider, DividerColor, ElevationIndex, - Indicator, PopoverMenu, Switch, SwitchColor, Tooltip, WithScrollbar, prelude::*, + Button, ButtonStyle, Chip, CommonAnimationExt, ContextMenu, Disclosure, Divider, DividerColor, + ElevationIndex, IconName, IconPosition, IconSize, Indicator, LabelSize, PopoverMenu, Switch, + SwitchColor, Tooltip, WithScrollbar, prelude::*, }; use util::ResultExt as _; use workspace::{Workspace, create_and_open_local_file}; @@ -304,10 +306,76 @@ impl AgentConfiguration { } })), ) - }), + }) + .when( + is_expanded && is_removable_provider(&provider.id(), cx), + |this| { + this.child( + Button::new( + SharedString::from(format!("delete-provider-{provider_id}")), + "Remove Provider", + ) + .full_width() + .style(ButtonStyle::Outlined) + .icon_position(IconPosition::Start) + .icon(IconName::Trash) + .icon_size(IconSize::Small) + .icon_color(Color::Muted) + .label_size(LabelSize::Small) + .on_click(cx.listener({ + let provider = provider.clone(); + move |this, _event, window, cx| { + this.delete_provider(provider.clone(), window, cx); + } + })), + ) + }, + ), ) } + fn delete_provider( + &mut self, + provider: Arc, + window: &mut Window, + cx: &mut Context, + ) { + let fs = self.fs.clone(); + let provider_id = provider.id(); + + cx.spawn_in(window, async move |_, cx| { + cx.update(|_window, cx| { + update_settings_file(fs.clone(), cx, { + let provider_id = provider_id.clone(); + move |settings, _| { + if let Some(ref mut openai_compatible) = settings + .language_models + .as_mut() + .and_then(|lm| lm.openai_compatible.as_mut()) + { + let key_to_remove: Arc = Arc::from(provider_id.0.as_ref()); + openai_compatible.remove(&key_to_remove); + } + } + }); + }) + .log_err(); + + cx.update(|_window, cx| { + LanguageModelRegistry::global(cx).update(cx, { + let provider_id = provider_id.clone(); + move |registry, cx| { + registry.unregister_provider(provider_id, cx); + } + }) + }) + .log_err(); + + anyhow::Ok(()) + }) + .detach_and_log_err(cx); + } + fn render_provider_configuration_section( &mut self, cx: &mut Context, @@ -1225,3 +1293,14 @@ fn find_text_in_buffer( None } } + +// OpenAI-compatible providers are user-configured and can be removed, +// whereas built-in providers (like Anthropic, OpenAI, Google, etc.) can't. +// +// If in the future we have more "API-compatible-type" of providers, +// they should be included here as removable providers. +fn is_removable_provider(provider_id: &LanguageModelProviderId, cx: &App) -> bool { + AllLanguageModelSettings::get_global(cx) + .openai_compatible + .contains_key(provider_id.0.as_ref()) +} From deacd3e92280d124858b8da304884283a81f5ccf Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Mon, 3 Nov 2025 00:54:47 -0300 Subject: [PATCH 16/16] extension_ui: Fix card label truncation (#41784) Closes https://github.com/zed-industries/zed/issues/41763 Release Notes: - N/A --- crates/extensions_ui/src/extensions_ui.rs | 25 ++++++++++------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index 539f2d8864134effdf0a3edcdefa4ca213b7eff3..3a7e1a80dd348d97a54f1dce21794760a2399740 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -805,25 +805,22 @@ impl ExtensionsPage { ) .child( h_flex() - .gap_2() + .gap_1() .justify_between() .child( - h_flex() - .gap_1() - .child( - Icon::new(IconName::Person) - .size(IconSize::XSmall) - .color(Color::Muted), - ) - .child( - Label::new(extension.manifest.authors.join(", ")) - .size(LabelSize::Small) - .color(Color::Muted) - .truncate(), - ), + Icon::new(IconName::Person) + .size(IconSize::XSmall) + .color(Color::Muted), + ) + .child( + Label::new(extension.manifest.authors.join(", ")) + .size(LabelSize::Small) + .color(Color::Muted) + .truncate(), ) .child( h_flex() + .ml_auto() .gap_1() .child( IconButton::new(