diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 765f4fe6c0f270ee88b17d140169095a34dc3a3c..d2903ab6ebc88c0b31d582d277b3d4c03c3e55e0 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -45,7 +45,8 @@ use ::acp_thread::{ use crate::acp::completion_provider::{ContextPickerCompletionProvider, MentionSet}; use crate::acp::message_history::MessageHistory; use crate::agent_diff::AgentDiff; -use crate::{AgentDiffPane, Follow, KeepAll, OpenAgentDiff, RejectAll}; +use crate::message_editor::{MAX_EDITOR_LINES, MIN_EDITOR_LINES}; +use crate::{AgentDiffPane, ExpandMessageEditor, Follow, KeepAll, OpenAgentDiff, RejectAll}; const RESPONSE_PADDING_X: Pixels = px(19.); @@ -65,6 +66,7 @@ pub struct AcpThreadView { expanded_tool_calls: HashSet, expanded_thinking_blocks: HashSet<(usize, usize)>, edits_expanded: bool, + editor_is_expanded: bool, message_history: Rc>>, } @@ -94,6 +96,8 @@ impl AcpThreadView { workspace: WeakEntity, project: Entity, message_history: Rc>>, + min_lines: usize, + max_lines: Option, window: &mut Window, cx: &mut Context, ) -> Self { @@ -113,8 +117,8 @@ impl AcpThreadView { let mut editor = Editor::new( editor::EditorMode::AutoHeight { - min_lines: 4, - max_lines: None, + min_lines, + max_lines: max_lines, }, buffer, None, @@ -182,6 +186,7 @@ impl AcpThreadView { expanded_tool_calls: HashSet::default(), expanded_thinking_blocks: HashSet::default(), edits_expanded: false, + editor_is_expanded: false, message_history, } } @@ -321,6 +326,35 @@ impl AcpThreadView { } } + pub fn expand_message_editor( + &mut self, + _: &ExpandMessageEditor, + _window: &mut Window, + cx: &mut Context, + ) { + self.set_editor_is_expanded(!self.editor_is_expanded, cx); + cx.notify(); + } + + fn set_editor_is_expanded(&mut self, is_expanded: bool, cx: &mut Context) { + self.editor_is_expanded = is_expanded; + self.message_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, + sized_by_content: false, + }) + } else { + editor.set_mode(EditorMode::AutoHeight { + min_lines: MIN_EDITOR_LINES, + max_lines: Some(MAX_EDITOR_LINES), + }) + } + }); + cx.notify(); + } + fn chat(&mut self, _: &Chat, window: &mut Window, cx: &mut Context) { self.last_error.take(); @@ -381,6 +415,7 @@ impl AcpThreadView { let mention_set = self.mention_set.clone(); + self.set_editor_is_expanded(false, cx); self.message_editor.update(cx, |editor, cx| { editor.clear(window, cx); editor.remove_creases(mention_set.lock().drain(), cx) @@ -1793,34 +1828,96 @@ impl AcpThreadView { )) } - fn render_message_editor(&mut self, cx: &mut Context) -> AnyElement { - let settings = ThemeSettings::get_global(cx); - let font_size = TextSize::Small - .rems(cx) - .to_pixels(settings.agent_font_size(cx)); - let line_height = settings.buffer_line_height.value() * font_size; - - let text_style = TextStyle { - color: cx.theme().colors().text, - 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(), - line_height: line_height.into(), - ..Default::default() + fn render_message_editor(&mut self, window: &mut Window, cx: &mut Context) -> AnyElement { + let focus_handle = self.message_editor.focus_handle(cx); + let editor_bg_color = cx.theme().colors().editor_background; + let (expand_icon, expand_tooltip) = if self.editor_is_expanded { + (IconName::Minimize, "Minimize Message Editor") + } else { + (IconName::Maximize, "Expand Message Editor") }; - EditorElement::new( - &self.message_editor, - EditorStyle { - background: cx.theme().colors().editor_background, - local_player: cx.theme().players().local(), - text: text_style, - syntax: cx.theme().syntax().clone(), - ..Default::default() - }, - ) - .into_any() + v_flex() + .on_action(cx.listener(Self::expand_message_editor)) + .p_2() + .gap_2() + .border_t_1() + .border_color(cx.theme().colors().border) + .bg(editor_bg_color) + .when(self.editor_is_expanded, |this| { + this.h(vh(0.8, window)).size_full().justify_between() + }) + .child( + v_flex() + .relative() + .size_full() + .pt_1() + .pr_2p5() + .child(div().flex_1().child({ + let settings = ThemeSettings::get_global(cx); + let font_size = TextSize::Small + .rems(cx) + .to_pixels(settings.agent_font_size(cx)); + let line_height = settings.buffer_line_height.value() * font_size; + + let text_style = TextStyle { + color: cx.theme().colors().text, + 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(), + line_height: line_height.into(), + ..Default::default() + }; + + EditorElement::new( + &self.message_editor, + EditorStyle { + background: editor_bg_color, + local_player: cx.theme().players().local(), + text: text_style, + syntax: cx.theme().syntax().clone(), + ..Default::default() + }, + ) + })) + .child( + h_flex() + .absolute() + .top_0() + .right_0() + .opacity(0.5) + .hover(|this| this.opacity(1.0)) + .child( + IconButton::new("toggle-height", expand_icon) + .icon_size(IconSize::XSmall) + .icon_color(Color::Muted) + .tooltip({ + let focus_handle = focus_handle.clone(); + move |window, cx| { + Tooltip::for_action_in( + expand_tooltip, + &ExpandMessageEditor, + &focus_handle, + window, + cx, + ) + } + }) + .on_click(cx.listener(|_, _, window, cx| { + window.dispatch_action(Box::new(ExpandMessageEditor), cx); + })), + ), + ), + ) + .child( + h_flex() + .flex_none() + .justify_between() + .child(self.render_follow_toggle(cx)) + .child(self.render_send_button(cx)), + ) + .into_any() } fn render_send_button(&self, cx: &mut Context) -> AnyElement { @@ -2132,7 +2229,6 @@ impl Render for AcpThreadView { .px(RESPONSE_PADDING_X) .opacity(0.4) .hover(|style| style.opacity(1.)) - .gap_1() .flex_wrap() .justify_end() .child(open_as_markdown) @@ -2166,22 +2262,7 @@ impl Render for AcpThreadView { ), ) }) - .child( - v_flex() - .p_2() - .pt_3() - .gap_1() - .bg(cx.theme().colors().editor_background) - .border_t_1() - .border_color(cx.theme().colors().border) - .child(self.render_message_editor(cx)) - .child( - h_flex() - .justify_between() - .child(self.render_follow_toggle(cx)) - .child(self.render_send_button(cx)), - ), - ) + .child(self.render_message_editor(window, cx)) } } diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 7f2fbce189280887ff8a7cbbcfa719542a0598d6..36851e44bac6e2455c19a403e417f9722db6b7c6 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize}; use crate::NewExternalAgentThread; use crate::agent_diff::AgentDiffThread; +use crate::message_editor::{MAX_EDITOR_LINES, MIN_EDITOR_LINES}; use crate::{ AddContextServer, AgentDiffPane, ContinueThread, ContinueWithBurnMode, DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread, @@ -960,6 +961,8 @@ impl AgentPanel { workspace.clone(), project, message_history, + MIN_EDITOR_LINES, + Some(MAX_EDITOR_LINES), window, cx, ) diff --git a/crates/agent_ui/src/message_editor.rs b/crates/agent_ui/src/message_editor.rs index ce9cc87fe3f0f3434fac744ec4b9caca8fef11b7..a2cf4aac48a0eb6e368596bc7458615ea1f008a1 100644 --- a/crates/agent_ui/src/message_editor.rs +++ b/crates/agent_ui/src/message_editor.rs @@ -65,6 +65,9 @@ use agent::{ thread_store::{TextThreadStore, ThreadStore}, }; +pub const MIN_EDITOR_LINES: usize = 4; +pub const MAX_EDITOR_LINES: usize = 8; + #[derive(RegisterComponent)] pub struct MessageEditor { thread: Entity, @@ -88,9 +91,6 @@ pub struct MessageEditor { _subscriptions: Vec, } -const MIN_EDITOR_LINES: usize = 4; -const MAX_EDITOR_LINES: usize = 8; - pub(crate) fn create_editor( workspace: WeakEntity, context_store: WeakEntity, @@ -711,11 +711,11 @@ impl MessageEditor { cx.listener(|this, _: &RejectAll, window, cx| this.handle_reject_all(window, cx)), ) .capture_action(cx.listener(Self::paste)) - .gap_2() .p_2() - .bg(editor_bg_color) + .gap_2() .border_t_1() .border_color(cx.theme().colors().border) + .bg(editor_bg_color) .child( h_flex() .justify_between()