From ffc07a2651b3ea54ef1e7cae7d6099813530b144 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Wed, 7 May 2025 01:15:58 +0200 Subject: [PATCH] Use agent panel font size for all content in thread / history views and fix text thread font size adjust (#30041) Release Notes: - N/A --- crates/agent/src/active_thread.rs | 356 +++++++++++++--------------- crates/agent/src/assistant_panel.rs | 188 +++++++++------ 2 files changed, 289 insertions(+), 255 deletions(-) diff --git a/crates/agent/src/active_thread.rs b/crates/agent/src/active_thread.rs index 39061f57f3a07ac9b5bf279a1e6cd16540b25031..ddc43be4e5606c5f61dd41bfc0803629cc02ffe8 100644 --- a/crates/agent/src/active_thread.rs +++ b/crates/agent/src/active_thread.rs @@ -43,7 +43,6 @@ use std::sync::Arc; use std::time::Duration; use text::ToPoint; use theme::ThemeSettings; -use ui::utils::WithRemSize; use ui::{ Disclosure, IconButton, KeyBinding, PopoverMenuHandle, Scrollbar, ScrollbarState, TextSize, Tooltip, prelude::*, @@ -2079,202 +2078,185 @@ impl ActiveThread { let panel_background = cx.theme().colors().panel_background; - WithRemSize::new(ThemeSettings::get_global(cx).agent_font_size(cx)) - .size_full() - .child( - v_flex() - .w_full() - .map(|parent| { - if let Some(checkpoint) = checkpoint.filter(|_| !is_generating) { - let mut is_pending = false; - let mut error = None; - if let Some(last_restore_checkpoint) = - self.thread.read(cx).last_restore_checkpoint() - { - if last_restore_checkpoint.message_id() == message_id { - match last_restore_checkpoint { - LastRestoreCheckpoint::Pending { .. } => is_pending = true, - LastRestoreCheckpoint::Error { error: err, .. } => { - error = Some(err.clone()); - } - } + v_flex() + .w_full() + .map(|parent| { + if let Some(checkpoint) = checkpoint.filter(|_| !is_generating) { + let mut is_pending = false; + let mut error = None; + if let Some(last_restore_checkpoint) = + self.thread.read(cx).last_restore_checkpoint() + { + if last_restore_checkpoint.message_id() == message_id { + match last_restore_checkpoint { + LastRestoreCheckpoint::Pending { .. } => is_pending = true, + LastRestoreCheckpoint::Error { error: err, .. } => { + error = Some(err.clone()); } } + } + } - let restore_checkpoint_button = - Button::new(("restore-checkpoint", ix), "Restore Checkpoint") - .icon(if error.is_some() { - IconName::XCircle - } else { - IconName::Undo - }) - .icon_size(IconSize::XSmall) - .icon_position(IconPosition::Start) - .icon_color(if error.is_some() { - Some(Color::Error) - } else { - None - }) - .label_size(LabelSize::XSmall) - .disabled(is_pending) - .on_click(cx.listener(move |this, _, _window, cx| { - this.thread.update(cx, |thread, cx| { - thread - .restore_checkpoint(checkpoint.clone(), cx) - .detach_and_log_err(cx); - }); - })); - - let restore_checkpoint_button = if is_pending { - restore_checkpoint_button - .with_animation( - ("pulsating-restore-checkpoint-button", ix), - Animation::new(Duration::from_secs(2)) - .repeat() - .with_easing(pulsating_between(0.6, 1.)), - |label, delta| label.alpha(delta), - ) - .into_any_element() - } else if let Some(error) = error { - restore_checkpoint_button - .tooltip(Tooltip::text(error.to_string())) - .into_any_element() + let restore_checkpoint_button = + Button::new(("restore-checkpoint", ix), "Restore Checkpoint") + .icon(if error.is_some() { + IconName::XCircle } else { - restore_checkpoint_button.into_any_element() - }; - - parent.child( - h_flex() - .pt_2p5() - .px_2p5() - .w_full() - .gap_1() - .child(ui::Divider::horizontal()) - .child(restore_checkpoint_button) - .child(ui::Divider::horizontal()), + IconName::Undo + }) + .icon_size(IconSize::XSmall) + .icon_position(IconPosition::Start) + .icon_color(if error.is_some() { + Some(Color::Error) + } else { + None + }) + .label_size(LabelSize::XSmall) + .disabled(is_pending) + .on_click(cx.listener(move |this, _, _window, cx| { + this.thread.update(cx, |thread, cx| { + thread + .restore_checkpoint(checkpoint.clone(), cx) + .detach_and_log_err(cx); + }); + })); + + let restore_checkpoint_button = if is_pending { + restore_checkpoint_button + .with_animation( + ("pulsating-restore-checkpoint-button", ix), + Animation::new(Duration::from_secs(2)) + .repeat() + .with_easing(pulsating_between(0.6, 1.)), + |label, delta| label.alpha(delta), ) - } else { - parent - } - }) - .when(is_first_message, |parent| { - parent.child(self.render_rules_item(cx)) - }) - .child(styled_message) - .when(is_generating && is_last_message, |this| { - this.child( - h_flex() - .h_8() - .mt_2() - .mb_4() - .ml_4() - .py_1p5() - .when_some(loading_dots, |this, loading_dots| { - this.child(loading_dots) - }), - ) - }) - .when(show_feedback, move |parent| { - parent.child(feedback_items).when_some( - self.open_feedback_editors.get(&message_id), - move |parent, feedback_editor| { - let focus_handle = feedback_editor.focus_handle(cx); - parent.child( - v_flex() - .key_context("AgentFeedbackMessageEditor") - .on_action(cx.listener( - move |this, _: &menu::Cancel, _, cx| { - this.open_feedback_editors.remove(&message_id); - cx.notify(); - }, - )) - .on_action(cx.listener( - move |this, _: &menu::Confirm, _, cx| { - this.submit_feedback_message(message_id, cx); - cx.notify(); - }, - )) - .on_action(cx.listener(Self::confirm_editing_message)) - .mb_2() - .mx_4() - .p_2() - .rounded_md() - .border_1() - .border_color(cx.theme().colors().border) - .bg(cx.theme().colors().editor_background) - .child(feedback_editor.clone()) + .into_any_element() + } else if let Some(error) = error { + restore_checkpoint_button + .tooltip(Tooltip::text(error.to_string())) + .into_any_element() + } else { + restore_checkpoint_button.into_any_element() + }; + + parent.child( + h_flex() + .pt_2p5() + .px_2p5() + .w_full() + .gap_1() + .child(ui::Divider::horizontal()) + .child(restore_checkpoint_button) + .child(ui::Divider::horizontal()), + ) + } else { + parent + } + }) + .when(is_first_message, |parent| { + parent.child(self.render_rules_item(cx)) + }) + .child(styled_message) + .when(is_generating && is_last_message, |this| { + this.child( + h_flex() + .h_8() + .mt_2() + .mb_4() + .ml_4() + .py_1p5() + .when_some(loading_dots, |this, loading_dots| this.child(loading_dots)), + ) + }) + .when(show_feedback, move |parent| { + parent.child(feedback_items).when_some( + self.open_feedback_editors.get(&message_id), + move |parent, feedback_editor| { + let focus_handle = feedback_editor.focus_handle(cx); + parent.child( + v_flex() + .key_context("AgentFeedbackMessageEditor") + .on_action(cx.listener(move |this, _: &menu::Cancel, _, cx| { + this.open_feedback_editors.remove(&message_id); + cx.notify(); + })) + .on_action(cx.listener(move |this, _: &menu::Confirm, _, cx| { + this.submit_feedback_message(message_id, cx); + cx.notify(); + })) + .on_action(cx.listener(Self::confirm_editing_message)) + .mb_2() + .mx_4() + .p_2() + .rounded_md() + .border_1() + .border_color(cx.theme().colors().border) + .bg(cx.theme().colors().editor_background) + .child(feedback_editor.clone()) + .child( + h_flex() + .gap_1() + .justify_end() .child( - h_flex() - .gap_1() - .justify_end() - .child( - Button::new( - "dismiss-feedback-message", - "Cancel", - ) - .label_size(LabelSize::Small) - .key_binding( - KeyBinding::for_action_in( - &menu::Cancel, - &focus_handle, - window, - cx, - ) - .map(|kb| kb.size(rems_from_px(10.))), + Button::new("dismiss-feedback-message", "Cancel") + .label_size(LabelSize::Small) + .key_binding( + KeyBinding::for_action_in( + &menu::Cancel, + &focus_handle, + window, + cx, ) - .on_click(cx.listener( - move |this, _, _window, cx| { - this.open_feedback_editors - .remove(&message_id); - cx.notify(); - }, - )), + .map(|kb| kb.size(rems_from_px(10.))), ) - .child( - Button::new( - "submit-feedback-message", - "Share Feedback", - ) - .style(ButtonStyle::Tinted( - ui::TintColor::Accent, - )) - .label_size(LabelSize::Small) - .key_binding( - KeyBinding::for_action_in( - &menu::Confirm, - &focus_handle, - window, - cx, - ) - .map(|kb| kb.size(rems_from_px(10.))), - ) - .on_click(cx.listener( - move |this, _, _window, cx| { - this.submit_feedback_message( - message_id, cx, - ); - cx.notify() - }, - )), - ), + .on_click(cx.listener( + move |this, _, _window, cx| { + this.open_feedback_editors + .remove(&message_id); + cx.notify(); + }, + )), + ) + .child( + Button::new( + "submit-feedback-message", + "Share Feedback", + ) + .style(ButtonStyle::Tinted(ui::TintColor::Accent)) + .label_size(LabelSize::Small) + .key_binding( + KeyBinding::for_action_in( + &menu::Confirm, + &focus_handle, + window, + cx, + ) + .map(|kb| kb.size(rems_from_px(10.))), + ) + .on_click( + cx.listener(move |this, _, _window, cx| { + this.submit_feedback_message(message_id, cx); + cx.notify() + }), + ), ), - ) - }, - ) - }) - .when(after_editing_message, |parent| { - // Backdrop to dim out the whole thread below the editing user message - parent.relative().child( - div() - .occlude() - .absolute() - .inset_0() - .size_full() - .bg(panel_background) - .opacity(0.8), + ), ) - }), - ) + }, + ) + }) + .when(after_editing_message, |parent| { + // Backdrop to dim out the whole thread below the editing user message + parent.relative().child( + div() + .occlude() + .absolute() + .inset_0() + .size_full() + .bg(panel_background) + .opacity(0.8), + ) + }) .into_any() } diff --git a/crates/agent/src/assistant_panel.rs b/crates/agent/src/assistant_panel.rs index 30c678b33d841ab93764c9037c66d71d44dacf1c..4cff679aa994ca1e088b04f33488a950011c25e5 100644 --- a/crates/agent/src/assistant_panel.rs +++ b/crates/agent/src/assistant_panel.rs @@ -39,6 +39,7 @@ use search::{BufferSearchBar, buffer_search}; use settings::{Settings, update_settings_file}; use theme::ThemeSettings; use time::UtcOffset; +use ui::utils::WithRemSize; use ui::{ Banner, CheckboxWithLabel, ContextMenu, KeyBinding, PopoverMenu, PopoverMenuHandle, ProgressBar, Tab, Tooltip, Vector, VectorName, prelude::*, @@ -169,7 +170,21 @@ enum ActiveView { Configuration, } +enum WhichFontSize { + AgentFont, + BufferFont, + None, +} + impl ActiveView { + pub fn which_font_size_used(&self) -> WhichFontSize { + match self { + ActiveView::Thread { .. } | ActiveView::History => WhichFontSize::AgentFont, + ActiveView::PromptEditor { .. } => WhichFontSize::BufferFont, + ActiveView::Configuration => WhichFontSize::None, + } + } + pub fn thread(thread: Entity, window: &mut Window, cx: &mut App) -> Self { let summary = thread.read(cx).summary_or_default(); @@ -1064,7 +1079,7 @@ impl AssistantPanel { _: &mut Window, cx: &mut Context, ) { - self.adjust_font_size(action.persist, px(1.0), cx); + self.handle_font_size_action(action.persist, px(1.0), cx); } pub fn decrease_font_size( @@ -1073,21 +1088,36 @@ impl AssistantPanel { _: &mut Window, cx: &mut Context, ) { - self.adjust_font_size(action.persist, px(-1.0), cx); + self.handle_font_size_action(action.persist, px(-1.0), cx); } - fn adjust_font_size(&mut self, persist: bool, delta: Pixels, cx: &mut Context) { - if persist { - update_settings_file::(self.fs.clone(), cx, move |settings, cx| { - let agent_font_size = ThemeSettings::get_global(cx).agent_font_size(cx) + delta; - let _ = settings - .agent_font_size - .insert(theme::clamp_font_size(agent_font_size).0); - }); - } else { - theme::adjust_agent_font_size(cx, |size| { - *size += delta; - }); + fn handle_font_size_action(&mut self, persist: bool, delta: Pixels, cx: &mut Context) { + match self.active_view.which_font_size_used() { + WhichFontSize::AgentFont => { + if persist { + update_settings_file::( + self.fs.clone(), + cx, + move |settings, cx| { + let agent_font_size = + ThemeSettings::get_global(cx).agent_font_size(cx) + delta; + let _ = settings + .agent_font_size + .insert(theme::clamp_font_size(agent_font_size).0); + }, + ); + } else { + theme::adjust_agent_font_size(cx, |size| { + *size += delta; + }); + } + } + WhichFontSize::BufferFont => { + // Prompt editor uses the buffer font size, so allow the action to propagate to the + // default handler that changes that font size. + cx.propagate(); + } + WhichFontSize::None => {} } } @@ -2552,6 +2582,46 @@ impl AssistantPanel { .into_any() } + fn render_prompt_editor( + &self, + context_editor: &Entity, + buffer_search_bar: &Entity, + window: &mut Window, + cx: &mut Context, + ) -> Div { + let mut registrar = buffer_search::DivRegistrar::new( + |this, _, _cx| match &this.active_view { + ActiveView::PromptEditor { + buffer_search_bar, .. + } => Some(buffer_search_bar.clone()), + _ => None, + }, + cx, + ); + BufferSearchBar::register(&mut registrar); + registrar + .into_div() + .size_full() + .relative() + .map(|parent| { + buffer_search_bar.update(cx, |buffer_search_bar, cx| { + if buffer_search_bar.is_dismissed() { + return parent; + } + parent.child( + div() + .p(DynamicSpacing::Base08.rems(cx)) + .border_b_1() + .border_color(cx.theme().colors().border_variant) + .bg(cx.theme().colors().editor_background) + .child(buffer_search_bar.render(window, cx)), + ) + }) + }) + .child(context_editor.clone()) + .child(self.render_drag_target(cx)) + } + fn render_drag_target(&self, cx: &Context) -> Div { let is_local = self.project.read(cx).is_local(); div() @@ -2675,6 +2745,41 @@ impl AssistantPanel { impl Render for AssistantPanel { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + let content = match &self.active_view { + ActiveView::Thread { .. } => v_flex() + .relative() + .justify_between() + .size_full() + .child(self.render_active_thread_or_empty_state(window, cx)) + .children(self.render_tool_use_limit_reached(cx)) + .child(h_flex().child(self.message_editor.clone())) + .children(self.render_last_error(cx)) + .child(self.render_drag_target(cx)) + .into_any(), + ActiveView::History => self.history.clone().into_any_element(), + ActiveView::PromptEditor { + context_editor, + buffer_search_bar, + .. + } => self + .render_prompt_editor(context_editor, buffer_search_bar, window, cx) + .into_any(), + ActiveView::Configuration => v_flex() + .size_full() + .children(self.configuration.clone()) + .into_any(), + }; + + let content = match self.active_view.which_font_size_used() { + WhichFontSize::AgentFont => { + WithRemSize::new(ThemeSettings::get_global(cx).agent_font_size(cx)) + .size_full() + .child(content) + .into_any() + } + _ => content, + }; + v_flex() .key_context(self.key_context()) .justify_between() @@ -2700,60 +2805,7 @@ impl Render for AssistantPanel { .on_action(cx.listener(Self::reset_font_size)) .child(self.render_toolbar(window, cx)) .children(self.render_trial_upsell(window, cx)) - .map(|parent| match &self.active_view { - ActiveView::Thread { .. } => parent.child( - v_flex() - .relative() - .justify_between() - .size_full() - .child(self.render_active_thread_or_empty_state(window, cx)) - .children(self.render_tool_use_limit_reached(cx)) - .child(h_flex().child(self.message_editor.clone())) - .children(self.render_last_error(cx)) - .child(self.render_drag_target(cx)), - ), - ActiveView::History => parent.child(self.history.clone()), - ActiveView::PromptEditor { - context_editor, - buffer_search_bar, - .. - } => { - let mut registrar = buffer_search::DivRegistrar::new( - |this, _, _cx| match &this.active_view { - ActiveView::PromptEditor { - buffer_search_bar, .. - } => Some(buffer_search_bar.clone()), - _ => None, - }, - cx, - ); - BufferSearchBar::register(&mut registrar); - parent.child( - registrar - .into_div() - .size_full() - .relative() - .map(|parent| { - buffer_search_bar.update(cx, |buffer_search_bar, cx| { - if buffer_search_bar.is_dismissed() { - return parent; - } - parent.child( - div() - .p(DynamicSpacing::Base08.rems(cx)) - .border_b_1() - .border_color(cx.theme().colors().border_variant) - .bg(cx.theme().colors().editor_background) - .child(buffer_search_bar.render(window, cx)), - ) - }) - }) - .child(context_editor.clone()) - .child(self.render_drag_target(cx)), - ) - } - ActiveView::Configuration => parent.children(self.configuration.clone()), - }) + .child(content) } }