agent: Add `use_modifier_to_send` section in the settings view (#34866)

Danilo Leal created

This PR also converts all of these switch-based settings to use the new
`SwitchField` component, introduced in
https://github.com/zed-industries/zed/pull/34713.

Release Notes:

- agent: Added the ability to change the `use_modifier_to_send` setting
from the agent panel settings UI.

Change summary

crates/agent_ui/src/agent_configuration.rs | 162 ++++++++---------------
crates/ui/src/components/toggle.rs         |  11 +
2 files changed, 69 insertions(+), 104 deletions(-)

Detailed changes

crates/agent_ui/src/agent_configuration.rs 🔗

@@ -28,7 +28,7 @@ use proto::Plan;
 use settings::{Settings, update_settings_file};
 use ui::{
     Chip, ContextMenu, Disclosure, Divider, DividerColor, ElevationIndex, Indicator, PopoverMenu,
-    Scrollbar, ScrollbarState, Switch, SwitchColor, Tooltip, prelude::*,
+    Scrollbar, ScrollbarState, Switch, SwitchColor, SwitchField, Tooltip, prelude::*,
 };
 use util::ResultExt as _;
 use workspace::Workspace;
@@ -330,119 +330,74 @@ impl AgentConfiguration {
 
     fn render_command_permission(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
         let always_allow_tool_actions = AgentSettings::get_global(cx).always_allow_tool_actions;
+        let fs = self.fs.clone();
 
-        h_flex()
-            .gap_4()
-            .justify_between()
-            .flex_wrap()
-            .child(
-                v_flex()
-                    .gap_0p5()
-                    .max_w_5_6()
-                    .child(Label::new("Allow running editing tools without asking for confirmation"))
-                    .child(
-                        Label::new(
-                            "The agent can perform potentially destructive actions without asking for your confirmation.",
-                        )
-                        .color(Color::Muted),
-                    ),
-            )
-            .child(
-                Switch::new(
-                    "always-allow-tool-actions-switch",
-                    always_allow_tool_actions.into(),
-                )
-                .color(SwitchColor::Accent)
-                .on_click({
-                    let fs = self.fs.clone();
-                    move |state, _window, cx| {
-                        let allow = state == &ToggleState::Selected;
-                        update_settings_file::<AgentSettings>(
-                            fs.clone(),
-                            cx,
-                            move |settings, _| {
-                                settings.set_always_allow_tool_actions(allow);
-                            },
-                        );
-                    }
-                }),
-            )
+        SwitchField::new(
+            "single-file-review",
+            "Enable single-file agent reviews",
+            "Agent edits are also displayed in single-file editors for review.",
+            always_allow_tool_actions,
+            move |state, _window, cx| {
+                let allow = state == &ToggleState::Selected;
+                update_settings_file::<AgentSettings>(fs.clone(), cx, move |settings, _| {
+                    settings.set_always_allow_tool_actions(allow);
+                });
+            },
+        )
     }
 
     fn render_single_file_review(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
         let single_file_review = AgentSettings::get_global(cx).single_file_review;
+        let fs = self.fs.clone();
 
-        h_flex()
-            .gap_4()
-            .justify_between()
-            .flex_wrap()
-            .child(
-                v_flex()
-                    .gap_0p5()
-                    .max_w_5_6()
-                    .child(Label::new("Enable single-file agent reviews"))
-                    .child(
-                        Label::new(
-                            "Agent edits are also displayed in single-file editors for review.",
-                        )
-                        .color(Color::Muted),
-                    ),
-            )
-            .child(
-                Switch::new("single-file-review-switch", single_file_review.into())
-                    .color(SwitchColor::Accent)
-                    .on_click({
-                        let fs = self.fs.clone();
-                        move |state, _window, cx| {
-                            let allow = state == &ToggleState::Selected;
-                            update_settings_file::<AgentSettings>(
-                                fs.clone(),
-                                cx,
-                                move |settings, _| {
-                                    settings.set_single_file_review(allow);
-                                },
-                            );
-                        }
-                    }),
-            )
+        SwitchField::new(
+            "single-file-review",
+            "Enable single-file agent reviews",
+            "Agent edits are also displayed in single-file editors for review.",
+            single_file_review,
+            move |state, _window, cx| {
+                let allow = state == &ToggleState::Selected;
+                update_settings_file::<AgentSettings>(fs.clone(), cx, move |settings, _| {
+                    settings.set_single_file_review(allow);
+                });
+            },
+        )
     }
 
     fn render_sound_notification(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
         let play_sound_when_agent_done = AgentSettings::get_global(cx).play_sound_when_agent_done;
+        let fs = self.fs.clone();
 
-        h_flex()
-            .gap_4()
-            .justify_between()
-            .flex_wrap()
-            .child(
-                v_flex()
-                    .gap_0p5()
-                    .max_w_5_6()
-                    .child(Label::new("Play sound when finished generating"))
-                    .child(
-                        Label::new(
-                            "Hear a notification sound when the agent is done generating changes or needs your input.",
-                        )
-                        .color(Color::Muted),
-                    ),
-            )
-            .child(
-                Switch::new("play-sound-notification-switch", play_sound_when_agent_done.into())
-                    .color(SwitchColor::Accent)
-                    .on_click({
-                        let fs = self.fs.clone();
-                        move |state, _window, cx| {
-                            let allow = state == &ToggleState::Selected;
-                            update_settings_file::<AgentSettings>(
-                                fs.clone(),
-                                cx,
-                                move |settings, _| {
-                                    settings.set_play_sound_when_agent_done(allow);
-                                },
-                            );
-                        }
-                    }),
-            )
+        SwitchField::new(
+            "sound-notification",
+            "Play sound when finished generating",
+            "Hear a notification sound when the agent is done generating changes or needs your input.",
+            play_sound_when_agent_done,
+            move |state, _window, cx| {
+                let allow = state == &ToggleState::Selected;
+                update_settings_file::<AgentSettings>(fs.clone(), cx, move |settings, _| {
+                    settings.set_play_sound_when_agent_done(allow);
+                });
+            },
+        )
+    }
+
+    fn render_modifier_to_send(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
+        let use_modifier_to_send = AgentSettings::get_global(cx).use_modifier_to_send;
+        let fs = self.fs.clone();
+
+        SwitchField::new(
+            "modifier-send",
+            "Use modifier to submit a message",
+            "Make a modifier (cmd-enter on macOS, ctrl-enter on Linux) required to send messages.",
+            use_modifier_to_send,
+            move |state, _window, cx| {
+                let allow = state == &ToggleState::Selected;
+                update_settings_file::<AgentSettings>(fs.clone(), cx, move |settings, _| {
+                    settings.set_use_modifier_to_send(allow);
+                });
+            },
+        )
     }
 
     fn render_general_settings_section(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
@@ -456,6 +411,7 @@ impl AgentConfiguration {
             .child(self.render_command_permission(cx))
             .child(self.render_single_file_review(cx))
             .child(self.render_sound_notification(cx))
+            .child(self.render_modifier_to_send(cx))
     }
 
     fn render_zed_plan_info(&self, plan: Option<Plan>, cx: &mut Context<Self>) -> impl IntoElement {

crates/ui/src/components/toggle.rs 🔗

@@ -588,7 +588,7 @@ impl SwitchField {
             toggle_state: toggle_state.into(),
             on_click: Arc::new(on_click),
             disabled: false,
-            color: SwitchColor::default(),
+            color: SwitchColor::Accent,
         }
     }
 
@@ -634,6 +634,15 @@ impl RenderOnce for SwitchField {
                     }
                 }),
             )
+            .when(!self.disabled, |this| {
+                this.on_click({
+                    let on_click = self.on_click.clone();
+                    let toggle_state = self.toggle_state;
+                    move |_click, window, cx| {
+                        (on_click)(&toggle_state.inverse(), window, cx);
+                    }
+                })
+            })
     }
 }