agent: Add keybinding to toggle Burn Mode (#31630)

Danilo Leal created

One caveat with this PR is that the keybinding still doesn't work for text threads. Will do that in a follow-up.

Release Notes:

- agent: Added a keybinding to toggle Burn Mode on and off.

Change summary

assets/keymaps/default-linux.json                       |  3 
assets/keymaps/default-macos.json                       |  3 
crates/agent/src/agent.rs                               |  1 
crates/agent/src/agent_panel.rs                         | 23 +++
crates/agent/src/message_editor.rs                      | 28 +++-
crates/agent/src/ui/max_mode_tooltip.rs                 | 62 ++++++----
crates/assistant_context_editor/src/max_mode_tooltip.rs | 51 ++++----
7 files changed, 108 insertions(+), 63 deletions(-)

Detailed changes

assets/keymaps/default-linux.json 🔗

@@ -250,7 +250,8 @@
       "ctrl-alt-e": "agent::RemoveAllContext",
       "ctrl-shift-e": "project_panel::ToggleFocus",
       "ctrl-shift-enter": "agent::ContinueThread",
-      "alt-enter": "agent::ContinueWithBurnMode"
+      "alt-enter": "agent::ContinueWithBurnMode",
+      "ctrl-alt-b": "agent::ToggleBurnMode"
     }
   },
   {

assets/keymaps/default-macos.json 🔗

@@ -285,7 +285,8 @@
       "cmd-alt-e": "agent::RemoveAllContext",
       "cmd-shift-e": "project_panel::ToggleFocus",
       "cmd-shift-enter": "agent::ContinueThread",
-      "alt-enter": "agent::ContinueWithBurnMode"
+      "alt-enter": "agent::ContinueWithBurnMode",
+      "cmd-alt-b": "agent::ToggleBurnMode"
     }
   },
   {

crates/agent/src/agent_panel.rs 🔗

@@ -67,8 +67,8 @@ use crate::{
     AddContextServer, AgentDiffPane, ContextStore, ContinueThread, ContinueWithBurnMode,
     DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread,
     NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell,
-    ResetTrialUpsell, TextThreadStore, ThreadEvent, ToggleContextPicker, ToggleNavigationMenu,
-    ToggleOptionsMenu,
+    ResetTrialUpsell, TextThreadStore, ThreadEvent, ToggleBurnMode, ToggleContextPicker,
+    ToggleNavigationMenu, ToggleOptionsMenu,
 };
 
 const AGENT_PANEL_KEY: &str = "agent_panel";
@@ -1304,6 +1304,24 @@ impl AgentPanel {
         }
     }
 
+    fn toggle_burn_mode(
+        &mut self,
+        _: &ToggleBurnMode,
+        _window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.thread.update(cx, |active_thread, cx| {
+            active_thread.thread().update(cx, |thread, _cx| {
+                let current_mode = thread.completion_mode();
+
+                thread.set_completion_mode(match current_mode {
+                    CompletionMode::Max => CompletionMode::Normal,
+                    CompletionMode::Normal => CompletionMode::Max,
+                });
+            });
+        });
+    }
+
     pub(crate) fn active_context_editor(&self) -> Option<Entity<ContextEditor>> {
         match &self.active_view {
             ActiveView::PromptEditor { context_editor, .. } => Some(context_editor.clone()),
@@ -3065,6 +3083,7 @@ impl Render for AgentPanel {
                 });
                 this.continue_conversation(window, cx);
             }))
+            .on_action(cx.listener(Self::toggle_burn_mode))
             .child(self.render_toolbar(window, cx))
             .children(self.render_upsell(window, cx))
             .children(self.render_trial_end_upsell(window, cx))

crates/agent/src/message_editor.rs 🔗

