From caefdcd7f104c777cca664b1ee0f7d3300e892d5 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 16 Dec 2024 12:45:01 -0500 Subject: [PATCH] assistant2: Factor out `ContextStrip` (#22096) This PR factors a `ContextStrip` view out of the `MessageEditor` so that we can use it in other places. Release Notes: - N/A --- crates/assistant2/src/assistant.rs | 1 + crates/assistant2/src/context_picker.rs | 14 +-- .../context_picker/fetch_context_picker.rs | 18 ++-- .../src/context_picker/file_context_picker.rs | 42 ++++---- .../context_picker/thread_context_picker.rs | 18 ++-- crates/assistant2/src/context_strip.rs | 101 ++++++++++++++++++ crates/assistant2/src/message_editor.rs | 85 ++------------- 7 files changed, 154 insertions(+), 125 deletions(-) create mode 100644 crates/assistant2/src/context_strip.rs diff --git a/crates/assistant2/src/assistant.rs b/crates/assistant2/src/assistant.rs index 5a7a88f2f3..d7d37fc78b 100644 --- a/crates/assistant2/src/assistant.rs +++ b/crates/assistant2/src/assistant.rs @@ -3,6 +3,7 @@ mod assistant_panel; mod assistant_settings; mod context; mod context_picker; +mod context_strip; mod inline_assistant; mod message_editor; mod prompts; diff --git a/crates/assistant2/src/context_picker.rs b/crates/assistant2/src/context_picker.rs index 0ff5f534b3..5766801bb2 100644 --- a/crates/assistant2/src/context_picker.rs +++ b/crates/assistant2/src/context_picker.rs @@ -16,7 +16,7 @@ use workspace::Workspace; use crate::context_picker::fetch_context_picker::FetchContextPicker; use crate::context_picker::file_context_picker::FileContextPicker; use crate::context_picker::thread_context_picker::ThreadContextPicker; -use crate::message_editor::MessageEditor; +use crate::context_strip::ContextStrip; use crate::thread_store::ThreadStore; #[derive(Debug, Clone)] @@ -36,14 +36,14 @@ impl ContextPicker { pub fn new( workspace: WeakView, thread_store: WeakModel, - message_editor: WeakView, + context_strip: WeakView, cx: &mut ViewContext, ) -> Self { let delegate = ContextPickerDelegate { context_picker: cx.view().downgrade(), workspace, thread_store, - message_editor, + context_strip, entries: vec![ ContextPickerEntry { name: "directory".into(), @@ -122,7 +122,7 @@ pub(crate) struct ContextPickerDelegate { context_picker: WeakView, workspace: WeakView, thread_store: WeakModel, - message_editor: WeakView, + context_strip: WeakView, entries: Vec, selected_ix: usize, } @@ -161,7 +161,7 @@ impl PickerDelegate for ContextPickerDelegate { FileContextPicker::new( self.context_picker.clone(), self.workspace.clone(), - self.message_editor.clone(), + self.context_strip.clone(), cx, ) })); @@ -171,7 +171,7 @@ impl PickerDelegate for ContextPickerDelegate { FetchContextPicker::new( self.context_picker.clone(), self.workspace.clone(), - self.message_editor.clone(), + self.context_strip.clone(), cx, ) })); @@ -181,7 +181,7 @@ impl PickerDelegate for ContextPickerDelegate { ThreadContextPicker::new( self.thread_store.clone(), self.context_picker.clone(), - self.message_editor.clone(), + self.context_strip.clone(), cx, ) })); diff --git a/crates/assistant2/src/context_picker/fetch_context_picker.rs b/crates/assistant2/src/context_picker/fetch_context_picker.rs index 352d3cd057..dd0a9d3d5a 100644 --- a/crates/assistant2/src/context_picker/fetch_context_picker.rs +++ b/crates/assistant2/src/context_picker/fetch_context_picker.rs @@ -13,7 +13,7 @@ use workspace::Workspace; use crate::context::ContextKind; use crate::context_picker::ContextPicker; -use crate::message_editor::MessageEditor; +use crate::context_strip::ContextStrip; pub struct FetchContextPicker { picker: View>, @@ -23,10 +23,10 @@ impl FetchContextPicker { pub fn new( context_picker: WeakView, workspace: WeakView, - message_editor: WeakView, + context_strip: WeakView, cx: &mut ViewContext, ) -> Self { - let delegate = FetchContextPickerDelegate::new(context_picker, workspace, message_editor); + let delegate = FetchContextPickerDelegate::new(context_picker, workspace, context_strip); let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx)); Self { picker } @@ -55,7 +55,7 @@ enum ContentType { pub struct FetchContextPickerDelegate { context_picker: WeakView, workspace: WeakView, - message_editor: WeakView, + context_strip: WeakView, url: String, } @@ -63,12 +63,12 @@ impl FetchContextPickerDelegate { pub fn new( context_picker: WeakView, workspace: WeakView, - message_editor: WeakView, + context_strip: WeakView, ) -> Self { FetchContextPickerDelegate { context_picker, workspace, - message_editor, + context_strip, url: String::new(), } } @@ -189,9 +189,9 @@ impl PickerDelegate for FetchContextPickerDelegate { this.update(&mut cx, |this, cx| { this.delegate - .message_editor - .update(cx, |message_editor, _cx| { - message_editor.insert_context(ContextKind::FetchedUrl, url, text); + .context_strip + .update(cx, |context_strip, _cx| { + context_strip.insert_context(ContextKind::FetchedUrl, url, text); }) })??; diff --git a/crates/assistant2/src/context_picker/file_context_picker.rs b/crates/assistant2/src/context_picker/file_context_picker.rs index c5441bb92a..a0e89aa700 100644 --- a/crates/assistant2/src/context_picker/file_context_picker.rs +++ b/crates/assistant2/src/context_picker/file_context_picker.rs @@ -14,7 +14,7 @@ use workspace::Workspace; use crate::context::ContextKind; use crate::context_picker::ContextPicker; -use crate::message_editor::MessageEditor; +use crate::context_strip::ContextStrip; pub struct FileContextPicker { picker: View>, @@ -24,10 +24,10 @@ impl FileContextPicker { pub fn new( context_picker: WeakView, workspace: WeakView, - message_editor: WeakView, + context_strip: WeakView, cx: &mut ViewContext, ) -> Self { - let delegate = FileContextPickerDelegate::new(context_picker, workspace, message_editor); + let delegate = FileContextPickerDelegate::new(context_picker, workspace, context_strip); let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx)); Self { picker } @@ -49,7 +49,7 @@ impl Render for FileContextPicker { pub struct FileContextPickerDelegate { context_picker: WeakView, workspace: WeakView, - message_editor: WeakView, + context_strip: WeakView, matches: Vec, selected_index: usize, } @@ -58,12 +58,12 @@ impl FileContextPickerDelegate { pub fn new( context_picker: WeakView, workspace: WeakView, - message_editor: WeakView, + context_strip: WeakView, ) -> Self { Self { context_picker, workspace, - message_editor, + context_strip, matches: Vec::new(), selected_index: 0, } @@ -214,24 +214,22 @@ impl PickerDelegate for FileContextPickerDelegate { let buffer = open_buffer_task.await?; this.update(&mut cx, |this, cx| { - this.delegate - .message_editor - .update(cx, |message_editor, cx| { - let mut text = String::new(); - text.push_str(&codeblock_fence_for_path(Some(&path), None)); - text.push_str(&buffer.read(cx).text()); - if !text.ends_with('\n') { - text.push('\n'); - } + this.delegate.context_strip.update(cx, |context_strip, cx| { + let mut text = String::new(); + text.push_str(&codeblock_fence_for_path(Some(&path), None)); + text.push_str(&buffer.read(cx).text()); + if !text.ends_with('\n') { + text.push('\n'); + } - text.push_str("```\n"); + text.push_str("```\n"); - message_editor.insert_context( - ContextKind::File, - path.to_string_lossy().to_string(), - text, - ); - }) + context_strip.insert_context( + ContextKind::File, + path.to_string_lossy().to_string(), + text, + ); + }) })??; anyhow::Ok(()) diff --git a/crates/assistant2/src/context_picker/thread_context_picker.rs b/crates/assistant2/src/context_picker/thread_context_picker.rs index 61b1ba0f05..57b2ee9ce0 100644 --- a/crates/assistant2/src/context_picker/thread_context_picker.rs +++ b/crates/assistant2/src/context_picker/thread_context_picker.rs @@ -7,7 +7,7 @@ use ui::{prelude::*, ListItem}; use crate::context::ContextKind; use crate::context_picker::ContextPicker; -use crate::message_editor::MessageEditor; +use crate::context_strip::ContextStrip; use crate::thread::ThreadId; use crate::thread_store::ThreadStore; @@ -19,11 +19,11 @@ impl ThreadContextPicker { pub fn new( thread_store: WeakModel, context_picker: WeakView, - message_editor: WeakView, + context_strip: WeakView, cx: &mut ViewContext, ) -> Self { let delegate = - ThreadContextPickerDelegate::new(thread_store, context_picker, message_editor); + ThreadContextPickerDelegate::new(thread_store, context_picker, context_strip); let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx)); ThreadContextPicker { picker } @@ -51,7 +51,7 @@ struct ThreadContextEntry { pub struct ThreadContextPickerDelegate { thread_store: WeakModel, context_picker: WeakView, - message_editor: WeakView, + context_strip: WeakView, matches: Vec, selected_index: usize, } @@ -60,12 +60,12 @@ impl ThreadContextPickerDelegate { pub fn new( thread_store: WeakModel, context_picker: WeakView, - message_editor: WeakView, + context_strip: WeakView, ) -> Self { ThreadContextPickerDelegate { thread_store, context_picker, - message_editor, + context_strip, matches: Vec::new(), selected_index: 0, } @@ -157,8 +157,8 @@ impl PickerDelegate for ThreadContextPickerDelegate { return; }; - self.message_editor - .update(cx, |message_editor, cx| { + self.context_strip + .update(cx, |context_strip, cx| { let text = thread.update(cx, |thread, _cx| { let mut text = String::new(); @@ -177,7 +177,7 @@ impl PickerDelegate for ThreadContextPickerDelegate { text }); - message_editor.insert_context(ContextKind::Thread, entry.summary.clone(), text); + context_strip.insert_context(ContextKind::Thread, entry.summary.clone(), text); }) .ok(); } diff --git a/crates/assistant2/src/context_strip.rs b/crates/assistant2/src/context_strip.rs new file mode 100644 index 0000000000..fd5b23a16a --- /dev/null +++ b/crates/assistant2/src/context_strip.rs @@ -0,0 +1,101 @@ +use std::rc::Rc; + +use gpui::{View, WeakModel, WeakView}; +use ui::{prelude::*, IconButtonShape, PopoverMenu, PopoverMenuHandle, Tooltip}; +use workspace::Workspace; + +use crate::context::{Context, ContextId, ContextKind}; +use crate::context_picker::ContextPicker; +use crate::thread_store::ThreadStore; +use crate::ui::ContextPill; + +pub struct ContextStrip { + context: Vec, + next_context_id: ContextId, + context_picker: View, + pub(crate) context_picker_handle: PopoverMenuHandle, +} + +impl ContextStrip { + pub fn new( + workspace: WeakView, + thread_store: WeakModel, + cx: &mut ViewContext, + ) -> Self { + let weak_self = cx.view().downgrade(); + + Self { + context: Vec::new(), + next_context_id: ContextId(0), + context_picker: cx.new_view(|cx| { + ContextPicker::new(workspace.clone(), thread_store.clone(), weak_self, cx) + }), + context_picker_handle: PopoverMenuHandle::default(), + } + } + + pub fn drain(&mut self) -> Vec { + self.context.drain(..).collect() + } + + pub fn insert_context( + &mut self, + kind: ContextKind, + name: impl Into, + text: impl Into, + ) { + self.context.push(Context { + id: self.next_context_id.post_inc(), + name: name.into(), + kind, + text: text.into(), + }); + } +} + +impl Render for ContextStrip { + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + let context_picker = self.context_picker.clone(); + + h_flex() + .flex_wrap() + .gap_2() + .child( + PopoverMenu::new("context-picker") + .menu(move |_cx| Some(context_picker.clone())) + .trigger( + IconButton::new("add-context", IconName::Plus) + .shape(IconButtonShape::Square) + .icon_size(IconSize::Small), + ) + .attach(gpui::AnchorCorner::TopLeft) + .anchor(gpui::AnchorCorner::BottomLeft) + .offset(gpui::Point { + x: px(0.0), + y: px(-16.0), + }) + .with_handle(self.context_picker_handle.clone()), + ) + .children(self.context.iter().map(|context| { + ContextPill::new(context.clone()).on_remove({ + let context = context.clone(); + Rc::new(cx.listener(move |this, _event, cx| { + this.context.retain(|other| other.id != context.id); + cx.notify(); + })) + }) + })) + .when(!self.context.is_empty(), |parent| { + parent.child( + IconButton::new("remove-all-context", IconName::Eraser) + .shape(IconButtonShape::Square) + .icon_size(IconSize::Small) + .tooltip(move |cx| Tooltip::text("Remove All Context", cx)) + .on_click(cx.listener(|this, _event, cx| { + this.context.clear(); + cx.notify(); + })), + ) + }) + } +} diff --git a/crates/assistant2/src/message_editor.rs b/crates/assistant2/src/message_editor.rs index f21caf8a76..f27d0789bb 100644 --- a/crates/assistant2/src/message_editor.rs +++ b/crates/assistant2/src/message_editor.rs @@ -1,31 +1,21 @@ -use std::rc::Rc; - use editor::{Editor, EditorElement, EditorStyle}; use gpui::{AppContext, FocusableView, Model, TextStyle, View, WeakModel, WeakView}; use language_model::{LanguageModelRegistry, LanguageModelRequestTool}; use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu}; use settings::Settings; use theme::ThemeSettings; -use ui::{ - prelude::*, ButtonLike, CheckboxWithLabel, ElevationIndex, IconButtonShape, KeyBinding, - PopoverMenu, PopoverMenuHandle, Tooltip, -}; +use ui::{prelude::*, ButtonLike, CheckboxWithLabel, ElevationIndex, KeyBinding, Tooltip}; use workspace::Workspace; -use crate::context::{Context, ContextId, ContextKind}; -use crate::context_picker::ContextPicker; +use crate::context_strip::ContextStrip; use crate::thread::{RequestKind, Thread}; use crate::thread_store::ThreadStore; -use crate::ui::ContextPill; use crate::{Chat, ToggleModelSelector}; pub struct MessageEditor { thread: Model, editor: View, - context: Vec, - next_context_id: ContextId, - context_picker: View, - pub(crate) context_picker_handle: PopoverMenuHandle, + context_strip: View, language_model_selector: View, use_tools: bool, } @@ -37,7 +27,6 @@ impl MessageEditor { thread: Model, cx: &mut ViewContext, ) -> Self { - let weak_self = cx.view().downgrade(); Self { thread, editor: cx.new_view(|cx| { @@ -46,12 +35,8 @@ impl MessageEditor { editor }), - context: Vec::new(), - next_context_id: ContextId(0), - context_picker: cx.new_view(|cx| { - ContextPicker::new(workspace.clone(), thread_store.clone(), weak_self, cx) - }), - context_picker_handle: PopoverMenuHandle::default(), + context_strip: cx + .new_view(|cx| ContextStrip::new(workspace.clone(), thread_store.clone(), cx)), language_model_selector: cx.new_view(|cx| { LanguageModelSelector::new( |model, _cx| { @@ -64,20 +49,6 @@ impl MessageEditor { } } - pub fn insert_context( - &mut self, - kind: ContextKind, - name: impl Into, - text: impl Into, - ) { - self.context.push(Context { - id: self.next_context_id.post_inc(), - name: name.into(), - kind, - text: text.into(), - }); - } - fn chat(&mut self, _: &Chat, cx: &mut ViewContext) { self.send_to_model(RequestKind::Chat, cx); } @@ -104,7 +75,7 @@ impl MessageEditor { editor.clear(cx); text }); - let context = self.context.drain(..).collect::>(); + let context = self.context_strip.update(cx, |this, _cx| this.drain()); self.thread.update(cx, |thread, cx| { thread.insert_user_message(user_message, context, cx); @@ -190,7 +161,6 @@ impl Render for MessageEditor { let font_size = TextSize::Default.rems(cx); let line_height = font_size.to_pixels(cx.rem_size()) * 1.3; let focus_handle = self.editor.focus_handle(cx); - let context_picker = self.context_picker.clone(); v_flex() .key_context("MessageEditor") @@ -199,48 +169,7 @@ impl Render for MessageEditor { .gap_2() .p_2() .bg(cx.theme().colors().editor_background) - .child( - h_flex() - .flex_wrap() - .gap_2() - .child( - PopoverMenu::new("context-picker") - .menu(move |_cx| Some(context_picker.clone())) - .trigger( - IconButton::new("add-context", IconName::Plus) - .shape(IconButtonShape::Square) - .icon_size(IconSize::Small), - ) - .attach(gpui::AnchorCorner::TopLeft) - .anchor(gpui::AnchorCorner::BottomLeft) - .offset(gpui::Point { - x: px(0.0), - y: px(-16.0), - }) - .with_handle(self.context_picker_handle.clone()), - ) - .children(self.context.iter().map(|context| { - ContextPill::new(context.clone()).on_remove({ - let context = context.clone(); - Rc::new(cx.listener(move |this, _event, cx| { - this.context.retain(|other| other.id != context.id); - cx.notify(); - })) - }) - })) - .when(!self.context.is_empty(), |parent| { - parent.child( - IconButton::new("remove-all-context", IconName::Eraser) - .shape(IconButtonShape::Square) - .icon_size(IconSize::Small) - .tooltip(move |cx| Tooltip::text("Remove All Context", cx)) - .on_click(cx.listener(|this, _event, cx| { - this.context.clear(); - cx.notify(); - })), - ) - }), - ) + .child(self.context_strip.clone()) .child({ let settings = ThemeSettings::get_global(cx); let text_style = TextStyle {