From bc6a483e5c8b9e99913057544913762769292af3 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 27 May 2026 12:08:52 -0400 Subject: [PATCH] Add Actions to open AGENTS.md (#57847) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Screenshot 2026-05-27 at 12 08 26 PM Add command palette actions for opening global and project-specific AGENTS.md files Closes AI-324 Release Notes: - Added commands to open global and project-specific AGENTS.md rules --- crates/agent_ui/src/agent_panel.rs | 106 +++++++++++------- crates/agent_ui/src/agent_ui.rs | 16 +++ crates/command_palette/src/command_palette.rs | 66 ++++++++++- crates/zed_actions/src/lib.rs | 6 + 4 files changed, 149 insertions(+), 45 deletions(-) diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index d19c8ef0120..0894c68685c 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -29,7 +29,8 @@ use zed_actions::{ ResolveConflictsWithAgent, ReviewBranchDiff, }, assistant::{ - CreateSkillFromUrl, FocusAgent, OpenRulesLibrary, OpenSkillCreator, Toggle, ToggleFocus, + CreateSkillFromUrl, FocusAgent, OpenGlobalAgentsMdRules, OpenProjectAgentsMdRules, + OpenRulesLibrary, OpenSkillCreator, Toggle, ToggleFocus, }, }; @@ -179,6 +180,60 @@ fn read_global_last_created_entry_kind(kvp: &KeyValueStore) -> Option, + require_existing_file: bool, + cx: &App, +) -> Option { + let rel_path = util::rel_path::RelPath::unix("AGENTS.md").ok()?; + project + .read(cx) + .visible_worktrees(cx) + .next() + .and_then(|worktree| { + let worktree = worktree.read(cx); + + if require_existing_file { + let entry = worktree.entry_for_path(rel_path)?; + if !entry.is_file() { + return None; + } + } + + Some(worktree.absolutize(rel_path)) + }) +} + +fn open_global_rules(workspace: &mut Workspace, window: &mut Window, cx: &mut Context) { + workspace + .open_abs_path( + paths::agents_file().clone(), + workspace::OpenOptions { + focus: Some(true), + ..Default::default() + }, + window, + cx, + ) + .detach_and_log_err(cx); +} + +fn open_project_rules(workspace: &mut Workspace, window: &mut Window, cx: &mut Context) { + if let Some(path) = project_agents_md_path(workspace.project(), false, cx) { + workspace + .open_abs_path( + path, + workspace::OpenOptions { + focus: Some(true), + ..Default::default() + }, + window, + cx, + ) + .detach_and_log_err(cx); + } +} + async fn write_global_last_created_entry_kind(kvp: KeyValueStore, entry_kind: AgentPanelEntryKind) { if let Some(json) = serde_json::to_string(&LastCreatedEntryKind { entry_kind }).log_err() { kvp.write_kvp(LAST_CREATED_ENTRY_KIND_KEY.to_string(), json) @@ -315,6 +370,12 @@ pub fn init(cx: &mut App) { }); } }) + .register_action(|workspace, _: &OpenGlobalAgentsMdRules, window, cx| { + open_global_rules(workspace, window, cx); + }) + .register_action(|workspace, _: &OpenProjectAgentsMdRules, window, cx| { + open_project_rules(workspace, window, cx); + }) .register_action(|workspace, action: &OpenSkillCreator, window, cx| { if let Some(panel) = workspace.panel::(cx) { workspace.focus_panel::(window, cx); @@ -4862,21 +4923,7 @@ impl AgentPanel { .active_conversation_view() .is_some_and(|conversation_view| conversation_view.read(cx).supports_logout()); - let project_agents_md_path: Option = self - .project - .read(cx) - .visible_worktrees(cx) - .next() - .and_then(|worktree| { - let worktree = worktree.read(cx); - let rel_path = util::rel_path::RelPath::unix("AGENTS.md").ok()?; - let entry = worktree.entry_for_path(rel_path)?; - if entry.is_file() { - Some(worktree.absolutize(rel_path)) - } else { - None - } - }); + let project_agents_md_path = project_agents_md_path(&self.project, true, cx); let global_agents_md_loaded = UserAgentsMd::global(cx) .and_then(|md| md.content()) @@ -4976,24 +5023,14 @@ impl AgentPanel { move |window, cx| { workspace .update(cx, |workspace, cx| { - workspace - .open_abs_path( - paths::agents_file().clone(), - workspace::OpenOptions { - focus: Some(true), - ..Default::default() - }, - window, - cx, - ) - .detach_and_log_err(cx); + open_global_rules(workspace, window, cx); }) .log_err(); }, ); } - if let Some(path) = project_agents_md_path.clone() { + if project_agents_md_path.is_some() { let workspace = workspace.clone(); menu = menu.custom_entry( |_window, _cx| { @@ -5009,20 +5046,9 @@ impl AgentPanel { .into_any_element() }, move |window, cx| { - let path = path.clone(); workspace .update(cx, |workspace, cx| { - workspace - .open_abs_path( - path, - workspace::OpenOptions { - focus: Some(true), - ..Default::default() - }, - window, - cx, - ) - .detach_and_log_err(cx); + open_project_rules(workspace, window, cx); }) .log_err(); }, diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs index 88f0bcb34b9..fc67e12904a 100644 --- a/crates/agent_ui/src/agent_ui.rs +++ b/crates/agent_ui/src/agent_ui.rs @@ -909,6 +909,14 @@ mod tests { !filter.is_hidden(&zed_actions::assistant::CreateSkillFromUrl), "CreateSkillFromUrl should be visible by default" ); + assert!( + !filter.is_hidden(&zed_actions::assistant::OpenGlobalAgentsMdRules), + "OpenGlobalAgentsMdRules should be visible by default" + ); + assert!( + !filter.is_hidden(&zed_actions::assistant::OpenProjectAgentsMdRules), + "OpenProjectAgentsMdRules should be visible by default" + ); }); // Disable agent @@ -932,6 +940,14 @@ mod tests { filter.is_hidden(&NewTerminalThread), "NewTerminalThread should be hidden when agent is disabled" ); + assert!( + filter.is_hidden(&zed_actions::assistant::OpenGlobalAgentsMdRules), + "OpenGlobalAgentsMdRules should be hidden when agent is disabled" + ); + assert!( + filter.is_hidden(&zed_actions::assistant::OpenProjectAgentsMdRules), + "OpenProjectAgentsMdRules should be hidden when agent is disabled" + ); }); // Test EditPredictionProvider diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 35af6f071be..3104fecf204 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -695,26 +695,69 @@ impl PickerDelegate for CommandPaletteDelegate { } pub fn humanize_action_name(name: &str) -> String { - let capacity = name.len() + name.chars().filter(|c| c.is_uppercase()).count(); + let chars = name.chars().collect::>(); + let capacity = name.len() + chars.iter().filter(|c| c.is_uppercase()).count(); let mut result = String::with_capacity(capacity); - for char in name.chars() { + let mut index = 0; + + while index < chars.len() { + let char = chars[index]; if char == ':' { if result.ends_with(':') { result.push(' '); } else { result.push(':'); } + index += 1; } else if char == '_' { result.push(' '); + index += 1; } else if char.is_uppercase() { - if !result.ends_with(' ') { - result.push(' '); + let start = index; + index += 1; + while chars + .get(index) + .is_some_and(|next_char| next_char.is_uppercase()) + { + index += 1; + } + + let uppercase_run = &chars[start..index]; + if uppercase_run.len() > 1 { + let split_before_last = chars + .get(index) + .is_some_and(|next_char| next_char.is_lowercase()); + let acronym_end = if split_before_last { + uppercase_run.len() - 1 + } else { + uppercase_run.len() + }; + + if acronym_end > 0 { + if !result.ends_with(' ') { + result.push(' '); + } + result.extend(&uppercase_run[..acronym_end]); + } + + if split_before_last { + if !result.ends_with(' ') { + result.push(' '); + } + result.extend(uppercase_run[acronym_end].to_lowercase()); + } + } else { + if !result.ends_with(' ') { + result.push(' '); + } + result.extend(char.to_lowercase()); } - result.extend(char.to_lowercase()); } else { result.push(char); + index += 1; } } + result } @@ -753,6 +796,19 @@ mod tests { humanize_action_name("go_to_line::Deploy"), "go to line: deploy" ); + assert_eq!( + humanize_action_name("agent::OpenGlobalAGENTS.mdRules"), + "agent: open global AGENTS.md rules" + ); + assert_eq!( + humanize_action_name("agent::OpenProjectAGENTS.mdRules"), + "agent: open project AGENTS.md rules" + ); + assert_eq!(humanize_action_name("editor::OpenURL"), "editor: open URL"); + assert_eq!( + humanize_action_name("editor::OpenURLParser"), + "editor: open URL parser" + ); } #[test] diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index 384c6fac54b..b84c4c7b97a 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -580,6 +580,12 @@ pub mod assistant { OpenSkillCreator, /// Opens the skill creator window to import a skill from a GitHub URL. CreateSkillFromUrl, + /// Opens the user-global AGENTS.md rules file. + #[action(name = "OpenGlobalAGENTS.mdRules")] + OpenGlobalAgentsMdRules, + /// Opens the project AGENTS.md rules file. + #[action(name = "OpenProjectAGENTS.mdRules")] + OpenProjectAgentsMdRules, ] );