diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index dbb1d14c103075223bf75c98a18fccac52b5bd30..2347f95a8cdef9c9c9c9437c2be1f1a300db5ec3 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -631,6 +631,7 @@ "ctrl-i": "agent::ToggleProfileSelector", "ctrl-alt-/": "assistant::ToggleModelSelector", "ctrl-shift-a": "agent::ToggleContextPicker", + "shift-escape": "agent::ExpandMessageEditor", "ctrl-e": "agent::ChatMode", "ctrl-alt-e": "agent::RemoveAllContext" } diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 7e091862f2c6aa66d346a3bce5b3d60cf90b56b8..7dfbca91946e32fd5dea35cc9ca3c62d5264473e 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -287,6 +287,7 @@ "cmd-i": "agent::ToggleProfileSelector", "cmd-alt-/": "assistant::ToggleModelSelector", "cmd-shift-a": "agent::ToggleContextPicker", + "shift-escape": "agent::ExpandMessageEditor", "cmd-e": "agent::ChatMode", "cmd-alt-e": "agent::RemoveAllContext" } diff --git a/crates/agent/src/assistant.rs b/crates/agent/src/assistant.rs index c9aba03288fb00a7d98c4f0d50dcd8ea6a42e878..b4712229566a2387687aa8dbad64c6c0424304d5 100644 --- a/crates/agent/src/assistant.rs +++ b/crates/agent/src/assistant.rs @@ -50,6 +50,7 @@ actions!( ToggleContextPicker, ToggleProfileSelector, RemoveAllContext, + ExpandMessageEditor, OpenHistory, AddContextServer, RemoveSelectedThread, diff --git a/crates/agent/src/assistant_panel.rs b/crates/agent/src/assistant_panel.rs index e257990d7dc9416ed6bca8195a0e3e4d8e635a4e..384377f7c6919b6376e6c5a10e3a61448447b1c3 100644 --- a/crates/agent/src/assistant_panel.rs +++ b/crates/agent/src/assistant_panel.rs @@ -1298,6 +1298,7 @@ impl AssistantPanel { let configuration_error_ref = &configuration_error; parent + .overflow_hidden() .p_1p5() .justify_end() .gap_1() diff --git a/crates/agent/src/context_picker.rs b/crates/agent/src/context_picker.rs index a370112d0d535982cfed080cfcad186c5502d44c..4d0b48db4d03dcb9b63b1be7ca351243a0ec7efd 100644 --- a/crates/agent/src/context_picker.rs +++ b/crates/agent/src/context_picker.rs @@ -606,12 +606,13 @@ fn render_fold_icon_button( .gap_1() .child( Icon::from_path(icon_path.clone()) - .size(IconSize::Small) + .size(IconSize::XSmall) .color(Color::Muted), ) .child( Label::new(label.clone()) .size(LabelSize::Small) + .buffer_font(cx) .single_line(), ), ) diff --git a/crates/agent/src/message_editor.rs b/crates/agent/src/message_editor.rs index 953c1f0f6834399231221191efd9c1c3c0f4aa7a..81adea894538b56a33a4ffca8d7d4c5379d2fc30 100644 --- a/crates/agent/src/message_editor.rs +++ b/crates/agent/src/message_editor.rs @@ -4,7 +4,8 @@ use crate::assistant_model_selector::ModelType; use collections::HashSet; use editor::actions::MoveUp; use editor::{ - ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement, EditorStyle, MultiBuffer, + ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement, EditorMode, EditorStyle, + MultiBuffer, }; use file_icons::FileIcons; use fs::Fs; @@ -32,8 +33,8 @@ use crate::profile_selector::ProfileSelector; use crate::thread::{RequestKind, Thread, TokenUsageRatio}; use crate::thread_store::ThreadStore; use crate::{ - AgentDiff, Chat, ChatMode, NewThread, OpenAgentDiff, RemoveAllContext, ToggleContextPicker, - ToggleProfileSelector, + AgentDiff, Chat, ChatMode, ExpandMessageEditor, NewThread, OpenAgentDiff, RemoveAllContext, + ToggleContextPicker, ToggleProfileSelector, }; pub struct MessageEditor { @@ -50,10 +51,13 @@ pub struct MessageEditor { model_selector: Entity, profile_selector: Entity, edits_expanded: bool, + editor_is_expanded: bool, waiting_for_summaries_to_send: bool, _subscriptions: Vec, } +const MAX_EDITOR_LINES: usize = 10; + impl MessageEditor { pub fn new( fs: Arc, @@ -80,7 +84,9 @@ impl MessageEditor { let buffer = cx.new(|cx| Buffer::local("", cx).with_language(Arc::new(language), cx)); let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx)); let mut editor = Editor::new( - editor::EditorMode::AutoHeight { max_lines: 10 }, + editor::EditorMode::AutoHeight { + max_lines: MAX_EDITOR_LINES, + }, buffer, None, window, @@ -159,6 +165,7 @@ impl MessageEditor { ) }), edits_expanded: false, + editor_is_expanded: false, waiting_for_summaries_to_send: false, profile_selector: cx .new(|cx| ProfileSelector::new(fs, thread_store, editor.focus_handle(cx), cx)), @@ -170,6 +177,32 @@ impl MessageEditor { cx.notify(); } + fn expand_message_editor( + &mut self, + _: &ExpandMessageEditor, + _window: &mut Window, + cx: &mut Context, + ) { + self.set_editor_is_expanded(!self.editor_is_expanded, cx); + } + + fn set_editor_is_expanded(&mut self, is_expanded: bool, cx: &mut Context) { + self.editor_is_expanded = is_expanded; + self.editor.update(cx, |editor, _| { + if self.editor_is_expanded { + editor.set_mode(EditorMode::Full { + scale_ui_elements_with_buffer_font_size: false, + show_active_line_background: false, + }) + } else { + editor.set_mode(EditorMode::AutoHeight { + max_lines: MAX_EDITOR_LINES, + }) + } + }); + cx.notify(); + } + fn toggle_context_picker( &mut self, _: &ToggleContextPicker, @@ -197,7 +230,10 @@ impl MessageEditor { return; } + self.set_editor_is_expanded(false, cx); self.send_to_model(RequestKind::Chat, window, cx); + + cx.notify(); } fn is_editor_empty(&self, cx: &App) -> bool { @@ -345,12 +381,20 @@ impl Focusable for MessageEditor { impl Render for MessageEditor { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - let font_size = TextSize::Default.rems(cx); + let font_size = TextSize::Small.rems(cx); let line_height = font_size.to_pixels(window.rem_size()) * 1.5; let focus_handle = self.editor.focus_handle(cx); + let focus_handle_clone = focus_handle.clone(); let inline_context_picker = self.inline_context_picker.clone(); + let is_editor_expanded = self.editor_is_expanded; + let expand_icon = if is_editor_expanded { + IconName::Minimize + } else { + IconName::Maximize + }; + let thread = self.thread.read(cx); let is_generating = thread.is_generating(); let total_token_usage = thread.total_token_usage(cx); @@ -657,24 +701,56 @@ impl Render for MessageEditor { .on_action(cx.listener(Self::remove_all_context)) .on_action(cx.listener(Self::move_up)) .on_action(cx.listener(Self::toggle_chat_mode)) + .on_action(cx.listener(Self::expand_message_editor)) .gap_2() .p_2() .bg(editor_bg_color) .border_t_1() .border_color(cx.theme().colors().border) - .child(h_flex().justify_between().child(self.context_strip.clone())) + .child( + h_flex() + .items_start() + .justify_between() + .child(self.context_strip.clone()) + .child( + IconButton::new("toggle-height", expand_icon) + .icon_size(IconSize::XSmall) + .icon_color(Color::Muted) + .tooltip(move |window, cx| { + let focus_handle = focus_handle.clone(); + let expand_label = if is_editor_expanded { + "Minimize Message Editor".to_string() + } else { + "Expand Message Editor".to_string() + }; + + Tooltip::for_action_in( + expand_label, + &ExpandMessageEditor, + &focus_handle, + window, + cx, + ) + }) + .on_click(cx.listener(|_, _, window, cx| { + window.dispatch_action(Box::new(ExpandMessageEditor), cx); + })) + ) + ) .child( v_flex() - .gap_5() - .child({ + .size_full() + .gap_4() + .when(is_editor_expanded, |this| this.h(vh(0.8, window)).justify_between()) + .child(div().when(is_editor_expanded, |this| this.h_full()).child({ let settings = ThemeSettings::get_global(cx); + let text_style = TextStyle { color: cx.theme().colors().text, - font_family: settings.ui_font.family.clone(), - font_fallbacks: settings.ui_font.fallbacks.clone(), - font_features: settings.ui_font.features.clone(), + font_family: settings.buffer_font.family.clone(), + font_fallbacks: settings.buffer_font.fallbacks.clone(), + font_features: settings.buffer_font.features.clone(), font_size: font_size.into(), - font_weight: settings.ui_font.weight, line_height: line_height.into(), ..Default::default() }; @@ -689,7 +765,7 @@ impl Render for MessageEditor { ..Default::default() }, ).into_any() - }) + })) .child( PopoverMenu::new("inline-context-picker") .menu(move |window, cx| { @@ -709,11 +785,13 @@ impl Render for MessageEditor { ) .child( h_flex() + .flex_none() .justify_between() .child(h_flex().gap_2().child(self.profile_selector.clone())) .child( - h_flex().gap_1().child(self.model_selector.clone()) - .map(|parent| { + h_flex().gap_1() + .child(self.model_selector.clone()) + .map(move |parent| { if is_generating { parent.child( IconButton::new("stop-generation", IconName::StopFilled) @@ -727,12 +805,15 @@ impl Render for MessageEditor { cx, ) }) - .on_click(move |_event, window, cx| { - focus_handle.dispatch_action( - &editor::actions::Cancel, - window, - cx, - ); + .on_click({ + let focus_handle = focus_handle_clone.clone(); + move |_event, window, cx| { + focus_handle.dispatch_action( + &editor::actions::Cancel, + window, + cx, + ); + } }) .with_animation( "pulsating-label", @@ -752,8 +833,11 @@ impl Render for MessageEditor { || !is_model_selected || self.waiting_for_summaries_to_send ) - .on_click(move |_event, window, cx| { - focus_handle.dispatch_action(&Chat, window, cx); + .on_click({ + let focus_handle = focus_handle_clone.clone(); + move |_event, window, cx| { + focus_handle.dispatch_action(&Chat, window, cx); + } }) .when(!is_editor_empty && is_model_selected, |button| { button.tooltip(move |window, cx| { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6c7d072a5945ee08e8ae89de8f85e9ca3652ec81..08829ee53a54ed94d1cea0806de2e41b2f30dab8 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -396,14 +396,26 @@ pub enum SelectMode { #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum EditorMode { - SingleLine { auto_width: bool }, - AutoHeight { max_lines: usize }, - Full, + SingleLine { + auto_width: bool, + }, + AutoHeight { + max_lines: usize, + }, + Full { + /// When set to `true`, the editor will scale its UI elements with the buffer font size. + scale_ui_elements_with_buffer_font_size: bool, + /// When set to `true`, the editor will render a background for the active line. + show_active_line_background: bool, + }, } impl EditorMode { pub fn full() -> Self { - Self::Full + Self::Full { + scale_ui_elements_with_buffer_font_size: true, + show_active_line_background: true, + } } pub fn is_full(&self) -> bool { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index aa01db17970685c27968a73b4f2d709575dc881d..91c84e4ca7c338a54a50ef97ad8dc96b7b439f95 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -4176,7 +4176,11 @@ impl EditorElement { self.style.background, )); - if let EditorMode::Full { .. } = layout.mode { + if let EditorMode::Full { + show_active_line_background, + .. + } = layout.mode + { let mut active_rows = layout.active_rows.iter().peekable(); while let Some((start_row, contains_non_empty_selection)) = active_rows.next() { let mut end_row = start_row.0; @@ -4191,7 +4195,7 @@ impl EditorElement { end_row += 1; } - if !contains_non_empty_selection.selection { + if show_active_line_background && !contains_non_empty_selection.selection { let highlight_h_range = match layout.position_map.snapshot.current_line_highlight { CurrentLineHighlight::Gutter => Some(Range { @@ -6414,7 +6418,13 @@ impl EditorElement { /// This allows UI elements to scale based on the `buffer_font_size`. fn rem_size(&self, cx: &mut App) -> Option { match self.editor.read(cx).mode { - EditorMode::Full { .. } => { + EditorMode::Full { + scale_ui_elements_with_buffer_font_size, + .. + } => { + if !scale_ui_elements_with_buffer_font_size { + return None; + } let buffer_font_size = self.style.text.font_size; match buffer_font_size { AbsoluteLength::Pixels(pixels) => {