@@ -51,7 +51,7 @@ use crate::thread::{MessageCrease, Thread, TokenUsageRatio};
 use crate::thread_store::{TextThreadStore, ThreadStore};
 use crate::{
     ActiveThread, AgentDiffPane, Chat, ChatWithFollow, ExpandMessageEditor, Follow, NewThread,
-    OpenAgentDiff, RemoveAllContext, ToggleContextPicker, ToggleProfileSelector,
+    OpenAgentDiff, RemoveAllContext, ToggleBurnMode, ToggleContextPicker, ToggleProfileSelector,
     register_agent_preview,
 };
 
@@ -471,6 +471,22 @@ impl MessageEditor {
         }
     }
 
+    pub fn toggle_burn_mode(
+        &mut self,
+        _: &ToggleBurnMode,
+        _window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        self.thread.update(cx, |thread, _cx| {
+            let active_completion_mode = thread.completion_mode();
+
+            thread.set_completion_mode(match active_completion_mode {
+                CompletionMode::Max => CompletionMode::Normal,
+                CompletionMode::Normal => CompletionMode::Max,
+            });
+        });
+    }
+
     fn render_max_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
         let thread = self.thread.read(cx);
         let model = thread.configured_model();
@@ -492,13 +508,8 @@ impl MessageEditor {
                 .icon_color(Color::Muted)
                 .toggle_state(max_mode_enabled)
                 .selected_icon_color(Color::Error)
-                .on_click(cx.listener(move |this, _event, _window, cx| {
-                    this.thread.update(cx, |thread, _cx| {
-                        thread.set_completion_mode(match active_completion_mode {
-                            CompletionMode::Max => CompletionMode::Normal,
-                            CompletionMode::Normal => CompletionMode::Max,
-                        });
-                    });
+                .on_click(cx.listener(|this, _event, window, cx| {
+                    this.toggle_burn_mode(&ToggleBurnMode, window, cx);
                 }))
                 .tooltip(move |_window, cx| {
                     cx.new(|_| MaxModeTooltip::new().selected(max_mode_enabled))
@@ -596,6 +607,7 @@ impl MessageEditor {
             .on_action(cx.listener(Self::remove_all_context))
             .on_action(cx.listener(Self::move_up))
             .on_action(cx.listener(Self::expand_message_editor))
+            .on_action(cx.listener(Self::toggle_burn_mode))
             .capture_action(cx.listener(Self::paste))
             .gap_2()
             .p_2()

crates/agent/src/ui/max_mode_tooltip.rs 🔗

@@ -1,5 +1,6 @@
-use gpui::{Context, IntoElement, Render, Window};
-use ui::{prelude::*, tooltip_container};
+use crate::ToggleBurnMode;
+use gpui::{Context, FontWeight, IntoElement, Render, Window};
+use ui::{KeyBinding, prelude::*, tooltip_container};
 
 pub struct MaxModeTooltip {
     selected: bool,
@@ -18,39 +19,48 @@ impl MaxModeTooltip {
 
 impl Render for MaxModeTooltip {
     fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        let icon = if self.selected {
-            IconName::ZedBurnModeOn
+        let (icon, color) = if self.selected {
+            (IconName::ZedBurnModeOn, Color::Error)
         } else {
-            IconName::ZedBurnMode
+            (IconName::ZedBurnMode, Color::Default)
         };
 
+        let turned_on = h_flex()
+            .h_4()
+            .px_1()
+            .border_1()
+            .border_color(cx.theme().colors().border)
+            .bg(cx.theme().colors().text_accent.opacity(0.1))
+            .rounded_sm()
+            .child(
+                Label::new("ON")
+                    .size(LabelSize::XSmall)
+                    .weight(FontWeight::SEMIBOLD)
+                    .color(Color::Accent),
+            );
+
         let title = h_flex()
-            .gap_1()
-            .child(Icon::new(icon).size(IconSize::Small))
-            .child(Label::new("Burn Mode"));
+            .gap_1p5()
+            .child(Icon::new(icon).size(IconSize::Small).color(color))
+            .child(Label::new("Burn Mode"))
+            .when(self.selected, |title| title.child(turned_on));
+
+        let keybinding = KeyBinding::for_action(&ToggleBurnMode, window, cx)
+            .map(|kb| kb.size(rems_from_px(12.)));
 
         tooltip_container(window, cx, |this, _, _| {
-            this.gap_0p5()
-                .map(|header| if self.selected {
-                    header.child(
-                        h_flex()
-                            .justify_between()
-                            .child(title)
-                            .child(
-                                h_flex()
-                                    .gap_0p5()
-                                    .child(Icon::new(IconName::Check).size(IconSize::XSmall).color(Color::Accent))
-                                    .child(Label::new("Turned On").size(LabelSize::XSmall).color(Color::Accent))
-                            )
-                    )
-                } else {
-                    header.child(title)
-                })
+            this
+                .child(
+                    h_flex()
+                        .justify_between()
+                        .child(title)
+                        .children(keybinding)
+                )
                 .child(
                     div()
-                        .max_w_72()
+                        .max_w_64()
                         .child(
-                            Label::new("Enables models to use large context windows, unlimited tool calls, and other capabilities for expanded reasoning, offering an unfettered agentic experience.")
+                            Label::new("Enables models to use large context windows, unlimited tool calls, and other capabilities for expanded reasoning.")
                                 .size(LabelSize::Small)
                                 .color(Color::Muted)
                         )

crates/assistant_context_editor/src/max_mode_tooltip.rs 🔗

@@ -1,4 +1,4 @@
-use gpui::{Context, IntoElement, Render, Window};
+use gpui::{Context, FontWeight, IntoElement, Render, Window};
 use ui::{prelude::*, tooltip_container};
 
 pub struct MaxModeTooltip {
@@ -18,39 +18,40 @@ impl MaxModeTooltip {
 
 impl Render for MaxModeTooltip {
     fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        let icon = if self.selected {
-            IconName::ZedBurnModeOn
+        let (icon, color) = if self.selected {
+            (IconName::ZedBurnModeOn, Color::Error)
         } else {
-            IconName::ZedBurnMode
+            (IconName::ZedBurnMode, Color::Default)
         };
 
+        let turned_on = h_flex()
+            .h_4()
+            .px_1()
+            .border_1()
+            .border_color(cx.theme().colors().border)
+            .bg(cx.theme().colors().text_accent.opacity(0.1))
+            .rounded_sm()
+            .child(
+                Label::new("ON")
+                    .size(LabelSize::XSmall)
+                    .weight(FontWeight::SEMIBOLD)
+                    .color(Color::Accent),
+            );
+
         let title = h_flex()
-            .gap_1()
-            .child(Icon::new(icon).size(IconSize::Small))
-            .child(Label::new("Burn Mode"));
+            .gap_1p5()
+            .child(Icon::new(icon).size(IconSize::Small).color(color))
+            .child(Label::new("Burn Mode"))
+            .when(self.selected, |title| title.child(turned_on));
 
         tooltip_container(window, cx, |this, _, _| {
-            this.gap_0p5()
-                .map(|header| if self.selected {
-                    header.child(
-                        h_flex()
-                            .justify_between()
-                            .child(title)
-                            .child(
-                                h_flex()
-                                    .gap_0p5()
-                                    .child(Icon::new(IconName::Check).size(IconSize::XSmall).color(Color::Accent))
-                                    .child(Label::new("Turned On").size(LabelSize::XSmall).color(Color::Accent))
-                            )
-                    )
-                } else {
-                    header.child(title)
-                })
+            this
+                .child(title)
                 .child(
                     div()
-                        .max_w_72()
+                        .max_w_64()
                         .child(
-                            Label::new("Enables models to use large context windows, unlimited tool calls, and other capabilities for expanded reasoning, offering an unfettered agentic experience.")
+                            Label::new("Enables models to use large context windows, unlimited tool calls, and other capabilities for expanded reasoning.")
                                 .size(LabelSize::Small)
                                 .color(Color::Muted)
                         )