@@ -35,7 +35,7 @@ use markdown::parser::{CodeBlockKind, CodeBlockMetadata};
use markdown::{HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle, ParsedMarkdown};
use project::{ProjectEntryId, ProjectItem as _};
use rope::Point;
-use settings::{Settings as _, update_settings_file};
+use settings::{Settings as _, SettingsStore, update_settings_file};
use std::ffi::OsStr;
use std::path::Path;
use std::rc::Rc;
@@ -43,6 +43,7 @@ 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::*,
@@ -764,6 +765,7 @@ impl ActiveThread {
cx.observe(&thread, |_, _, cx| cx.notify()),
cx.subscribe_in(&thread, window, Self::handle_thread_event),
cx.subscribe(&thread_store, Self::handle_rules_loading_error),
+ cx.observe_global::<SettingsStore>(|_, cx| cx.notify()),
];
let list_state = ListState::new(0, ListAlignment::Bottom, px(2048.), {
@@ -1689,12 +1691,14 @@ impl ActiveThread {
fn render_edit_message_editor(
&self,
state: &EditingMessageState,
- window: &mut Window,
+ _window: &mut Window,
cx: &Context<Self>,
) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
- let font_size = TextSize::Small.rems(cx);
- let line_height = font_size.to_pixels(window.rem_size()) * 1.75;
+ let font_size = TextSize::Small
+ .rems(cx)
+ .to_pixels(settings.agent_font_size(cx));
+ let line_height = font_size * 1.75;
let colors = cx.theme().colors();
@@ -2061,185 +2065,202 @@ impl ActiveThread {
let panel_background = cx.theme().colors().panel_background;
- 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());
+ 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());
+ }
+ }
}
}
- }
- }
- 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)
+ 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()
} 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()
- } else {
- restore_checkpoint_button.into_any_element()
- };
+ 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()
+ 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(
- Button::new("dismiss-feedback-message", "Cancel")
- .label_size(LabelSize::Small)
- .key_binding(
- KeyBinding::for_action_in(
- &menu::Cancel,
- &focus_handle,
- window,
- cx,
+ h_flex()
+ .gap_1()
+ .justify_end()
+ .child(
+ Button::new(
+ "dismiss-feedback-message",
+ "Cancel",
)
- .map(|kb| kb.size(rems_from_px(10.))),
- )
- .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,
+ .label_size(LabelSize::Small)
+ .key_binding(
+ KeyBinding::for_action_in(
+ &menu::Cancel,
+ &focus_handle,
+ window,
+ cx,
+ )
+ .map(|kb| kb.size(rems_from_px(10.))),
+ )
+ .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.))),
- )
- .on_click(
- cx.listener(move |this, _, _window, cx| {
- this.submit_feedback_message(message_id, cx);
- 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()
}
@@ -33,6 +33,7 @@ use proto::Plan;
use rules_library::{RulesLibrary, open_rules_library};
use search::{BufferSearchBar, buffer_search::DivRegistrar};
use settings::{Settings, update_settings_file};
+use theme::ThemeSettings;
use time::UtcOffset;
use ui::{
Banner, ContextMenu, KeyBinding, PopoverMenu, PopoverMenuHandle, ProgressBar, Tab, Tooltip,
@@ -43,6 +44,7 @@ use workspace::dock::{DockPosition, Panel, PanelEvent};
use workspace::{CollaboratorId, ToolbarItemView, Workspace};
use zed_actions::agent::OpenConfiguration;
use zed_actions::assistant::{OpenRulesLibrary, ToggleFocus};
+use zed_actions::{DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize};
use zed_llm_client::UsageLimit;
use crate::active_thread::{ActiveThread, ActiveThreadEvent};
@@ -1032,6 +1034,54 @@ impl AssistantPanel {
self.assistant_dropdown_menu_handle.toggle(window, cx);
}
+ pub fn increase_font_size(
+ &mut self,
+ action: &IncreaseBufferFontSize,
+ _: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ self.adjust_font_size(action.persist, px(1.0), cx);
+ }
+
+ pub fn decrease_font_size(
+ &mut self,
+ action: &DecreaseBufferFontSize,
+ _: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ self.adjust_font_size(action.persist, px(-1.0), cx);
+ }
+
+ fn adjust_font_size(&mut self, persist: bool, delta: Pixels, cx: &mut Context<Self>) {
+ if persist {
+ update_settings_file::<ThemeSettings>(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;
+ });
+ }
+ }
+
+ pub fn reset_font_size(
+ &mut self,
+ action: &ResetBufferFontSize,
+ _: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ if action.persist {
+ update_settings_file::<ThemeSettings>(self.fs.clone(), cx, move |settings, _| {
+ settings.agent_font_size = None;
+ });
+ } else {
+ theme::reset_agent_font_size(cx);
+ }
+ }
+
pub fn open_agent_diff(
&mut self,
_: &OpenAgentDiff,
@@ -2371,6 +2421,9 @@ impl Render for AssistantPanel {
.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::increase_font_size))
+ .on_action(cx.listener(Self::decrease_font_size))
+ .on_action(cx.listener(Self::reset_font_size))
.child(self.render_toolbar(window, cx))
.map(|parent| match &self.active_view {
ActiveView::Thread { .. } => parent
@@ -106,6 +106,8 @@ pub struct ThemeSettings {
///
/// The terminal font family can be overridden using it's own setting.
pub buffer_font: Font,
+ /// The agent font size. Determines the size of text in the agent panel.
+ agent_font_size: Pixels,
/// The line height for buffers, and the terminal.
///
/// Changing this may affect the spacing of some UI elements.
@@ -251,6 +253,11 @@ pub(crate) struct UiFontSize(Pixels);
impl Global for UiFontSize {}
+#[derive(Default)]
+pub(crate) struct AgentFontSize(Pixels);
+
+impl Global for AgentFontSize {}
+
/// Represents the selection of a theme, which can be either static or dynamic.
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(untagged)]
@@ -409,6 +416,9 @@ pub struct ThemeSettingsContent {
#[serde(default)]
#[schemars(default = "default_font_features")]
pub buffer_font_features: Option<FontFeatures>,
+ /// The font size for the agent panel.
+ #[serde(default)]
+ pub agent_font_size: Option<f32>,
/// The name of the Zed theme to use.
#[serde(default)]
pub theme: Option<ThemeSelection>,
@@ -579,6 +589,15 @@ impl ThemeSettings {
clamp_font_size(font_size)
}
+ /// Returns the UI font size.
+ pub fn agent_font_size(&self, cx: &App) -> Pixels {
+ let font_size = cx
+ .try_global::<AgentFontSize>()
+ .map(|size| size.0)
+ .unwrap_or(self.agent_font_size);
+ clamp_font_size(font_size)
+ }
+
/// Returns the buffer font size, read from the settings.
///
/// The real buffer font size is stored in-memory, to support temporary font size changes.
@@ -746,6 +765,26 @@ pub fn reset_ui_font_size(cx: &mut App) {
}
}
+/// Sets the adjusted UI font size.
+pub fn adjust_agent_font_size(cx: &mut App, mut f: impl FnMut(&mut Pixels)) {
+ let agent_font_size = ThemeSettings::get_global(cx).agent_font_size(cx);
+ let mut adjusted_size = cx
+ .try_global::<AgentFontSize>()
+ .map_or(agent_font_size, |adjusted_size| adjusted_size.0);
+
+ f(&mut adjusted_size);
+ cx.set_global(AgentFontSize(clamp_font_size(adjusted_size)));
+ cx.refresh_windows();
+}
+
+/// Resets the UI font size to the default value.
+pub fn reset_agent_font_size(cx: &mut App) {
+ if cx.has_global::<AgentFontSize>() {
+ cx.remove_global::<AgentFontSize>();
+ cx.refresh_windows();
+ }
+}
+
/// Ensures font size is within the valid range.
pub fn clamp_font_size(size: Pixels) -> Pixels {
size.max(MIN_FONT_SIZE)
@@ -789,6 +828,7 @@ impl settings::Settings for ThemeSettings {
},
buffer_font_size: defaults.buffer_font_size.unwrap().into(),
buffer_line_height: defaults.buffer_line_height.unwrap(),
+ agent_font_size: defaults.agent_font_size.unwrap().into(),
theme_selection: defaults.theme.clone(),
active_theme: themes
.get(defaults.theme.as_ref().unwrap().theme(*system_appearance))
@@ -891,6 +931,12 @@ impl settings::Settings for ThemeSettings {
);
this.buffer_font_size = this.buffer_font_size.clamp(px(6.), px(100.));
+ merge(
+ &mut this.agent_font_size,
+ value.agent_font_size.map(Into::into),
+ );
+ this.agent_font_size = this.agent_font_size.clamp(px(6.), px(100.));
+
merge(&mut this.buffer_line_height, value.buffer_line_height);
// Clamp the `unnecessary_code_fade` to ensure text can't disappear entirely.