Detailed changes
@@ -258,7 +258,7 @@
"ctrl-shift-j": "agent::ToggleNavigationMenu",
"ctrl-alt-i": "agent::ToggleOptionsMenu",
"ctrl-alt-shift-n": "agent::ToggleNewThreadMenu",
- "ctrl-alt-shift-t": "agent::ToggleStartThreadInSelector",
+ "ctrl-shift-t": "agent::CycleStartThreadIn",
"shift-alt-escape": "agent::ExpandMessageEditor",
"ctrl->": "agent::AddSelectionToThread",
"ctrl-shift-e": "project_panel::ToggleFocus",
@@ -297,7 +297,7 @@
"cmd-shift-j": "agent::ToggleNavigationMenu",
"cmd-alt-m": "agent::ToggleOptionsMenu",
"cmd-alt-shift-n": "agent::ToggleNewThreadMenu",
- "cmd-alt-shift-t": "agent::ToggleStartThreadInSelector",
+ "cmd-shift-t": "agent::CycleStartThreadIn",
"shift-alt-escape": "agent::ExpandMessageEditor",
"cmd->": "agent::AddSelectionToThread",
"cmd-shift-e": "project_panel::ToggleFocus",
@@ -259,7 +259,7 @@
"shift-alt-j": "agent::ToggleNavigationMenu",
"shift-alt-i": "agent::ToggleOptionsMenu",
"ctrl-shift-alt-n": "agent::ToggleNewThreadMenu",
- "ctrl-shift-alt-t": "agent::ToggleStartThreadInSelector",
+ "ctrl-shift-t": "agent::CycleStartThreadIn",
"shift-alt-escape": "agent::ExpandMessageEditor",
"ctrl-shift-.": "agent::AddSelectionToThread",
"ctrl-shift-e": "project_panel::ToggleFocus",
@@ -560,6 +560,7 @@ mod tests {
message_editor_min_lines: 1,
tool_permissions,
show_turn_stats: false,
+ new_thread_location: Default::default(),
}
}
@@ -12,7 +12,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{
DefaultAgentView, DockPosition, LanguageModelParameters, LanguageModelSelection,
- NotifyWhenAgentWaiting, RegisterSetting, Settings, ToolPermissionMode,
+ NewThreadLocation, NotifyWhenAgentWaiting, RegisterSetting, Settings, ToolPermissionMode,
};
pub use crate::agent_profile::*;
@@ -51,6 +51,7 @@ pub struct AgentSettings {
pub message_editor_min_lines: usize,
pub show_turn_stats: bool,
pub tool_permissions: ToolPermissions,
+ pub new_thread_location: NewThreadLocation,
}
impl AgentSettings {
@@ -438,6 +439,7 @@ impl Settings for AgentSettings {
message_editor_min_lines: agent.message_editor_min_lines.unwrap(),
show_turn_stats: agent.show_turn_stats.unwrap(),
tool_permissions: compile_tool_permissions(agent.tool_permissions),
+ new_thread_location: agent.new_thread_location.unwrap_or_default(),
}
}
}
@@ -29,12 +29,12 @@ use zed_actions::agent::{
ResolveConflictedFilesWithAgent, ResolveConflictsWithAgent, ReviewBranchDiff,
};
-use crate::ui::{AcpOnboardingModal, ClaudeCodeOnboardingModal};
+use crate::ui::{AcpOnboardingModal, ClaudeCodeOnboardingModal, HoldForDefault};
use crate::{
- AddContextServer, AgentDiffPane, ConnectionView, CopyThreadToClipboard, Follow,
- InlineAssistant, LoadThreadFromClipboard, NewTextThread, NewThread, OpenActiveThreadAsMarkdown,
- OpenAgentDiff, OpenHistory, ResetTrialEndUpsell, ResetTrialUpsell, StartThreadIn,
- ToggleNavigationMenu, ToggleNewThreadMenu, ToggleOptionsMenu, ToggleStartThreadInSelector,
+ AddContextServer, AgentDiffPane, ConnectionView, CopyThreadToClipboard, CycleStartThreadIn,
+ Follow, InlineAssistant, LoadThreadFromClipboard, NewTextThread, NewThread,
+ OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell, ResetTrialUpsell,
+ StartThreadIn, ToggleNavigationMenu, ToggleNewThreadMenu, ToggleOptionsMenu,
agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
connection_view::{AcpThreadViewEvent, ThreadView},
slash_command::SlashCommandCompletionProvider,
@@ -312,18 +312,6 @@ pub fn init(cx: &mut App) {
});
}
})
- .register_action(|workspace, _: &ToggleStartThreadInSelector, window, cx| {
- if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
- workspace.focus_panel::<AgentPanel>(window, cx);
- panel.update(cx, |panel, cx| {
- panel.toggle_start_thread_in_selector(
- &ToggleStartThreadInSelector,
- window,
- cx,
- );
- });
- }
- })
.register_action(|workspace, _: &OpenAcpOnboardingModal, window, cx| {
AcpOnboardingModal::toggle(workspace, window, cx)
})
@@ -477,6 +465,13 @@ pub fn init(cx: &mut App) {
});
}
})
+ .register_action(|workspace, _: &CycleStartThreadIn, _window, cx| {
+ if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
+ panel.update(cx, |panel, cx| {
+ panel.cycle_start_thread_in(cx);
+ });
+ }
+ })
.register_action(|workspace, _: &ToggleWorkspaceSidebar, window, cx| {
if !multi_workspace_enabled(cx) {
return;
@@ -1751,15 +1746,6 @@ impl AgentPanel {
self.new_thread_menu_handle.toggle(window, cx);
}
- pub fn toggle_start_thread_in_selector(
- &mut self,
- _: &ToggleStartThreadInSelector,
- window: &mut Window,
- cx: &mut Context<Self>,
- ) {
- self.start_thread_in_menu_handle.toggle(window, cx);
- }
-
pub fn increase_font_size(
&mut self,
action: &IncreaseBufferFontSize,
@@ -2388,6 +2374,28 @@ impl AgentPanel {
cx.notify();
}
+ fn cycle_start_thread_in(&mut self, cx: &mut Context<Self>) {
+ let next = match self.start_thread_in {
+ StartThreadIn::LocalProject => StartThreadIn::NewWorktree,
+ StartThreadIn::NewWorktree => StartThreadIn::LocalProject,
+ };
+ self.set_start_thread_in(&next, cx);
+ }
+
+ fn reset_start_thread_in_to_default(&mut self, cx: &mut Context<Self>) {
+ use settings::{NewThreadLocation, Settings};
+ let default = AgentSettings::get_global(cx).new_thread_location;
+ let start_thread_in = match default {
+ NewThreadLocation::LocalProject => StartThreadIn::LocalProject,
+ NewThreadLocation::NewWorktree => StartThreadIn::NewWorktree,
+ };
+ if self.start_thread_in != start_thread_in {
+ self.start_thread_in = start_thread_in;
+ self.serialize(cx);
+ cx.notify();
+ }
+ }
+
fn selected_external_agent(&self) -> Option<Agent> {
match &self.selected_agent {
AgentType::NativeAgent => Some(Agent::NativeAgent),
@@ -2445,6 +2453,7 @@ impl AgentPanel {
window: &mut Window,
cx: &mut Context<Self>,
) {
+ self.reset_start_thread_in_to_default(cx);
self.new_agent_thread_inner(agent, true, window, cx);
}
@@ -3592,9 +3601,12 @@ impl AgentPanel {
}
fn render_start_thread_in_selector(&self, cx: &mut Context<Self>) -> impl IntoElement {
+ use settings::{NewThreadLocation, Settings};
+
let focus_handle = self.focus_handle(cx);
let has_git_repo = self.project_has_git_repository(cx);
let is_via_collab = self.project.read(cx).is_via_collab();
+ let fs = self.fs.clone();
let is_creating = matches!(
self.worktree_creation_status,
@@ -3604,6 +3616,10 @@ impl AgentPanel {
let current_target = self.start_thread_in;
let trigger_label = self.start_thread_in.label();
+ let new_thread_location = AgentSettings::get_global(cx).new_thread_location;
+ let is_local_default = new_thread_location == NewThreadLocation::LocalProject;
+ let is_new_worktree_default = new_thread_location == NewThreadLocation::NewWorktree;
+
let icon = if self.start_thread_in_menu_handle.is_deployed() {
IconName::ChevronUp
} else {
@@ -3631,7 +3647,7 @@ impl AgentPanel {
move |_window, cx| {
Tooltip::for_action_in(
"Start Thread Inβ¦",
- &ToggleStartThreadInSelector,
+ &CycleStartThreadIn,
&focus_handle,
cx,
)
@@ -3640,6 +3656,7 @@ impl AgentPanel {
.menu(move |window, cx| {
let is_local_selected = current_target == StartThreadIn::LocalProject;
let is_new_worktree_selected = current_target == StartThreadIn::NewWorktree;
+ let fs = fs.clone();
Some(ContextMenu::build(window, cx, move |menu, _window, _cx| {
let new_worktree_disabled = !has_git_repo || is_via_collab;
@@ -3648,18 +3665,53 @@ impl AgentPanel {
.item(
ContextMenuEntry::new("Current Project")
.toggleable(IconPosition::End, is_local_selected)
- .handler(|window, cx| {
- window
- .dispatch_action(Box::new(StartThreadIn::LocalProject), cx);
+ .documentation_aside(documentation_side, move |_| {
+ HoldForDefault::new(is_local_default)
+ .more_content(false)
+ .into_any_element()
+ })
+ .handler({
+ let fs = fs.clone();
+ move |window, cx| {
+ if window.modifiers().secondary() {
+ update_settings_file(fs.clone(), cx, |settings, _| {
+ settings
+ .agent
+ .get_or_insert_default()
+ .set_new_thread_location(
+ NewThreadLocation::LocalProject,
+ );
+ });
+ }
+ window.dispatch_action(
+ Box::new(StartThreadIn::LocalProject),
+ cx,
+ );
+ }
}),
)
.item({
let entry = ContextMenuEntry::new("New Worktree")
.toggleable(IconPosition::End, is_new_worktree_selected)
.disabled(new_worktree_disabled)
- .handler(|window, cx| {
- window
- .dispatch_action(Box::new(StartThreadIn::NewWorktree), cx);
+ .handler({
+ let fs = fs.clone();
+ move |window, cx| {
+ if window.modifiers().secondary() {
+ update_settings_file(fs.clone(), cx, |settings, _| {
+ settings
+ .agent
+ .get_or_insert_default()
+ .set_new_thread_location(
+ NewThreadLocation::NewWorktree,
+ );
+ });
+ }
+ window.dispatch_action(
+ Box::new(StartThreadIn::NewWorktree),
+ cx,
+ );
+ }
});
if new_worktree_disabled {
@@ -3675,7 +3727,11 @@ impl AgentPanel {
.into_any_element()
})
} else {
- entry
+ entry.documentation_aside(documentation_side, move |_| {
+ HoldForDefault::new(is_new_worktree_default)
+ .more_content(false)
+ .into_any_element()
+ })
}
})
}))
@@ -4849,7 +4905,6 @@ impl Render for AgentPanel {
.on_action(cx.listener(Self::go_back))
.on_action(cx.listener(Self::toggle_navigation_menu))
.on_action(cx.listener(Self::toggle_options_menu))
- .on_action(cx.listener(Self::toggle_start_thread_in_selector))
.on_action(cx.listener(Self::increase_font_size))
.on_action(cx.listener(Self::decrease_font_size))
.on_action(cx.listener(Self::reset_font_size))
@@ -86,8 +86,8 @@ actions!(
NewTextThread,
/// Toggles the menu to create new agent threads.
ToggleNewThreadMenu,
- /// Toggles the selector for choosing where new threads start (current project or new worktree).
- ToggleStartThreadInSelector,
+ /// Cycles through the options for where new threads start (current project or new worktree).
+ CycleStartThreadIn,
/// Toggles the navigation menu for switching between threads and views.
ToggleNavigationMenu,
/// Toggles the options menu for agent settings and preferences.
@@ -655,6 +655,7 @@ mod tests {
message_editor_min_lines: 1,
tool_permissions: Default::default(),
show_turn_stats: false,
+ new_thread_location: Default::default(),
};
cx.update(|cx| {
@@ -4,20 +4,31 @@ use ui::{prelude::*, render_modifiers};
#[derive(IntoElement)]
pub struct HoldForDefault {
is_default: bool,
+ more_content: bool,
}
impl HoldForDefault {
pub fn new(is_default: bool) -> Self {
- Self { is_default }
+ Self {
+ is_default,
+ more_content: true,
+ }
+ }
+
+ pub fn more_content(mut self, more_content: bool) -> Self {
+ self.more_content = more_content;
+ self
}
}
impl RenderOnce for HoldForDefault {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
h_flex()
- .pt_1()
- .border_t_1()
- .border_color(cx.theme().colors().border_variant)
+ .when(self.more_content, |this| {
+ this.pt_1()
+ .border_t_1()
+ .border_color(cx.theme().colors().border_variant)
+ })
.gap_0p5()
.text_sm()
.text_color(Color::Muted.color(cx))
@@ -9,6 +9,19 @@ use crate::ExtendingVec;
use crate::DockPosition;
+/// Where new threads should start by default.
+#[derive(
+ Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom,
+)]
+#[serde(rename_all = "snake_case")]
+pub enum NewThreadLocation {
+ /// Start threads in the current project.
+ #[default]
+ LocalProject,
+ /// Start threads in a new worktree.
+ NewWorktree,
+}
+
#[with_fallible_options]
#[derive(Clone, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, Default)]
pub struct AgentSettingsContent {
@@ -59,6 +72,10 @@ pub struct AgentSettingsContent {
///
/// Default: "thread"
pub default_view: Option<DefaultAgentView>,
+ /// Where new threads should start by default.
+ ///
+ /// Default: "local_project"
+ pub new_thread_location: Option<NewThreadLocation>,
/// The available agent profiles.
pub profiles: Option<IndexMap<Arc<str>, AgentProfileContent>>,
/// Where to show a popup notification when the agent is waiting for user input.
@@ -146,6 +163,10 @@ impl AgentSettingsContent {
self.default_profile = Some(profile_id);
}
+ pub fn set_new_thread_location(&mut self, value: NewThreadLocation) {
+ self.new_thread_location = Some(value);
+ }
+
pub fn add_favorite_model(&mut self, model: LanguageModelSelection) {
if !self.favorite_models.contains(&model) {
self.favorite_models.push(model);
@@ -6972,7 +6972,7 @@ fn ai_page() -> SettingsPage {
]
}
- fn agent_configuration_section() -> [SettingsPageItem; 12] {
+ fn agent_configuration_section() -> [SettingsPageItem; 13] {
[
SettingsPageItem::SectionHeader("Agent Configuration"),
SettingsPageItem::SubPageLink(SubPageLink {
@@ -6984,6 +6984,28 @@ fn ai_page() -> SettingsPage {
files: USER,
render: render_tool_permissions_setup_page,
}),
+ SettingsPageItem::SettingItem(SettingItem {
+ title: "New Thread Location",
+ description: "Whether to start a new thread in the current local project or in a new Git worktree.",
+ field: Box::new(SettingField {
+ json_path: Some("agent.default_start_thread_in"),
+ pick: |settings_content| {
+ settings_content
+ .agent
+ .as_ref()?
+ .new_thread_location
+ .as_ref()
+ },
+ write: |settings_content, value| {
+ settings_content
+ .agent
+ .get_or_insert_default()
+ .new_thread_location = value;
+ },
+ }),
+ metadata: None,
+ files: USER,
+ }),
SettingsPageItem::SettingItem(SettingItem {
title: "Single File Review",
description: "When enabled, agent edits will also be displayed in single-file buffers for review.",