Remove the nested Pane from the assistant
Since we don't want tabs, I think it would be better to render the toolbar for ourselves directly and handle switching between conversations. Co-Authored-By: Julia Risley <julia@zed.dev>
This commit is contained in:
parent
7a051a0dcb
commit
23bc11f8b3
9 changed files with 189 additions and 177 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -109,6 +109,7 @@ dependencies = [
|
||||||
"isahc",
|
"isahc",
|
||||||
"language",
|
"language",
|
||||||
"menu",
|
"menu",
|
||||||
|
"project",
|
||||||
"regex",
|
"regex",
|
||||||
"schemars",
|
"schemars",
|
||||||
"search",
|
"search",
|
||||||
|
|
|
@ -40,7 +40,8 @@
|
||||||
"cmd-o": "workspace::Open",
|
"cmd-o": "workspace::Open",
|
||||||
"alt-cmd-o": "projects::OpenRecent",
|
"alt-cmd-o": "projects::OpenRecent",
|
||||||
"ctrl-~": "workspace::NewTerminal",
|
"ctrl-~": "workspace::NewTerminal",
|
||||||
"ctrl-`": "terminal_panel::ToggleFocus"
|
"ctrl-`": "terminal_panel::ToggleFocus",
|
||||||
|
"shift-escape": "workspace::ToggleZoom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -235,8 +236,7 @@
|
||||||
"cmd-shift-g": "search::SelectPrevMatch",
|
"cmd-shift-g": "search::SelectPrevMatch",
|
||||||
"alt-cmd-c": "search::ToggleCaseSensitive",
|
"alt-cmd-c": "search::ToggleCaseSensitive",
|
||||||
"alt-cmd-w": "search::ToggleWholeWord",
|
"alt-cmd-w": "search::ToggleWholeWord",
|
||||||
"alt-cmd-r": "search::ToggleRegex",
|
"alt-cmd-r": "search::ToggleRegex"
|
||||||
"shift-escape": "workspace::ToggleZoom"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Bindings from VS Code
|
// Bindings from VS Code
|
||||||
|
|
|
@ -57,37 +57,37 @@
|
||||||
"show_whitespaces": "selection",
|
"show_whitespaces": "selection",
|
||||||
// Scrollbar related settings
|
// Scrollbar related settings
|
||||||
"scrollbar": {
|
"scrollbar": {
|
||||||
// When to show the scrollbar in the editor.
|
// When to show the scrollbar in the editor.
|
||||||
// This setting can take four values:
|
// This setting can take four values:
|
||||||
//
|
//
|
||||||
// 1. Show the scrollbar if there's important information or
|
// 1. Show the scrollbar if there's important information or
|
||||||
// follow the system's configured behavior (default):
|
// follow the system's configured behavior (default):
|
||||||
// "auto"
|
// "auto"
|
||||||
// 2. Match the system's configured behavior:
|
// 2. Match the system's configured behavior:
|
||||||
// "system"
|
// "system"
|
||||||
// 3. Always show the scrollbar:
|
// 3. Always show the scrollbar:
|
||||||
// "always"
|
// "always"
|
||||||
// 4. Never show the scrollbar:
|
// 4. Never show the scrollbar:
|
||||||
// "never"
|
// "never"
|
||||||
"show": "auto",
|
"show": "auto",
|
||||||
// Whether to show git diff indicators in the scrollbar.
|
// Whether to show git diff indicators in the scrollbar.
|
||||||
"git_diff": true
|
"git_diff": true
|
||||||
},
|
},
|
||||||
"project_panel": {
|
"project_panel": {
|
||||||
// Whether to show the git status in the project panel.
|
// Whether to show the git status in the project panel.
|
||||||
"git_status": true,
|
"git_status": true,
|
||||||
// Where to dock project panel. Can be 'left' or 'right'.
|
// Where to dock project panel. Can be 'left' or 'right'.
|
||||||
"dock": "left",
|
"dock": "left",
|
||||||
// Default width of the project panel.
|
// Default width of the project panel.
|
||||||
"default_width": 240
|
"default_width": 240
|
||||||
},
|
},
|
||||||
"assistant": {
|
"assistant": {
|
||||||
// Where to dock the assistant. Can be 'left', 'right' or 'bottom'.
|
// Where to dock the assistant. Can be 'left', 'right' or 'bottom'.
|
||||||
"dock": "right",
|
"dock": "right",
|
||||||
// Default width when the assistant is docked to the left or right.
|
// Default width when the assistant is docked to the left or right.
|
||||||
"default_width": 450,
|
"default_width": 450,
|
||||||
// Default height when the assistant is docked to the bottom.
|
// Default height when the assistant is docked to the bottom.
|
||||||
"default_height": 320
|
"default_height": 320
|
||||||
},
|
},
|
||||||
// Whether the screen sharing icon is shown in the os status bar.
|
// Whether the screen sharing icon is shown in the os status bar.
|
||||||
"show_call_status_icon": true,
|
"show_call_status_icon": true,
|
||||||
|
|
|
@ -34,3 +34,4 @@ tiktoken-rs = "0.4"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
editor = { path = "../editor", features = ["test-support"] }
|
editor = { path = "../editor", features = ["test-support"] }
|
||||||
|
project = { path = "../project", features = ["test-support"] }
|
||||||
|
|
|
@ -37,7 +37,7 @@ use util::{
|
||||||
use workspace::{
|
use workspace::{
|
||||||
dock::{DockPosition, Panel},
|
dock::{DockPosition, Panel},
|
||||||
item::Item,
|
item::Item,
|
||||||
pane, Pane, Save, Workspace,
|
Save, Workspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
const OPENAI_API_URL: &'static str = "https://api.openai.com/v1";
|
const OPENAI_API_URL: &'static str = "https://api.openai.com/v1";
|
||||||
|
@ -66,21 +66,24 @@ pub fn init(cx: &mut AppContext) {
|
||||||
cx.add_action(
|
cx.add_action(
|
||||||
|workspace: &mut Workspace, _: &NewContext, cx: &mut ViewContext<Workspace>| {
|
|workspace: &mut Workspace, _: &NewContext, cx: &mut ViewContext<Workspace>| {
|
||||||
if let Some(this) = workspace.panel::<AssistantPanel>(cx) {
|
if let Some(this) = workspace.panel::<AssistantPanel>(cx) {
|
||||||
this.update(cx, |this, cx| this.add_context(cx))
|
this.update(cx, |this, cx| {
|
||||||
|
this.add_conversation(cx);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
workspace.focus_panel::<AssistantPanel>(cx);
|
workspace.focus_panel::<AssistantPanel>(cx);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
cx.add_action(AssistantEditor::assist);
|
cx.add_action(ConversationEditor::assist);
|
||||||
cx.capture_action(AssistantEditor::cancel_last_assist);
|
cx.capture_action(ConversationEditor::cancel_last_assist);
|
||||||
cx.capture_action(AssistantEditor::save);
|
cx.capture_action(ConversationEditor::save);
|
||||||
cx.add_action(AssistantEditor::quote_selection);
|
cx.add_action(ConversationEditor::quote_selection);
|
||||||
cx.capture_action(AssistantEditor::copy);
|
cx.capture_action(ConversationEditor::copy);
|
||||||
cx.capture_action(AssistantEditor::split);
|
cx.capture_action(ConversationEditor::split);
|
||||||
cx.capture_action(AssistantEditor::cycle_message_role);
|
cx.capture_action(ConversationEditor::cycle_message_role);
|
||||||
cx.add_action(AssistantPanel::save_api_key);
|
cx.add_action(AssistantPanel::save_api_key);
|
||||||
cx.add_action(AssistantPanel::reset_api_key);
|
cx.add_action(AssistantPanel::reset_api_key);
|
||||||
|
cx.add_action(AssistantPanel::toggle_zoom);
|
||||||
cx.add_action(
|
cx.add_action(
|
||||||
|workspace: &mut Workspace, _: &ToggleFocus, cx: &mut ViewContext<Workspace>| {
|
|workspace: &mut Workspace, _: &ToggleFocus, cx: &mut ViewContext<Workspace>| {
|
||||||
workspace.toggle_panel_focus::<AssistantPanel>(cx);
|
workspace.toggle_panel_focus::<AssistantPanel>(cx);
|
||||||
|
@ -88,6 +91,7 @@ pub fn init(cx: &mut AppContext) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum AssistantPanelEvent {
|
pub enum AssistantPanelEvent {
|
||||||
ZoomIn,
|
ZoomIn,
|
||||||
ZoomOut,
|
ZoomOut,
|
||||||
|
@ -99,14 +103,17 @@ pub enum AssistantPanelEvent {
|
||||||
pub struct AssistantPanel {
|
pub struct AssistantPanel {
|
||||||
width: Option<f32>,
|
width: Option<f32>,
|
||||||
height: Option<f32>,
|
height: Option<f32>,
|
||||||
pane: ViewHandle<Pane>,
|
active_conversation_index: usize,
|
||||||
|
conversation_editors: Vec<ViewHandle<ConversationEditor>>,
|
||||||
|
saved_conversations: Vec<SavedConversationMetadata>,
|
||||||
|
zoomed: bool,
|
||||||
|
has_focus: bool,
|
||||||
api_key: Rc<RefCell<Option<String>>>,
|
api_key: Rc<RefCell<Option<String>>>,
|
||||||
api_key_editor: Option<ViewHandle<Editor>>,
|
api_key_editor: Option<ViewHandle<Editor>>,
|
||||||
has_read_credentials: bool,
|
has_read_credentials: bool,
|
||||||
languages: Arc<LanguageRegistry>,
|
languages: Arc<LanguageRegistry>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
subscriptions: Vec<Subscription>,
|
subscriptions: Vec<Subscription>,
|
||||||
saved_conversations: Vec<SavedConversationMetadata>,
|
|
||||||
_watch_saved_conversations: Task<Result<()>>,
|
_watch_saved_conversations: Task<Result<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,61 +132,6 @@ impl AssistantPanel {
|
||||||
// TODO: deserialize state.
|
// TODO: deserialize state.
|
||||||
workspace.update(&mut cx, |workspace, cx| {
|
workspace.update(&mut cx, |workspace, cx| {
|
||||||
cx.add_view::<Self, _>(|cx| {
|
cx.add_view::<Self, _>(|cx| {
|
||||||
let weak_self = cx.weak_handle();
|
|
||||||
let pane = cx.add_view(|cx| {
|
|
||||||
let mut pane = Pane::new(
|
|
||||||
workspace.weak_handle(),
|
|
||||||
workspace.project().clone(),
|
|
||||||
workspace.app_state().background_actions,
|
|
||||||
Default::default(),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
pane.set_can_split(false, cx);
|
|
||||||
pane.set_can_navigate(false, cx);
|
|
||||||
pane.on_can_drop(move |_, _| false);
|
|
||||||
pane.set_render_tab_bar_buttons(cx, move |pane, cx| {
|
|
||||||
let weak_self = weak_self.clone();
|
|
||||||
Flex::row()
|
|
||||||
.with_child(Pane::render_tab_bar_button(
|
|
||||||
0,
|
|
||||||
"icons/plus_12.svg",
|
|
||||||
false,
|
|
||||||
Some(("New Context".into(), Some(Box::new(NewContext)))),
|
|
||||||
cx,
|
|
||||||
move |_, cx| {
|
|
||||||
let weak_self = weak_self.clone();
|
|
||||||
cx.window_context().defer(move |cx| {
|
|
||||||
if let Some(this) = weak_self.upgrade(cx) {
|
|
||||||
this.update(cx, |this, cx| this.add_context(cx));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
))
|
|
||||||
.with_child(Pane::render_tab_bar_button(
|
|
||||||
1,
|
|
||||||
if pane.is_zoomed() {
|
|
||||||
"icons/minimize_8.svg"
|
|
||||||
} else {
|
|
||||||
"icons/maximize_8.svg"
|
|
||||||
},
|
|
||||||
pane.is_zoomed(),
|
|
||||||
Some((
|
|
||||||
"Toggle Zoom".into(),
|
|
||||||
Some(Box::new(workspace::ToggleZoom)),
|
|
||||||
)),
|
|
||||||
cx,
|
|
||||||
move |pane, cx| pane.toggle_zoom(&Default::default(), cx),
|
|
||||||
None,
|
|
||||||
))
|
|
||||||
.into_any()
|
|
||||||
});
|
|
||||||
let buffer_search_bar = cx.add_view(search::BufferSearchBar::new);
|
|
||||||
pane.toolbar()
|
|
||||||
.update(cx, |toolbar, cx| toolbar.add_item(buffer_search_bar, cx));
|
|
||||||
pane
|
|
||||||
});
|
|
||||||
|
|
||||||
const CONVERSATION_WATCH_DURATION: Duration = Duration::from_millis(100);
|
const CONVERSATION_WATCH_DURATION: Duration = Duration::from_millis(100);
|
||||||
let _watch_saved_conversations = cx.spawn(move |this, mut cx| async move {
|
let _watch_saved_conversations = cx.spawn(move |this, mut cx| async move {
|
||||||
let mut events = fs
|
let mut events = fs
|
||||||
|
@ -200,7 +152,11 @@ impl AssistantPanel {
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
pane,
|
active_conversation_index: 0,
|
||||||
|
conversation_editors: Default::default(),
|
||||||
|
saved_conversations,
|
||||||
|
zoomed: false,
|
||||||
|
has_focus: false,
|
||||||
api_key: Rc::new(RefCell::new(None)),
|
api_key: Rc::new(RefCell::new(None)),
|
||||||
api_key_editor: None,
|
api_key_editor: None,
|
||||||
has_read_credentials: false,
|
has_read_credentials: false,
|
||||||
|
@ -209,22 +165,18 @@ impl AssistantPanel {
|
||||||
width: None,
|
width: None,
|
||||||
height: None,
|
height: None,
|
||||||
subscriptions: Default::default(),
|
subscriptions: Default::default(),
|
||||||
saved_conversations,
|
|
||||||
_watch_saved_conversations,
|
_watch_saved_conversations,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut old_dock_position = this.position(cx);
|
let mut old_dock_position = this.position(cx);
|
||||||
this.subscriptions = vec![
|
this.subscriptions =
|
||||||
cx.observe(&this.pane, |_, _, cx| cx.notify()),
|
vec![cx.observe_global::<SettingsStore, _>(move |this, cx| {
|
||||||
cx.subscribe(&this.pane, Self::handle_pane_event),
|
|
||||||
cx.observe_global::<SettingsStore, _>(move |this, cx| {
|
|
||||||
let new_dock_position = this.position(cx);
|
let new_dock_position = this.position(cx);
|
||||||
if new_dock_position != old_dock_position {
|
if new_dock_position != old_dock_position {
|
||||||
old_dock_position = new_dock_position;
|
old_dock_position = new_dock_position;
|
||||||
cx.emit(AssistantPanelEvent::DockPositionChanged);
|
cx.emit(AssistantPanelEvent::DockPositionChanged);
|
||||||
}
|
}
|
||||||
}),
|
})];
|
||||||
];
|
|
||||||
|
|
||||||
this
|
this
|
||||||
})
|
})
|
||||||
|
@ -232,25 +184,14 @@ impl AssistantPanel {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_pane_event(
|
fn add_conversation(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<ConversationEditor> {
|
||||||
&mut self,
|
|
||||||
_pane: ViewHandle<Pane>,
|
|
||||||
event: &pane::Event,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) {
|
|
||||||
match event {
|
|
||||||
pane::Event::ZoomIn => cx.emit(AssistantPanelEvent::ZoomIn),
|
|
||||||
pane::Event::ZoomOut => cx.emit(AssistantPanelEvent::ZoomOut),
|
|
||||||
pane::Event::Focus => cx.emit(AssistantPanelEvent::Focus),
|
|
||||||
pane::Event::Remove => cx.emit(AssistantPanelEvent::Close),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_context(&mut self, cx: &mut ViewContext<Self>) {
|
|
||||||
let focus = self.has_focus(cx);
|
let focus = self.has_focus(cx);
|
||||||
let editor = cx.add_view(|cx| {
|
let editor = cx.add_view(|cx| {
|
||||||
AssistantEditor::new(
|
if focus {
|
||||||
|
cx.focus_self();
|
||||||
|
}
|
||||||
|
|
||||||
|
ConversationEditor::new(
|
||||||
self.api_key.clone(),
|
self.api_key.clone(),
|
||||||
self.languages.clone(),
|
self.languages.clone(),
|
||||||
self.fs.clone(),
|
self.fs.clone(),
|
||||||
|
@ -258,20 +199,23 @@ impl AssistantPanel {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
self.subscriptions
|
self.subscriptions
|
||||||
.push(cx.subscribe(&editor, Self::handle_assistant_editor_event));
|
.push(cx.subscribe(&editor, Self::handle_conversation_editor_event));
|
||||||
self.pane.update(cx, |pane, cx| {
|
|
||||||
pane.add_item(Box::new(editor), true, focus, None, cx)
|
self.active_conversation_index = self.conversation_editors.len();
|
||||||
});
|
self.conversation_editors.push(editor.clone());
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
|
editor
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_assistant_editor_event(
|
fn handle_conversation_editor_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: ViewHandle<AssistantEditor>,
|
_: ViewHandle<ConversationEditor>,
|
||||||
event: &AssistantEditorEvent,
|
event: &AssistantEditorEvent,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
AssistantEditorEvent::TabContentChanged => self.pane.update(cx, |_, cx| cx.notify()),
|
AssistantEditorEvent::TabContentChanged => cx.notify(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,6 +246,19 @@ impl AssistantPanel {
|
||||||
cx.focus_self();
|
cx.focus_self();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn toggle_zoom(&mut self, _: &workspace::ToggleZoom, cx: &mut ViewContext<Self>) {
|
||||||
|
if self.zoomed {
|
||||||
|
cx.emit(AssistantPanelEvent::ZoomOut)
|
||||||
|
} else {
|
||||||
|
cx.emit(AssistantPanelEvent::ZoomIn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn active_conversation_editor(&self) -> Option<&ViewHandle<ConversationEditor>> {
|
||||||
|
self.conversation_editors
|
||||||
|
.get(self.active_conversation_index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_api_key_editor(cx: &mut ViewContext<AssistantPanel>) -> ViewHandle<Editor> {
|
fn build_api_key_editor(cx: &mut ViewContext<AssistantPanel>) -> ViewHandle<Editor> {
|
||||||
|
@ -345,20 +302,27 @@ impl View for AssistantPanel {
|
||||||
.with_style(style.api_key_prompt.container)
|
.with_style(style.api_key_prompt.container)
|
||||||
.aligned()
|
.aligned()
|
||||||
.into_any()
|
.into_any()
|
||||||
|
} else if let Some(editor) = self.active_conversation_editor() {
|
||||||
|
ChildView::new(editor, cx).into_any()
|
||||||
} else {
|
} else {
|
||||||
ChildView::new(&self.pane, cx).into_any()
|
Empty::new().into_any()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
|
fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||||
|
self.has_focus = true;
|
||||||
if cx.is_self_focused() {
|
if cx.is_self_focused() {
|
||||||
if let Some(api_key_editor) = self.api_key_editor.as_ref() {
|
if let Some(editor) = self.active_conversation_editor() {
|
||||||
|
cx.focus(editor);
|
||||||
|
} else if let Some(api_key_editor) = self.api_key_editor.as_ref() {
|
||||||
cx.focus(api_key_editor);
|
cx.focus(api_key_editor);
|
||||||
} else {
|
|
||||||
cx.focus(&self.pane);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
|
||||||
|
self.has_focus = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Panel for AssistantPanel {
|
impl Panel for AssistantPanel {
|
||||||
|
@ -411,12 +375,13 @@ impl Panel for AssistantPanel {
|
||||||
matches!(event, AssistantPanelEvent::ZoomOut)
|
matches!(event, AssistantPanelEvent::ZoomOut)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_zoomed(&self, cx: &WindowContext) -> bool {
|
fn is_zoomed(&self, _: &WindowContext) -> bool {
|
||||||
self.pane.read(cx).is_zoomed()
|
self.zoomed
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
|
fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
|
||||||
self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx));
|
self.zoomed = zoomed;
|
||||||
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
|
fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
|
||||||
|
@ -443,8 +408,8 @@ impl Panel for AssistantPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.pane.read(cx).items_len() == 0 {
|
if self.conversation_editors.is_empty() {
|
||||||
self.add_context(cx);
|
self.add_conversation(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -469,12 +434,8 @@ impl Panel for AssistantPanel {
|
||||||
matches!(event, AssistantPanelEvent::Close)
|
matches!(event, AssistantPanelEvent::Close)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_focus(&self, cx: &WindowContext) -> bool {
|
fn has_focus(&self, _: &WindowContext) -> bool {
|
||||||
self.pane.read(cx).has_focus()
|
self.has_focus
|
||||||
|| self
|
|
||||||
.api_key_editor
|
|
||||||
.as_ref()
|
|
||||||
.map_or(false, |editor| editor.is_focused(cx))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_focus_event(event: &Self::Event) -> bool {
|
fn is_focus_event(event: &Self::Event) -> bool {
|
||||||
|
@ -494,7 +455,7 @@ struct Summary {
|
||||||
done: bool,
|
done: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Assistant {
|
struct Conversation {
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
message_anchors: Vec<MessageAnchor>,
|
message_anchors: Vec<MessageAnchor>,
|
||||||
messages_metadata: HashMap<MessageId, MessageMetadata>,
|
messages_metadata: HashMap<MessageId, MessageMetadata>,
|
||||||
|
@ -513,11 +474,11 @@ struct Assistant {
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for Assistant {
|
impl Entity for Conversation {
|
||||||
type Event = AssistantEvent;
|
type Event = AssistantEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Assistant {
|
impl Conversation {
|
||||||
fn new(
|
fn new(
|
||||||
api_key: Rc<RefCell<Option<String>>>,
|
api_key: Rc<RefCell<Option<String>>>,
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
|
@ -1080,7 +1041,7 @@ impl Assistant {
|
||||||
&mut self,
|
&mut self,
|
||||||
debounce: Option<Duration>,
|
debounce: Option<Duration>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
cx: &mut ModelContext<Assistant>,
|
cx: &mut ModelContext<Conversation>,
|
||||||
) {
|
) {
|
||||||
self.pending_save = cx.spawn(|this, mut cx| async move {
|
self.pending_save = cx.spawn(|this, mut cx| async move {
|
||||||
if let Some(debounce) = debounce {
|
if let Some(debounce) = debounce {
|
||||||
|
@ -1158,8 +1119,8 @@ struct ScrollPosition {
|
||||||
cursor: Anchor,
|
cursor: Anchor,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AssistantEditor {
|
struct ConversationEditor {
|
||||||
assistant: ModelHandle<Assistant>,
|
assistant: ModelHandle<Conversation>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
editor: ViewHandle<Editor>,
|
editor: ViewHandle<Editor>,
|
||||||
blocks: HashSet<BlockId>,
|
blocks: HashSet<BlockId>,
|
||||||
|
@ -1167,14 +1128,14 @@ struct AssistantEditor {
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssistantEditor {
|
impl ConversationEditor {
|
||||||
fn new(
|
fn new(
|
||||||
api_key: Rc<RefCell<Option<String>>>,
|
api_key: Rc<RefCell<Option<String>>>,
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let assistant = cx.add_model(|cx| Assistant::new(api_key, language_registry, cx));
|
let assistant = cx.add_model(|cx| Conversation::new(api_key, language_registry, cx));
|
||||||
let editor = cx.add_view(|cx| {
|
let editor = cx.add_view(|cx| {
|
||||||
let mut editor = Editor::for_buffer(assistant.read(cx).buffer.clone(), None, cx);
|
let mut editor = Editor::for_buffer(assistant.read(cx).buffer.clone(), None, cx);
|
||||||
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
|
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
|
||||||
|
@ -1262,7 +1223,7 @@ impl AssistantEditor {
|
||||||
|
|
||||||
fn handle_assistant_event(
|
fn handle_assistant_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: ModelHandle<Assistant>,
|
_: ModelHandle<Conversation>,
|
||||||
event: &AssistantEvent,
|
event: &AssistantEvent,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
|
@ -1501,20 +1462,15 @@ impl AssistantEditor {
|
||||||
|
|
||||||
if let Some(text) = text {
|
if let Some(text) = text {
|
||||||
panel.update(cx, |panel, cx| {
|
panel.update(cx, |panel, cx| {
|
||||||
if let Some(assistant) = panel
|
let editor = panel
|
||||||
.pane
|
.active_conversation_editor()
|
||||||
.read(cx)
|
.cloned()
|
||||||
.active_item()
|
.unwrap_or_else(|| panel.add_conversation(cx));
|
||||||
.and_then(|item| item.downcast::<AssistantEditor>())
|
editor.update(cx, |assistant, cx| {
|
||||||
.ok_or_else(|| anyhow!("no active context"))
|
assistant
|
||||||
.log_err()
|
.editor
|
||||||
{
|
.update(cx, |editor, cx| editor.insert(&text, cx))
|
||||||
assistant.update(cx, |assistant, cx| {
|
});
|
||||||
assistant
|
|
||||||
.editor
|
|
||||||
.update(cx, |editor, cx| editor.insert(&text, cx))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1592,11 +1548,11 @@ impl AssistantEditor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for AssistantEditor {
|
impl Entity for ConversationEditor {
|
||||||
type Event = AssistantEditorEvent;
|
type Event = AssistantEditorEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View for AssistantEditor {
|
impl View for ConversationEditor {
|
||||||
fn ui_name() -> &'static str {
|
fn ui_name() -> &'static str {
|
||||||
"AssistantEditor"
|
"AssistantEditor"
|
||||||
}
|
}
|
||||||
|
@ -1655,7 +1611,7 @@ impl View for AssistantEditor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Item for AssistantEditor {
|
impl Item for ConversationEditor {
|
||||||
fn tab_content<V: View>(
|
fn tab_content<V: View>(
|
||||||
&self,
|
&self,
|
||||||
_: Option<usize>,
|
_: Option<usize>,
|
||||||
|
@ -1812,12 +1768,55 @@ async fn stream_completion(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use gpui::AppContext;
|
use fs::FakeFs;
|
||||||
|
use gpui::{AppContext, TestAppContext};
|
||||||
|
use project::Project;
|
||||||
|
|
||||||
|
fn init_test(cx: &mut TestAppContext) {
|
||||||
|
cx.foreground().forbid_parking();
|
||||||
|
cx.update(|cx| {
|
||||||
|
cx.set_global(SettingsStore::test(cx));
|
||||||
|
theme::init((), cx);
|
||||||
|
language::init(cx);
|
||||||
|
editor::init_settings(cx);
|
||||||
|
crate::init(cx);
|
||||||
|
workspace::init_settings(cx);
|
||||||
|
Project::init_settings(cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_panel(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx);
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.background());
|
||||||
|
let project = Project::test(fs, [], cx).await;
|
||||||
|
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||||
|
let weak_workspace = workspace.downgrade();
|
||||||
|
|
||||||
|
let panel = cx
|
||||||
|
.spawn(|cx| async move { AssistantPanel::load(weak_workspace, cx).await })
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
workspace.add_panel(panel.clone(), cx);
|
||||||
|
workspace.toggle_dock(DockPosition::Right, cx);
|
||||||
|
assert!(workspace.right_dock().read(cx).is_open());
|
||||||
|
cx.focus(&panel);
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.dispatch_action(window_id, workspace::ToggleZoom);
|
||||||
|
|
||||||
|
workspace.read_with(cx, |workspace, cx| {
|
||||||
|
assert_eq!(workspace.zoomed_view(cx).unwrap(), panel);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_inserting_and_removing_messages(cx: &mut AppContext) {
|
fn test_inserting_and_removing_messages(cx: &mut AppContext) {
|
||||||
let registry = Arc::new(LanguageRegistry::test());
|
let registry = Arc::new(LanguageRegistry::test());
|
||||||
let assistant = cx.add_model(|cx| Assistant::new(Default::default(), registry, cx));
|
let assistant = cx.add_model(|cx| Conversation::new(Default::default(), registry, cx));
|
||||||
let buffer = assistant.read(cx).buffer.clone();
|
let buffer = assistant.read(cx).buffer.clone();
|
||||||
|
|
||||||
let message_1 = assistant.read(cx).message_anchors[0].clone();
|
let message_1 = assistant.read(cx).message_anchors[0].clone();
|
||||||
|
@ -1943,7 +1942,7 @@ mod tests {
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_message_splitting(cx: &mut AppContext) {
|
fn test_message_splitting(cx: &mut AppContext) {
|
||||||
let registry = Arc::new(LanguageRegistry::test());
|
let registry = Arc::new(LanguageRegistry::test());
|
||||||
let assistant = cx.add_model(|cx| Assistant::new(Default::default(), registry, cx));
|
let assistant = cx.add_model(|cx| Conversation::new(Default::default(), registry, cx));
|
||||||
let buffer = assistant.read(cx).buffer.clone();
|
let buffer = assistant.read(cx).buffer.clone();
|
||||||
|
|
||||||
let message_1 = assistant.read(cx).message_anchors[0].clone();
|
let message_1 = assistant.read(cx).message_anchors[0].clone();
|
||||||
|
@ -2036,7 +2035,7 @@ mod tests {
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_messages_for_offsets(cx: &mut AppContext) {
|
fn test_messages_for_offsets(cx: &mut AppContext) {
|
||||||
let registry = Arc::new(LanguageRegistry::test());
|
let registry = Arc::new(LanguageRegistry::test());
|
||||||
let assistant = cx.add_model(|cx| Assistant::new(Default::default(), registry, cx));
|
let assistant = cx.add_model(|cx| Conversation::new(Default::default(), registry, cx));
|
||||||
let buffer = assistant.read(cx).buffer.clone();
|
let buffer = assistant.read(cx).buffer.clone();
|
||||||
|
|
||||||
let message_1 = assistant.read(cx).message_anchors[0].clone();
|
let message_1 = assistant.read(cx).message_anchors[0].clone();
|
||||||
|
@ -2080,7 +2079,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
fn message_ids_for_offsets(
|
fn message_ids_for_offsets(
|
||||||
assistant: &ModelHandle<Assistant>,
|
assistant: &ModelHandle<Conversation>,
|
||||||
offsets: &[usize],
|
offsets: &[usize],
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> Vec<MessageId> {
|
) -> Vec<MessageId> {
|
||||||
|
@ -2094,7 +2093,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn messages(
|
fn messages(
|
||||||
assistant: &ModelHandle<Assistant>,
|
assistant: &ModelHandle<Conversation>,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> Vec<(MessageId, Role, Range<usize>)> {
|
) -> Vec<(MessageId, Role, Range<usize>)> {
|
||||||
assistant
|
assistant
|
||||||
|
|
|
@ -153,6 +153,7 @@ pub fn init(cx: &mut AppContext) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
OpenedEntry {
|
OpenedEntry {
|
||||||
entry_id: ProjectEntryId,
|
entry_id: ProjectEntryId,
|
||||||
|
|
|
@ -25,6 +25,7 @@ pub fn init(cx: &mut AppContext) {
|
||||||
cx.add_action(TerminalPanel::new_terminal);
|
cx.add_action(TerminalPanel::new_terminal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
Close,
|
Close,
|
||||||
DockPositionChanged,
|
DockPositionChanged,
|
||||||
|
|
|
@ -249,7 +249,7 @@ impl Dock {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>) {
|
pub(crate) fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>) {
|
||||||
let subscriptions = [
|
let subscriptions = [
|
||||||
cx.observe(&panel, |_, _, cx| cx.notify()),
|
cx.observe(&panel, |_, _, cx| cx.notify()),
|
||||||
cx.subscribe(&panel, |this, panel, event, cx| {
|
cx.subscribe(&panel, |this, panel, event, cx| {
|
||||||
|
@ -603,6 +603,7 @@ pub mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use gpui::{ViewContext, WindowContext};
|
use gpui::{ViewContext, WindowContext};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum TestPanelEvent {
|
pub enum TestPanelEvent {
|
||||||
PositionChanged,
|
PositionChanged,
|
||||||
Activated,
|
Activated,
|
||||||
|
|
|
@ -859,7 +859,10 @@ impl Workspace {
|
||||||
&self.right_dock
|
&self.right_dock
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>) {
|
pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>)
|
||||||
|
where
|
||||||
|
T::Event: std::fmt::Debug,
|
||||||
|
{
|
||||||
let dock = match panel.position(cx) {
|
let dock = match panel.position(cx) {
|
||||||
DockPosition::Left => &self.left_dock,
|
DockPosition::Left => &self.left_dock,
|
||||||
DockPosition::Bottom => &self.bottom_dock,
|
DockPosition::Bottom => &self.bottom_dock,
|
||||||
|
@ -1700,6 +1703,11 @@ impl Workspace {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> {
|
||||||
|
self.zoomed.and_then(|view| view.upgrade(cx))
|
||||||
|
}
|
||||||
|
|
||||||
fn dismiss_zoomed_items_to_reveal(
|
fn dismiss_zoomed_items_to_reveal(
|
||||||
&mut self,
|
&mut self,
|
||||||
dock_to_reveal: Option<DockPosition>,
|
dock_to_reveal: Option<DockPosition>,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue