Eliminate GPUI View, ViewContext, and WindowContext types (#22632)
There's still a bit more work to do on this, but this PR is compiling (with warnings) after eliminating the key types. When the tasks below are complete, this will be the new narrative for GPUI: - `Entity<T>` - This replaces `View<T>`/`Model<T>`. It represents a unit of state, and if `T` implements `Render`, then `Entity<T>` implements `Element`. - `&mut App` This replaces `AppContext` and represents the app. - `&mut Context<T>` This replaces `ModelContext` and derefs to `App`. It is provided by the framework when updating an entity. - `&mut Window` Broken out of `&mut WindowContext` which no longer exists. Every method that once took `&mut WindowContext` now takes `&mut Window, &mut App` and every method that took `&mut ViewContext<T>` now takes `&mut Window, &mut Context<T>` Not pictured here are the two other failed attempts. It's been quite a month! Tasks: - [x] Remove `View`, `ViewContext`, `WindowContext` and thread through `Window` - [x] [@cole-miller @mikayla-maki] Redraw window when entities change - [x] [@cole-miller @mikayla-maki] Get examples and Zed running - [x] [@cole-miller @mikayla-maki] Fix Zed rendering - [x] [@mikayla-maki] Fix todo! macros and comments - [x] Fix a bug where the editor would not be redrawn because of view caching - [x] remove publicness window.notify() and replace with `AppContext::notify` - [x] remove `observe_new_window_models`, replace with `observe_new_models` with an optional window - [x] Fix a bug where the project panel would not be redrawn because of the wrong refresh() call being used - [x] Fix the tests - [x] Fix warnings by eliminating `Window` params or using `_` - [x] Fix conflicts - [x] Simplify generic code where possible - [x] Rename types - [ ] Update docs ### issues post merge - [x] Issues switching between normal and insert mode - [x] Assistant re-rendering failure - [x] Vim test failures - [x] Mac build issue Release Notes: - N/A --------- Co-authored-by: Antonio Scandurra <me@as-cii.com> Co-authored-by: Cole Miller <cole@zed.dev> Co-authored-by: Mikayla <mikayla@zed.dev> Co-authored-by: Joseph <joseph@zed.dev> Co-authored-by: max <max@zed.dev> Co-authored-by: Michael Sloan <michael@zed.dev> Co-authored-by: Mikayla Maki <mikaylamaki@Mikaylas-MacBook-Pro.local> Co-authored-by: Mikayla <mikayla.c.maki@gmail.com> Co-authored-by: joão <joao@zed.dev>
This commit is contained in:
parent
21b4a0d50e
commit
6fca1d2b0b
648 changed files with 36248 additions and 28208 deletions
|
@ -3,9 +3,9 @@ use std::sync::Arc;
|
|||
use assistant_tool::ToolWorkingSet;
|
||||
use collections::HashMap;
|
||||
use gpui::{
|
||||
list, AbsoluteLength, AnyElement, AppContext, DefiniteLength, EdgesRefinement, Empty, Length,
|
||||
ListAlignment, ListOffset, ListState, Model, StyleRefinement, Subscription,
|
||||
TextStyleRefinement, UnderlineStyle, View, WeakView,
|
||||
list, AbsoluteLength, AnyElement, App, DefiniteLength, EdgesRefinement, Empty, Entity, Length,
|
||||
ListAlignment, ListOffset, ListState, StyleRefinement, Subscription, TextStyleRefinement,
|
||||
UnderlineStyle, WeakEntity,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::Role;
|
||||
|
@ -20,30 +20,31 @@ use crate::thread_store::ThreadStore;
|
|||
use crate::ui::ContextPill;
|
||||
|
||||
pub struct ActiveThread {
|
||||
workspace: WeakView<Workspace>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
thread_store: Model<ThreadStore>,
|
||||
thread: Model<Thread>,
|
||||
thread_store: Entity<ThreadStore>,
|
||||
thread: Entity<Thread>,
|
||||
messages: Vec<MessageId>,
|
||||
list_state: ListState,
|
||||
rendered_messages_by_id: HashMap<MessageId, View<Markdown>>,
|
||||
rendered_messages_by_id: HashMap<MessageId, Entity<Markdown>>,
|
||||
last_error: Option<ThreadError>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
impl ActiveThread {
|
||||
pub fn new(
|
||||
thread: Model<Thread>,
|
||||
thread_store: Model<ThreadStore>,
|
||||
workspace: WeakView<Workspace>,
|
||||
thread: Entity<Thread>,
|
||||
thread_store: Entity<ThreadStore>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let subscriptions = vec![
|
||||
cx.observe(&thread, |_, _, cx| cx.notify()),
|
||||
cx.subscribe(&thread, Self::handle_thread_event),
|
||||
cx.subscribe_in(&thread, window, Self::handle_thread_event),
|
||||
];
|
||||
|
||||
let mut this = Self {
|
||||
|
@ -55,8 +56,8 @@ impl ActiveThread {
|
|||
messages: Vec::new(),
|
||||
rendered_messages_by_id: HashMap::default(),
|
||||
list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), {
|
||||
let this = cx.view().downgrade();
|
||||
move |ix, cx: &mut WindowContext| {
|
||||
let this = cx.model().downgrade();
|
||||
move |ix, _: &mut Window, cx: &mut App| {
|
||||
this.update(cx, |this, cx| this.render_message(ix, cx))
|
||||
.unwrap()
|
||||
}
|
||||
|
@ -66,13 +67,13 @@ impl ActiveThread {
|
|||
};
|
||||
|
||||
for message in thread.read(cx).messages().cloned().collect::<Vec<_>>() {
|
||||
this.push_message(&message.id, message.text.clone(), cx);
|
||||
this.push_message(&message.id, message.text.clone(), window, cx);
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
pub fn thread(&self) -> &Model<Thread> {
|
||||
pub fn thread(&self) -> &Entity<Thread> {
|
||||
&self.thread
|
||||
}
|
||||
|
||||
|
@ -80,15 +81,15 @@ impl ActiveThread {
|
|||
self.messages.is_empty()
|
||||
}
|
||||
|
||||
pub fn summary(&self, cx: &AppContext) -> Option<SharedString> {
|
||||
pub fn summary(&self, cx: &App) -> Option<SharedString> {
|
||||
self.thread.read(cx).summary()
|
||||
}
|
||||
|
||||
pub fn summary_or_default(&self, cx: &AppContext) -> SharedString {
|
||||
pub fn summary_or_default(&self, cx: &App) -> SharedString {
|
||||
self.thread.read(cx).summary_or_default()
|
||||
}
|
||||
|
||||
pub fn cancel_last_completion(&mut self, cx: &mut AppContext) -> bool {
|
||||
pub fn cancel_last_completion(&mut self, cx: &mut App) -> bool {
|
||||
self.last_error.take();
|
||||
self.thread
|
||||
.update(cx, |thread, _cx| thread.cancel_last_completion())
|
||||
|
@ -102,7 +103,13 @@ impl ActiveThread {
|
|||
self.last_error.take();
|
||||
}
|
||||
|
||||
fn push_message(&mut self, id: &MessageId, text: String, cx: &mut ViewContext<Self>) {
|
||||
fn push_message(
|
||||
&mut self,
|
||||
id: &MessageId,
|
||||
text: String,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let old_len = self.messages.len();
|
||||
self.messages.push(*id);
|
||||
self.list_state.splice(old_len..old_len, 1);
|
||||
|
@ -111,7 +118,7 @@ impl ActiveThread {
|
|||
let colors = cx.theme().colors();
|
||||
let ui_font_size = TextSize::Default.rems(cx);
|
||||
let buffer_font_size = TextSize::Small.rems(cx);
|
||||
let mut text_style = cx.text_style();
|
||||
let mut text_style = window.text_style();
|
||||
|
||||
text_style.refine(&TextStyleRefinement {
|
||||
font_family: Some(theme_settings.ui_font.family.clone()),
|
||||
|
@ -170,12 +177,13 @@ impl ActiveThread {
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
let markdown = cx.new_view(|cx| {
|
||||
let markdown = cx.new(|cx| {
|
||||
Markdown::new(
|
||||
text,
|
||||
markdown_style,
|
||||
Some(self.language_registry.clone()),
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
@ -188,9 +196,10 @@ impl ActiveThread {
|
|||
|
||||
fn handle_thread_event(
|
||||
&mut self,
|
||||
_: Model<Thread>,
|
||||
_: &Entity<Thread>,
|
||||
event: &ThreadEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match event {
|
||||
ThreadEvent::ShowError(error) => {
|
||||
|
@ -206,7 +215,7 @@ impl ActiveThread {
|
|||
ThreadEvent::StreamedAssistantText(message_id, text) => {
|
||||
if let Some(markdown) = self.rendered_messages_by_id.get_mut(&message_id) {
|
||||
markdown.update(cx, |markdown, cx| {
|
||||
markdown.append(text, cx);
|
||||
markdown.append(text, window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -217,7 +226,7 @@ impl ActiveThread {
|
|||
.message(*message_id)
|
||||
.map(|message| message.text.clone())
|
||||
{
|
||||
self.push_message(message_id, message_text, cx);
|
||||
self.push_message(message_id, message_text, window, cx);
|
||||
}
|
||||
|
||||
self.thread_store
|
||||
|
@ -240,7 +249,7 @@ impl ActiveThread {
|
|||
|
||||
for tool_use in pending_tool_uses {
|
||||
if let Some(tool) = self.tools.tool(&tool_use.name, cx) {
|
||||
let task = tool.run(tool_use.input, self.workspace.clone(), cx);
|
||||
let task = tool.run(tool_use.input, self.workspace.clone(), window, cx);
|
||||
|
||||
self.thread.update(cx, |thread, cx| {
|
||||
thread.insert_tool_output(
|
||||
|
@ -257,7 +266,7 @@ impl ActiveThread {
|
|||
}
|
||||
}
|
||||
|
||||
fn render_message(&self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement {
|
||||
fn render_message(&self, ix: usize, cx: &mut Context<Self>) -> AnyElement {
|
||||
let message_id = self.messages[ix];
|
||||
let Some(message) = self.thread.read(cx).message(message_id) else {
|
||||
return Empty.into_any();
|
||||
|
@ -338,7 +347,7 @@ impl ActiveThread {
|
|||
}
|
||||
|
||||
impl Render for ActiveThread {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.size_full()
|
||||
.pt_1p5()
|
||||
|
|
|
@ -24,7 +24,7 @@ use client::Client;
|
|||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
|
||||
use fs::Fs;
|
||||
use gpui::{actions, AppContext};
|
||||
use gpui::{actions, App};
|
||||
use prompt_library::PromptBuilder;
|
||||
use settings::Settings as _;
|
||||
|
||||
|
@ -63,7 +63,7 @@ pub fn init(
|
|||
fs: Arc<dyn Fs>,
|
||||
client: Arc<Client>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut App,
|
||||
) {
|
||||
AssistantSettings::register(cx);
|
||||
assistant_panel::init(cx);
|
||||
|
@ -84,7 +84,7 @@ pub fn init(
|
|||
feature_gate_assistant2_actions(cx);
|
||||
}
|
||||
|
||||
fn feature_gate_assistant2_actions(cx: &mut AppContext) {
|
||||
fn feature_gate_assistant2_actions(cx: &mut App) {
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
filter.hide_namespace(NAMESPACE);
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use collections::HashMap;
|
||||
use gpui::{Action, AnyView, AppContext, EventEmitter, FocusHandle, FocusableView, Subscription};
|
||||
use gpui::{AnyView, App, EventEmitter, FocusHandle, Focusable, Subscription};
|
||||
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
|
||||
use ui::{prelude::*, ElevationIndex};
|
||||
use zed_actions::assistant::DeployPromptLibrary;
|
||||
|
@ -13,16 +13,17 @@ pub struct AssistantConfiguration {
|
|||
}
|
||||
|
||||
impl AssistantConfiguration {
|
||||
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let focus_handle = cx.focus_handle();
|
||||
|
||||
let registry_subscription = cx.subscribe(
|
||||
let registry_subscription = cx.subscribe_in(
|
||||
&LanguageModelRegistry::global(cx),
|
||||
|this, _, event: &language_model::Event, cx| match event {
|
||||
window,
|
||||
|this, _, event: &language_model::Event, window, cx| match event {
|
||||
language_model::Event::AddedProvider(provider_id) => {
|
||||
let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
|
||||
if let Some(provider) = provider {
|
||||
this.add_provider_configuration_view(&provider, cx);
|
||||
this.add_provider_configuration_view(&provider, window, cx);
|
||||
}
|
||||
}
|
||||
language_model::Event::RemovedProvider(provider_id) => {
|
||||
|
@ -37,14 +38,14 @@ impl AssistantConfiguration {
|
|||
configuration_views_by_provider: HashMap::default(),
|
||||
_registry_subscription: registry_subscription,
|
||||
};
|
||||
this.build_provider_configuration_views(cx);
|
||||
this.build_provider_configuration_views(window, cx);
|
||||
this
|
||||
}
|
||||
|
||||
fn build_provider_configuration_views(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn build_provider_configuration_views(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let providers = LanguageModelRegistry::read_global(cx).providers();
|
||||
for provider in providers {
|
||||
self.add_provider_configuration_view(&provider, cx);
|
||||
self.add_provider_configuration_view(&provider, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,16 +56,17 @@ impl AssistantConfiguration {
|
|||
fn add_provider_configuration_view(
|
||||
&mut self,
|
||||
provider: &Arc<dyn LanguageModelProvider>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let configuration_view = provider.configuration_view(cx);
|
||||
let configuration_view = provider.configuration_view(window, cx);
|
||||
self.configuration_views_by_provider
|
||||
.insert(provider.id(), configuration_view);
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for AssistantConfiguration {
|
||||
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
|
||||
impl Focusable for AssistantConfiguration {
|
||||
fn focus_handle(&self, _: &App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +81,7 @@ impl AssistantConfiguration {
|
|||
fn render_provider_configuration(
|
||||
&mut self,
|
||||
provider: &Arc<dyn LanguageModelProvider>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let provider_id = provider.id().0.clone();
|
||||
let provider_name = provider.name().0.clone();
|
||||
|
@ -107,7 +109,7 @@ impl AssistantConfiguration {
|
|||
.layer(ElevationIndex::ModalSurface)
|
||||
.on_click(cx.listener({
|
||||
let provider = provider.clone();
|
||||
move |_this, _event, cx| {
|
||||
move |_this, _event, _window, cx| {
|
||||
cx.emit(AssistantConfigurationEvent::NewThread(
|
||||
provider.clone(),
|
||||
))
|
||||
|
@ -135,7 +137,7 @@ impl AssistantConfiguration {
|
|||
}
|
||||
|
||||
impl Render for AssistantConfiguration {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let providers = LanguageModelRegistry::read_global(cx).providers();
|
||||
|
||||
v_flex()
|
||||
|
@ -152,9 +154,7 @@ impl Render for AssistantConfiguration {
|
|||
.icon(IconName::Book)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_position(IconPosition::Start)
|
||||
.on_click(|_event, cx| {
|
||||
cx.dispatch_action(DeployPromptLibrary.boxed_clone())
|
||||
}),
|
||||
.on_click(|_event, _window, cx| cx.dispatch_action(&DeployPromptLibrary)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use assistant_settings::AssistantSettings;
|
||||
use fs::Fs;
|
||||
use gpui::{FocusHandle, View};
|
||||
use gpui::{Entity, FocusHandle};
|
||||
use language_model::LanguageModelRegistry;
|
||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||
use settings::update_settings_file;
|
||||
|
@ -10,7 +10,7 @@ use ui::{prelude::*, ButtonLike, PopoverMenuHandle, Tooltip};
|
|||
use crate::ToggleModelSelector;
|
||||
|
||||
pub struct AssistantModelSelector {
|
||||
selector: View<LanguageModelSelector>,
|
||||
selector: Entity<LanguageModelSelector>,
|
||||
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
@ -20,10 +20,11 @@ impl AssistantModelSelector {
|
|||
fs: Arc<dyn Fs>,
|
||||
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
focus_handle: FocusHandle,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
Self {
|
||||
selector: cx.new_view(|cx| {
|
||||
selector: cx.new(|cx| {
|
||||
let fs = fs.clone();
|
||||
LanguageModelSelector::new(
|
||||
move |model, cx| {
|
||||
|
@ -33,6 +34,7 @@ impl AssistantModelSelector {
|
|||
move |settings, _cx| settings.set_model(model.clone()),
|
||||
);
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
|
@ -43,7 +45,7 @@ impl AssistantModelSelector {
|
|||
}
|
||||
|
||||
impl Render for AssistantModelSelector {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let active_model = LanguageModelRegistry::read_global(cx).active_model();
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
|
||||
|
@ -79,8 +81,14 @@ impl Render for AssistantModelSelector {
|
|||
.size(IconSize::XSmall),
|
||||
),
|
||||
)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Change Model",
|
||||
&ToggleModelSelector,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
)
|
||||
.with_handle(self.menu_handle.clone())
|
||||
|
|
|
@ -14,9 +14,8 @@ use client::zed_urls;
|
|||
use editor::Editor;
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
prelude::*, px, svg, Action, AnyElement, AppContext, AsyncWindowContext, Corner, EventEmitter,
|
||||
FocusHandle, FocusableView, FontWeight, Model, Pixels, Subscription, Task, UpdateGlobal, View,
|
||||
ViewContext, WeakView, WindowContext,
|
||||
prelude::*, px, svg, Action, AnyElement, App, AsyncWindowContext, Corner, Entity, EventEmitter,
|
||||
FocusHandle, Focusable, FontWeight, Pixels, Subscription, Task, UpdateGlobal, WeakEntity,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::{LanguageModelProviderTosView, LanguageModelRegistry};
|
||||
|
@ -41,38 +40,38 @@ use crate::{
|
|||
OpenPromptEditorHistory,
|
||||
};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(
|
||||
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
|
||||
pub fn init(cx: &mut App) {
|
||||
cx.observe_new(
|
||||
|workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {
|
||||
workspace
|
||||
.register_action(|workspace, _: &NewThread, cx| {
|
||||
.register_action(|workspace, _: &NewThread, window, cx| {
|
||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||
panel.update(cx, |panel, cx| panel.new_thread(cx));
|
||||
workspace.focus_panel::<AssistantPanel>(cx);
|
||||
panel.update(cx, |panel, cx| panel.new_thread(window, cx));
|
||||
workspace.focus_panel::<AssistantPanel>(window, cx);
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &OpenHistory, cx| {
|
||||
.register_action(|workspace, _: &OpenHistory, window, cx| {
|
||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||
workspace.focus_panel::<AssistantPanel>(cx);
|
||||
panel.update(cx, |panel, cx| panel.open_history(cx));
|
||||
workspace.focus_panel::<AssistantPanel>(window, cx);
|
||||
panel.update(cx, |panel, cx| panel.open_history(window, cx));
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &NewPromptEditor, cx| {
|
||||
.register_action(|workspace, _: &NewPromptEditor, window, cx| {
|
||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||
workspace.focus_panel::<AssistantPanel>(cx);
|
||||
panel.update(cx, |panel, cx| panel.new_prompt_editor(cx));
|
||||
workspace.focus_panel::<AssistantPanel>(window, cx);
|
||||
panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx));
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &OpenPromptEditorHistory, cx| {
|
||||
.register_action(|workspace, _: &OpenPromptEditorHistory, window, cx| {
|
||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||
workspace.focus_panel::<AssistantPanel>(cx);
|
||||
panel.update(cx, |panel, cx| panel.open_prompt_editor_history(cx));
|
||||
workspace.focus_panel::<AssistantPanel>(window, cx);
|
||||
panel.update(cx, |panel, cx| panel.open_prompt_editor_history(window, cx));
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &OpenConfiguration, cx| {
|
||||
.register_action(|workspace, _: &OpenConfiguration, window, cx| {
|
||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||
workspace.focus_panel::<AssistantPanel>(cx);
|
||||
panel.update(cx, |panel, cx| panel.open_configuration(cx));
|
||||
workspace.focus_panel::<AssistantPanel>(window, cx);
|
||||
panel.update(cx, |panel, cx| panel.open_configuration(window, cx));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -89,22 +88,22 @@ enum ActiveView {
|
|||
}
|
||||
|
||||
pub struct AssistantPanel {
|
||||
workspace: WeakView<Workspace>,
|
||||
project: Model<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
project: Entity<Project>,
|
||||
fs: Arc<dyn Fs>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
thread_store: Model<ThreadStore>,
|
||||
thread: View<ActiveThread>,
|
||||
message_editor: View<MessageEditor>,
|
||||
context_store: Model<assistant_context_editor::ContextStore>,
|
||||
context_editor: Option<View<ContextEditor>>,
|
||||
context_history: Option<View<ContextHistory>>,
|
||||
configuration: Option<View<AssistantConfiguration>>,
|
||||
thread_store: Entity<ThreadStore>,
|
||||
thread: Entity<ActiveThread>,
|
||||
message_editor: Entity<MessageEditor>,
|
||||
context_store: Entity<assistant_context_editor::ContextStore>,
|
||||
context_editor: Option<Entity<ContextEditor>>,
|
||||
context_history: Option<Entity<ContextHistory>>,
|
||||
configuration: Option<Entity<AssistantConfiguration>>,
|
||||
configuration_subscription: Option<Subscription>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
local_timezone: UtcOffset,
|
||||
active_view: ActiveView,
|
||||
history: View<ThreadHistory>,
|
||||
history: Entity<ThreadHistory>,
|
||||
new_item_context_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||
open_history_context_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||
width: Option<Pixels>,
|
||||
|
@ -113,10 +112,10 @@ pub struct AssistantPanel {
|
|||
|
||||
impl AssistantPanel {
|
||||
pub fn load(
|
||||
workspace: WeakView<Workspace>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
cx: AsyncWindowContext,
|
||||
) -> Task<Result<View<Self>>> {
|
||||
) -> Task<Result<Entity<Self>>> {
|
||||
cx.spawn(|mut cx| async move {
|
||||
let tools = Arc::new(ToolWorkingSet::default());
|
||||
let thread_store = workspace
|
||||
|
@ -140,32 +139,34 @@ impl AssistantPanel {
|
|||
})?
|
||||
.await?;
|
||||
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
cx.new_view(|cx| Self::new(workspace, thread_store, context_store, tools, cx))
|
||||
workspace.update_in(&mut cx, |workspace, window, cx| {
|
||||
cx.new(|cx| Self::new(workspace, thread_store, context_store, tools, window, cx))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn new(
|
||||
workspace: &Workspace,
|
||||
thread_store: Model<ThreadStore>,
|
||||
context_store: Model<assistant_context_editor::ContextStore>,
|
||||
thread_store: Entity<ThreadStore>,
|
||||
context_store: Entity<assistant_context_editor::ContextStore>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
let project = workspace.project().clone();
|
||||
let language_registry = project.read(cx).languages().clone();
|
||||
let workspace = workspace.weak_handle();
|
||||
let weak_self = cx.view().downgrade();
|
||||
let weak_self = cx.model().downgrade();
|
||||
|
||||
let message_editor = cx.new_view(|cx| {
|
||||
let message_editor = cx.new(|cx| {
|
||||
MessageEditor::new(
|
||||
fs.clone(),
|
||||
workspace.clone(),
|
||||
thread_store.downgrade(),
|
||||
thread.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
@ -177,13 +178,14 @@ impl AssistantPanel {
|
|||
fs: fs.clone(),
|
||||
language_registry: language_registry.clone(),
|
||||
thread_store: thread_store.clone(),
|
||||
thread: cx.new_view(|cx| {
|
||||
thread: cx.new(|cx| {
|
||||
ActiveThread::new(
|
||||
thread.clone(),
|
||||
thread_store.clone(),
|
||||
workspace,
|
||||
language_registry,
|
||||
tools.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
|
@ -198,7 +200,7 @@ impl AssistantPanel {
|
|||
chrono::Local::now().offset().local_minus_utc(),
|
||||
)
|
||||
.unwrap(),
|
||||
history: cx.new_view(|cx| ThreadHistory::new(weak_self, thread_store, cx)),
|
||||
history: cx.new(|cx| ThreadHistory::new(weak_self, thread_store, cx)),
|
||||
new_item_context_menu_handle: PopoverMenuHandle::default(),
|
||||
open_history_context_menu_handle: PopoverMenuHandle::default(),
|
||||
width: None,
|
||||
|
@ -209,58 +211,66 @@ impl AssistantPanel {
|
|||
pub fn toggle_focus(
|
||||
workspace: &mut Workspace,
|
||||
_: &ToggleFocus,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
if !settings.enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
workspace.toggle_panel_focus::<Self>(cx);
|
||||
workspace.toggle_panel_focus::<Self>(window, cx);
|
||||
}
|
||||
|
||||
pub(crate) fn local_timezone(&self) -> UtcOffset {
|
||||
self.local_timezone
|
||||
}
|
||||
|
||||
pub(crate) fn thread_store(&self) -> &Model<ThreadStore> {
|
||||
pub(crate) fn thread_store(&self) -> &Entity<ThreadStore> {
|
||||
&self.thread_store
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
|
||||
fn cancel(
|
||||
&mut self,
|
||||
_: &editor::actions::Cancel,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.thread
|
||||
.update(cx, |thread, cx| thread.cancel_last_completion(cx));
|
||||
}
|
||||
|
||||
fn new_thread(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn new_thread(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let thread = self
|
||||
.thread_store
|
||||
.update(cx, |this, cx| this.create_thread(cx));
|
||||
|
||||
self.active_view = ActiveView::Thread;
|
||||
self.thread = cx.new_view(|cx| {
|
||||
self.thread = cx.new(|cx| {
|
||||
ActiveThread::new(
|
||||
thread.clone(),
|
||||
self.thread_store.clone(),
|
||||
self.workspace.clone(),
|
||||
self.language_registry.clone(),
|
||||
self.tools.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
self.message_editor = cx.new_view(|cx| {
|
||||
self.message_editor = cx.new(|cx| {
|
||||
MessageEditor::new(
|
||||
self.fs.clone(),
|
||||
self.workspace.clone(),
|
||||
self.thread_store.downgrade(),
|
||||
thread,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
self.message_editor.focus_handle(cx).focus(cx);
|
||||
self.message_editor.focus_handle(cx).focus(window);
|
||||
}
|
||||
|
||||
fn new_prompt_editor(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn new_prompt_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.active_view = ActiveView::PromptEditor;
|
||||
|
||||
let context = self
|
||||
|
@ -270,25 +280,31 @@ impl AssistantPanel {
|
|||
.log_err()
|
||||
.flatten();
|
||||
|
||||
self.context_editor = Some(cx.new_view(|cx| {
|
||||
self.context_editor = Some(cx.new(|cx| {
|
||||
let mut editor = ContextEditor::for_context(
|
||||
context,
|
||||
self.fs.clone(),
|
||||
self.workspace.clone(),
|
||||
self.project.clone(),
|
||||
lsp_adapter_delegate,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
editor.insert_default_prompt(cx);
|
||||
editor.insert_default_prompt(window, cx);
|
||||
editor
|
||||
}));
|
||||
|
||||
if let Some(context_editor) = self.context_editor.as_ref() {
|
||||
context_editor.focus_handle(cx).focus(cx);
|
||||
context_editor.focus_handle(cx).focus(window);
|
||||
}
|
||||
}
|
||||
|
||||
fn deploy_prompt_library(&mut self, _: &DeployPromptLibrary, cx: &mut ViewContext<Self>) {
|
||||
fn deploy_prompt_library(
|
||||
&mut self,
|
||||
_: &DeployPromptLibrary,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
open_prompt_library(
|
||||
self.language_registry.clone(),
|
||||
Box::new(PromptLibraryInlineAssist::new(self.workspace.clone())),
|
||||
|
@ -304,25 +320,26 @@ impl AssistantPanel {
|
|||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
fn open_history(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn open_history(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.active_view = ActiveView::History;
|
||||
self.history.focus_handle(cx).focus(cx);
|
||||
self.history.focus_handle(cx).focus(window);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn open_prompt_editor_history(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn open_prompt_editor_history(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.active_view = ActiveView::PromptEditorHistory;
|
||||
self.context_history = Some(cx.new_view(|cx| {
|
||||
self.context_history = Some(cx.new(|cx| {
|
||||
ContextHistory::new(
|
||||
self.project.clone(),
|
||||
self.context_store.clone(),
|
||||
self.workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
|
||||
if let Some(context_history) = self.context_history.as_ref() {
|
||||
context_history.focus_handle(cx).focus(cx);
|
||||
context_history.focus_handle(cx).focus(window);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
|
@ -331,7 +348,8 @@ impl AssistantPanel {
|
|||
fn open_saved_prompt_editor(
|
||||
&mut self,
|
||||
path: PathBuf,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let context = self
|
||||
.context_store
|
||||
|
@ -342,16 +360,17 @@ impl AssistantPanel {
|
|||
|
||||
let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err().flatten();
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
let context = context.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let editor = cx.new_view(|cx| {
|
||||
this.update_in(&mut cx, |this, window, cx| {
|
||||
let editor = cx.new(|cx| {
|
||||
ContextEditor::for_context(
|
||||
context,
|
||||
fs,
|
||||
workspace,
|
||||
project,
|
||||
lsp_adapter_delegate,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
@ -367,57 +386,64 @@ impl AssistantPanel {
|
|||
pub(crate) fn open_thread(
|
||||
&mut self,
|
||||
thread_id: &ThreadId,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let open_thread_task = self
|
||||
.thread_store
|
||||
.update(cx, |this, cx| this.open_thread(thread_id, cx));
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
let thread = open_thread_task.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.update_in(&mut cx, |this, window, cx| {
|
||||
this.active_view = ActiveView::Thread;
|
||||
this.thread = cx.new_view(|cx| {
|
||||
this.thread = cx.new(|cx| {
|
||||
ActiveThread::new(
|
||||
thread.clone(),
|
||||
this.thread_store.clone(),
|
||||
this.workspace.clone(),
|
||||
this.language_registry.clone(),
|
||||
this.tools.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
this.message_editor = cx.new_view(|cx| {
|
||||
this.message_editor = cx.new(|cx| {
|
||||
MessageEditor::new(
|
||||
this.fs.clone(),
|
||||
this.workspace.clone(),
|
||||
this.thread_store.downgrade(),
|
||||
thread,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
this.message_editor.focus_handle(cx).focus(cx);
|
||||
this.message_editor.focus_handle(cx).focus(window);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn open_configuration(&mut self, cx: &mut ViewContext<Self>) {
|
||||
pub(crate) fn open_configuration(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.active_view = ActiveView::Configuration;
|
||||
self.configuration = Some(cx.new_view(AssistantConfiguration::new));
|
||||
self.configuration = Some(cx.new(|cx| AssistantConfiguration::new(window, cx)));
|
||||
|
||||
if let Some(configuration) = self.configuration.as_ref() {
|
||||
self.configuration_subscription =
|
||||
Some(cx.subscribe(configuration, Self::handle_assistant_configuration_event));
|
||||
self.configuration_subscription = Some(cx.subscribe_in(
|
||||
configuration,
|
||||
window,
|
||||
Self::handle_assistant_configuration_event,
|
||||
));
|
||||
|
||||
configuration.focus_handle(cx).focus(cx);
|
||||
configuration.focus_handle(cx).focus(window);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_assistant_configuration_event(
|
||||
&mut self,
|
||||
_view: View<AssistantConfiguration>,
|
||||
_model: &Entity<AssistantConfiguration>,
|
||||
event: &AssistantConfigurationEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match event {
|
||||
AssistantConfigurationEvent::NewThread(provider) => {
|
||||
|
@ -436,24 +462,24 @@ impl AssistantPanel {
|
|||
}
|
||||
}
|
||||
|
||||
self.new_thread(cx);
|
||||
self.new_thread(window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn active_thread(&self, cx: &AppContext) -> Model<Thread> {
|
||||
pub(crate) fn active_thread(&self, cx: &App) -> Entity<Thread> {
|
||||
self.thread.read(cx).thread().clone()
|
||||
}
|
||||
|
||||
pub(crate) fn delete_thread(&mut self, thread_id: &ThreadId, cx: &mut ViewContext<Self>) {
|
||||
pub(crate) fn delete_thread(&mut self, thread_id: &ThreadId, cx: &mut Context<Self>) {
|
||||
self.thread_store
|
||||
.update(cx, |this, cx| this.delete_thread(thread_id, cx))
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for AssistantPanel {
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
impl Focusable for AssistantPanel {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
match self.active_view {
|
||||
ActiveView::Thread => self.message_editor.focus_handle(cx),
|
||||
ActiveView::History => self.history.focus_handle(cx),
|
||||
|
@ -489,7 +515,7 @@ impl Panel for AssistantPanel {
|
|||
"AssistantPanel2"
|
||||
}
|
||||
|
||||
fn position(&self, cx: &WindowContext) -> DockPosition {
|
||||
fn position(&self, _window: &Window, cx: &App) -> DockPosition {
|
||||
match AssistantSettings::get_global(cx).dock {
|
||||
AssistantDockPosition::Left => DockPosition::Left,
|
||||
AssistantDockPosition::Bottom => DockPosition::Bottom,
|
||||
|
@ -501,7 +527,7 @@ impl Panel for AssistantPanel {
|
|||
true
|
||||
}
|
||||
|
||||
fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
|
||||
fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
|
||||
settings::update_settings_file::<AssistantSettings>(
|
||||
self.fs.clone(),
|
||||
cx,
|
||||
|
@ -516,9 +542,9 @@ impl Panel for AssistantPanel {
|
|||
);
|
||||
}
|
||||
|
||||
fn size(&self, cx: &WindowContext) -> Pixels {
|
||||
fn size(&self, window: &Window, cx: &App) -> Pixels {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
match self.position(cx) {
|
||||
match self.position(window, cx) {
|
||||
DockPosition::Left | DockPosition::Right => {
|
||||
self.width.unwrap_or(settings.default_width)
|
||||
}
|
||||
|
@ -526,21 +552,21 @@ impl Panel for AssistantPanel {
|
|||
}
|
||||
}
|
||||
|
||||
fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
|
||||
match self.position(cx) {
|
||||
fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
|
||||
match self.position(window, cx) {
|
||||
DockPosition::Left | DockPosition::Right => self.width = size,
|
||||
DockPosition::Bottom => self.height = size,
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn set_active(&mut self, _active: bool, _cx: &mut ViewContext<Self>) {}
|
||||
fn set_active(&mut self, _active: bool, _window: &mut Window, _cx: &mut Context<Self>) {}
|
||||
|
||||
fn remote_id() -> Option<proto::PanelId> {
|
||||
Some(proto::PanelId::AssistantPanel)
|
||||
}
|
||||
|
||||
fn icon(&self, cx: &WindowContext) -> Option<IconName> {
|
||||
fn icon(&self, _window: &Window, cx: &App) -> Option<IconName> {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
if !settings.enabled || !settings.button {
|
||||
return None;
|
||||
|
@ -549,7 +575,7 @@ impl Panel for AssistantPanel {
|
|||
Some(IconName::ZedAssistant)
|
||||
}
|
||||
|
||||
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
|
||||
fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
|
||||
Some("Assistant Panel")
|
||||
}
|
||||
|
||||
|
@ -563,7 +589,7 @@ impl Panel for AssistantPanel {
|
|||
}
|
||||
|
||||
impl AssistantPanel {
|
||||
fn render_toolbar(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render_toolbar(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let thread = self.thread.read(cx);
|
||||
|
||||
let title = match self.active_view {
|
||||
|
@ -612,12 +638,12 @@ impl AssistantPanel {
|
|||
IconButton::new("new", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(|cx| Tooltip::text("New…", cx)),
|
||||
.tooltip(Tooltip::text("New…")),
|
||||
)
|
||||
.anchor(Corner::TopRight)
|
||||
.with_handle(self.new_item_context_menu_handle.clone())
|
||||
.menu(move |cx| {
|
||||
Some(ContextMenu::build(cx, |menu, _| {
|
||||
.menu(move |window, cx| {
|
||||
Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
|
||||
menu.action("New Thread", NewThread.boxed_clone())
|
||||
.action("New Prompt Editor", NewPromptEditor.boxed_clone())
|
||||
}))
|
||||
|
@ -629,12 +655,12 @@ impl AssistantPanel {
|
|||
IconButton::new("open-history", IconName::HistoryRerun)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(|cx| Tooltip::text("History…", cx)),
|
||||
.tooltip(Tooltip::text("History…")),
|
||||
)
|
||||
.anchor(Corner::TopRight)
|
||||
.with_handle(self.open_history_context_menu_handle.clone())
|
||||
.menu(move |cx| {
|
||||
Some(ContextMenu::build(cx, |menu, _| {
|
||||
.menu(move |window, cx| {
|
||||
Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
|
||||
menu.action("Thread History", OpenHistory.boxed_clone())
|
||||
.action(
|
||||
"Prompt Editor History",
|
||||
|
@ -647,23 +673,29 @@ impl AssistantPanel {
|
|||
IconButton::new("configure-assistant", IconName::Settings)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(move |cx| Tooltip::text("Configure Assistant", cx))
|
||||
.on_click(move |_event, cx| {
|
||||
cx.dispatch_action(OpenConfiguration.boxed_clone());
|
||||
.tooltip(Tooltip::text("Configure Assistant"))
|
||||
.on_click(move |_event, _window, cx| {
|
||||
cx.dispatch_action(&OpenConfiguration);
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_active_thread_or_empty_state(&self, cx: &mut ViewContext<Self>) -> AnyElement {
|
||||
fn render_active_thread_or_empty_state(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> AnyElement {
|
||||
if self.thread.read(cx).is_empty() {
|
||||
return self.render_thread_empty_state(cx).into_any_element();
|
||||
return self
|
||||
.render_thread_empty_state(window, cx)
|
||||
.into_any_element();
|
||||
}
|
||||
|
||||
self.thread.clone().into_any()
|
||||
self.thread.clone().into_any_element()
|
||||
}
|
||||
|
||||
fn configuration_error(&self, cx: &AppContext) -> Option<ConfigurationError> {
|
||||
fn configuration_error(&self, cx: &App) -> Option<ConfigurationError> {
|
||||
let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
|
||||
return Some(ConfigurationError::NoProvider);
|
||||
};
|
||||
|
@ -679,7 +711,11 @@ impl AssistantPanel {
|
|||
None
|
||||
}
|
||||
|
||||
fn render_thread_empty_state(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render_thread_empty_state(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let recent_threads = self
|
||||
.thread_store
|
||||
.update(cx, |this, _cx| this.recent_threads(3));
|
||||
|
@ -729,8 +765,8 @@ impl AssistantPanel {
|
|||
.icon(Some(IconName::Sliders))
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_position(IconPosition::Start)
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
this.open_configuration(cx);
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.open_configuration(window, cx);
|
||||
})),
|
||||
),
|
||||
),
|
||||
|
@ -775,7 +811,7 @@ impl AssistantPanel {
|
|||
.child(v_flex().mx_auto().w_4_5().gap_2().children(
|
||||
recent_threads.into_iter().map(|thread| {
|
||||
// TODO: keyboard navigation
|
||||
PastThread::new(thread, cx.view().downgrade(), false)
|
||||
PastThread::new(thread, cx.model().downgrade(), false)
|
||||
}),
|
||||
))
|
||||
.child(
|
||||
|
@ -786,17 +822,17 @@ impl AssistantPanel {
|
|||
.key_binding(KeyBinding::for_action_in(
|
||||
&OpenHistory,
|
||||
&self.focus_handle(cx),
|
||||
cx,
|
||||
window,
|
||||
))
|
||||
.on_click(move |_event, cx| {
|
||||
cx.dispatch_action(OpenHistory.boxed_clone());
|
||||
.on_click(move |_event, window, cx| {
|
||||
window.dispatch_action(OpenHistory.boxed_clone(), cx);
|
||||
}),
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn render_last_error(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
|
||||
fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||
let last_error = self.thread.read(cx).last_error()?;
|
||||
|
||||
Some(
|
||||
|
@ -822,7 +858,7 @@ impl AssistantPanel {
|
|||
)
|
||||
}
|
||||
|
||||
fn render_payment_required_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
|
||||
fn render_payment_required_error(&self, cx: &mut Context<Self>) -> AnyElement {
|
||||
const ERROR_MESSAGE: &str = "Free tier exceeded. Subscribe and add payment to continue using Zed LLMs. You'll be billed at cost for tokens used.";
|
||||
|
||||
v_flex()
|
||||
|
@ -846,7 +882,7 @@ impl AssistantPanel {
|
|||
.justify_end()
|
||||
.mt_1()
|
||||
.child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
|
||||
|this, _, cx| {
|
||||
|this, _, _, cx| {
|
||||
this.thread.update(cx, |this, _cx| {
|
||||
this.clear_last_error();
|
||||
});
|
||||
|
@ -856,7 +892,7 @@ impl AssistantPanel {
|
|||
},
|
||||
)))
|
||||
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|
||||
|this, _, cx| {
|
||||
|this, _, _, cx| {
|
||||
this.thread.update(cx, |this, _cx| {
|
||||
this.clear_last_error();
|
||||
});
|
||||
|
@ -868,7 +904,7 @@ impl AssistantPanel {
|
|||
.into_any()
|
||||
}
|
||||
|
||||
fn render_max_monthly_spend_reached_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
|
||||
fn render_max_monthly_spend_reached_error(&self, cx: &mut Context<Self>) -> AnyElement {
|
||||
const ERROR_MESSAGE: &str = "You have reached your maximum monthly spend. Increase your spend limit to continue using Zed LLMs.";
|
||||
|
||||
v_flex()
|
||||
|
@ -893,7 +929,7 @@ impl AssistantPanel {
|
|||
.mt_1()
|
||||
.child(
|
||||
Button::new("subscribe", "Update Monthly Spend Limit").on_click(
|
||||
cx.listener(|this, _, cx| {
|
||||
cx.listener(|this, _, _, cx| {
|
||||
this.thread.update(cx, |this, _cx| {
|
||||
this.clear_last_error();
|
||||
});
|
||||
|
@ -904,7 +940,7 @@ impl AssistantPanel {
|
|||
),
|
||||
)
|
||||
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|
||||
|this, _, cx| {
|
||||
|this, _, _, cx| {
|
||||
this.thread.update(cx, |this, _cx| {
|
||||
this.clear_last_error();
|
||||
});
|
||||
|
@ -919,7 +955,7 @@ impl AssistantPanel {
|
|||
fn render_error_message(
|
||||
&self,
|
||||
error_message: &SharedString,
|
||||
cx: &mut ViewContext<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> AnyElement {
|
||||
v_flex()
|
||||
.gap_0p5()
|
||||
|
@ -945,7 +981,7 @@ impl AssistantPanel {
|
|||
.justify_end()
|
||||
.mt_1()
|
||||
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|
||||
|this, _, cx| {
|
||||
|this, _, _, cx| {
|
||||
this.thread.update(cx, |this, _cx| {
|
||||
this.clear_last_error();
|
||||
});
|
||||
|
@ -959,23 +995,23 @@ impl AssistantPanel {
|
|||
}
|
||||
|
||||
impl Render for AssistantPanel {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.key_context("AssistantPanel2")
|
||||
.justify_between()
|
||||
.size_full()
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.on_action(cx.listener(|this, _: &NewThread, cx| {
|
||||
this.new_thread(cx);
|
||||
.on_action(cx.listener(|this, _: &NewThread, window, cx| {
|
||||
this.new_thread(window, cx);
|
||||
}))
|
||||
.on_action(cx.listener(|this, _: &OpenHistory, cx| {
|
||||
this.open_history(cx);
|
||||
.on_action(cx.listener(|this, _: &OpenHistory, window, cx| {
|
||||
this.open_history(window, cx);
|
||||
}))
|
||||
.on_action(cx.listener(Self::deploy_prompt_library))
|
||||
.child(self.render_toolbar(cx))
|
||||
.map(|parent| match self.active_view {
|
||||
ActiveView::Thread => parent
|
||||
.child(self.render_active_thread_or_empty_state(cx))
|
||||
.child(self.render_active_thread_or_empty_state(window, cx))
|
||||
.child(
|
||||
h_flex()
|
||||
.border_t_1()
|
||||
|
@ -992,11 +1028,11 @@ impl Render for AssistantPanel {
|
|||
}
|
||||
|
||||
struct PromptLibraryInlineAssist {
|
||||
workspace: WeakView<Workspace>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
}
|
||||
|
||||
impl PromptLibraryInlineAssist {
|
||||
pub fn new(workspace: WeakView<Workspace>) -> Self {
|
||||
pub fn new(workspace: WeakEntity<Workspace>) -> Self {
|
||||
Self { workspace }
|
||||
}
|
||||
}
|
||||
|
@ -1004,21 +1040,25 @@ impl PromptLibraryInlineAssist {
|
|||
impl prompt_library::InlineAssistDelegate for PromptLibraryInlineAssist {
|
||||
fn assist(
|
||||
&self,
|
||||
prompt_editor: &View<Editor>,
|
||||
prompt_editor: &Entity<Editor>,
|
||||
_initial_prompt: Option<String>,
|
||||
cx: &mut ViewContext<PromptLibrary>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<PromptLibrary>,
|
||||
) {
|
||||
InlineAssistant::update_global(cx, |assistant, cx| {
|
||||
assistant.assist(&prompt_editor, self.workspace.clone(), None, cx)
|
||||
assistant.assist(&prompt_editor, self.workspace.clone(), None, window, cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn focus_assistant_panel(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> bool {
|
||||
workspace.focus_panel::<AssistantPanel>(cx).is_some()
|
||||
workspace
|
||||
.focus_panel::<AssistantPanel>(window, cx)
|
||||
.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1028,8 +1068,9 @@ impl AssistantPanelDelegate for ConcreteAssistantPanelDelegate {
|
|||
fn active_context_editor(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Option<View<ContextEditor>> {
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Option<Entity<ContextEditor>> {
|
||||
let panel = workspace.panel::<AssistantPanel>(cx)?;
|
||||
panel.update(cx, |panel, _cx| panel.context_editor.clone())
|
||||
}
|
||||
|
@ -1038,21 +1079,25 @@ impl AssistantPanelDelegate for ConcreteAssistantPanelDelegate {
|
|||
&self,
|
||||
workspace: &mut Workspace,
|
||||
path: std::path::PathBuf,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Task<Result<()>> {
|
||||
let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
|
||||
return Task::ready(Err(anyhow!("Assistant panel not found")));
|
||||
};
|
||||
|
||||
panel.update(cx, |panel, cx| panel.open_saved_prompt_editor(path, cx))
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.open_saved_prompt_editor(path, window, cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn open_remote_context(
|
||||
&self,
|
||||
_workspace: &mut Workspace,
|
||||
_context_id: assistant_context_editor::ContextId,
|
||||
_cx: &mut ViewContext<Workspace>,
|
||||
) -> Task<Result<View<ContextEditor>>> {
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Workspace>,
|
||||
) -> Task<Result<Entity<ContextEditor>>> {
|
||||
Task::ready(Err(anyhow!("opening remote context not implemented")))
|
||||
}
|
||||
|
||||
|
@ -1060,7 +1105,8 @@ impl AssistantPanelDelegate for ConcreteAssistantPanelDelegate {
|
|||
&self,
|
||||
_workspace: &mut Workspace,
|
||||
_creases: Vec<(String, String)>,
|
||||
_cx: &mut ViewContext<Workspace>,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Workspace>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use client::telemetry::Telemetry;
|
|||
use collections::HashSet;
|
||||
use editor::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint};
|
||||
use futures::{channel::mpsc, future::LocalBoxFuture, join, SinkExt, Stream, StreamExt};
|
||||
use gpui::{AppContext, Context as _, EventEmitter, Model, ModelContext, Subscription, Task};
|
||||
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription, Task};
|
||||
use language::{Buffer, IndentKind, Point, TransactionId};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
|
@ -32,14 +32,14 @@ use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
|
|||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
|
||||
pub struct BufferCodegen {
|
||||
alternatives: Vec<Model<CodegenAlternative>>,
|
||||
alternatives: Vec<Entity<CodegenAlternative>>,
|
||||
pub active_alternative: usize,
|
||||
seen_alternatives: HashSet<usize>,
|
||||
subscriptions: Vec<Subscription>,
|
||||
buffer: Model<MultiBuffer>,
|
||||
buffer: Entity<MultiBuffer>,
|
||||
range: Range<Anchor>,
|
||||
initial_transaction_id: Option<TransactionId>,
|
||||
context_store: Model<ContextStore>,
|
||||
context_store: Entity<ContextStore>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
builder: Arc<PromptBuilder>,
|
||||
pub is_insertion: bool,
|
||||
|
@ -47,15 +47,15 @@ pub struct BufferCodegen {
|
|||
|
||||
impl BufferCodegen {
|
||||
pub fn new(
|
||||
buffer: Model<MultiBuffer>,
|
||||
buffer: Entity<MultiBuffer>,
|
||||
range: Range<Anchor>,
|
||||
initial_transaction_id: Option<TransactionId>,
|
||||
context_store: Model<ContextStore>,
|
||||
context_store: Entity<ContextStore>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
builder: Arc<PromptBuilder>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let codegen = cx.new_model(|cx| {
|
||||
let codegen = cx.new(|cx| {
|
||||
CodegenAlternative::new(
|
||||
buffer.clone(),
|
||||
range.clone(),
|
||||
|
@ -83,7 +83,7 @@ impl BufferCodegen {
|
|||
this
|
||||
}
|
||||
|
||||
fn subscribe_to_alternative(&mut self, cx: &mut ModelContext<Self>) {
|
||||
fn subscribe_to_alternative(&mut self, cx: &mut Context<Self>) {
|
||||
let codegen = self.active_alternative().clone();
|
||||
self.subscriptions.clear();
|
||||
self.subscriptions
|
||||
|
@ -92,22 +92,22 @@ impl BufferCodegen {
|
|||
.push(cx.subscribe(&codegen, |_, _, event, cx| cx.emit(*event)));
|
||||
}
|
||||
|
||||
pub fn active_alternative(&self) -> &Model<CodegenAlternative> {
|
||||
pub fn active_alternative(&self) -> &Entity<CodegenAlternative> {
|
||||
&self.alternatives[self.active_alternative]
|
||||
}
|
||||
|
||||
pub fn status<'a>(&self, cx: &'a AppContext) -> &'a CodegenStatus {
|
||||
pub fn status<'a>(&self, cx: &'a App) -> &'a CodegenStatus {
|
||||
&self.active_alternative().read(cx).status
|
||||
}
|
||||
|
||||
pub fn alternative_count(&self, cx: &AppContext) -> usize {
|
||||
pub fn alternative_count(&self, cx: &App) -> usize {
|
||||
LanguageModelRegistry::read_global(cx)
|
||||
.inline_alternative_models()
|
||||
.len()
|
||||
+ 1
|
||||
}
|
||||
|
||||
pub fn cycle_prev(&mut self, cx: &mut ModelContext<Self>) {
|
||||
pub fn cycle_prev(&mut self, cx: &mut Context<Self>) {
|
||||
let next_active_ix = if self.active_alternative == 0 {
|
||||
self.alternatives.len() - 1
|
||||
} else {
|
||||
|
@ -116,12 +116,12 @@ impl BufferCodegen {
|
|||
self.activate(next_active_ix, cx);
|
||||
}
|
||||
|
||||
pub fn cycle_next(&mut self, cx: &mut ModelContext<Self>) {
|
||||
pub fn cycle_next(&mut self, cx: &mut Context<Self>) {
|
||||
let next_active_ix = (self.active_alternative + 1) % self.alternatives.len();
|
||||
self.activate(next_active_ix, cx);
|
||||
}
|
||||
|
||||
fn activate(&mut self, index: usize, cx: &mut ModelContext<Self>) {
|
||||
fn activate(&mut self, index: usize, cx: &mut Context<Self>) {
|
||||
self.active_alternative()
|
||||
.update(cx, |codegen, cx| codegen.set_active(false, cx));
|
||||
self.seen_alternatives.insert(index);
|
||||
|
@ -132,7 +132,7 @@ impl BufferCodegen {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn start(&mut self, user_prompt: String, cx: &mut ModelContext<Self>) -> Result<()> {
|
||||
pub fn start(&mut self, user_prompt: String, cx: &mut Context<Self>) -> Result<()> {
|
||||
let alternative_models = LanguageModelRegistry::read_global(cx)
|
||||
.inline_alternative_models()
|
||||
.to_vec();
|
||||
|
@ -143,7 +143,7 @@ impl BufferCodegen {
|
|||
self.alternatives.truncate(1);
|
||||
|
||||
for _ in 0..alternative_models.len() {
|
||||
self.alternatives.push(cx.new_model(|cx| {
|
||||
self.alternatives.push(cx.new(|cx| {
|
||||
CodegenAlternative::new(
|
||||
self.buffer.clone(),
|
||||
self.range.clone(),
|
||||
|
@ -172,13 +172,13 @@ impl BufferCodegen {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop(&mut self, cx: &mut ModelContext<Self>) {
|
||||
pub fn stop(&mut self, cx: &mut Context<Self>) {
|
||||
for codegen in &self.alternatives {
|
||||
codegen.update(cx, |codegen, cx| codegen.stop(cx));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
|
||||
pub fn undo(&mut self, cx: &mut Context<Self>) {
|
||||
self.active_alternative()
|
||||
.update(cx, |codegen, cx| codegen.undo(cx));
|
||||
|
||||
|
@ -190,27 +190,27 @@ impl BufferCodegen {
|
|||
});
|
||||
}
|
||||
|
||||
pub fn buffer(&self, cx: &AppContext) -> Model<MultiBuffer> {
|
||||
pub fn buffer(&self, cx: &App) -> Entity<MultiBuffer> {
|
||||
self.active_alternative().read(cx).buffer.clone()
|
||||
}
|
||||
|
||||
pub fn old_buffer(&self, cx: &AppContext) -> Model<Buffer> {
|
||||
pub fn old_buffer(&self, cx: &App) -> Entity<Buffer> {
|
||||
self.active_alternative().read(cx).old_buffer.clone()
|
||||
}
|
||||
|
||||
pub fn snapshot(&self, cx: &AppContext) -> MultiBufferSnapshot {
|
||||
pub fn snapshot(&self, cx: &App) -> MultiBufferSnapshot {
|
||||
self.active_alternative().read(cx).snapshot.clone()
|
||||
}
|
||||
|
||||
pub fn edit_position(&self, cx: &AppContext) -> Option<Anchor> {
|
||||
pub fn edit_position(&self, cx: &App) -> Option<Anchor> {
|
||||
self.active_alternative().read(cx).edit_position
|
||||
}
|
||||
|
||||
pub fn diff<'a>(&self, cx: &'a AppContext) -> &'a Diff {
|
||||
pub fn diff<'a>(&self, cx: &'a App) -> &'a Diff {
|
||||
&self.active_alternative().read(cx).diff
|
||||
}
|
||||
|
||||
pub fn last_equal_ranges<'a>(&self, cx: &'a AppContext) -> &'a [Range<Anchor>] {
|
||||
pub fn last_equal_ranges<'a>(&self, cx: &'a App) -> &'a [Range<Anchor>] {
|
||||
self.active_alternative().read(cx).last_equal_ranges()
|
||||
}
|
||||
}
|
||||
|
@ -218,8 +218,8 @@ impl BufferCodegen {
|
|||
impl EventEmitter<CodegenEvent> for BufferCodegen {}
|
||||
|
||||
pub struct CodegenAlternative {
|
||||
buffer: Model<MultiBuffer>,
|
||||
old_buffer: Model<Buffer>,
|
||||
buffer: Entity<MultiBuffer>,
|
||||
old_buffer: Entity<Buffer>,
|
||||
snapshot: MultiBufferSnapshot,
|
||||
edit_position: Option<Anchor>,
|
||||
range: Range<Anchor>,
|
||||
|
@ -228,7 +228,7 @@ pub struct CodegenAlternative {
|
|||
status: CodegenStatus,
|
||||
generation: Task<()>,
|
||||
diff: Diff,
|
||||
context_store: Option<Model<ContextStore>>,
|
||||
context_store: Option<Entity<ContextStore>>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
_subscription: gpui::Subscription,
|
||||
builder: Arc<PromptBuilder>,
|
||||
|
@ -245,13 +245,13 @@ impl EventEmitter<CodegenEvent> for CodegenAlternative {}
|
|||
|
||||
impl CodegenAlternative {
|
||||
pub fn new(
|
||||
buffer: Model<MultiBuffer>,
|
||||
buffer: Entity<MultiBuffer>,
|
||||
range: Range<Anchor>,
|
||||
active: bool,
|
||||
context_store: Option<Model<ContextStore>>,
|
||||
context_store: Option<Entity<ContextStore>>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
builder: Arc<PromptBuilder>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let snapshot = buffer.read(cx).snapshot(cx);
|
||||
|
||||
|
@ -259,7 +259,7 @@ impl CodegenAlternative {
|
|||
.range_to_buffer_ranges(range.clone())
|
||||
.pop()
|
||||
.unwrap();
|
||||
let old_buffer = cx.new_model(|cx| {
|
||||
let old_buffer = cx.new(|cx| {
|
||||
let text = old_buffer.as_rope().clone();
|
||||
let line_ending = old_buffer.line_ending();
|
||||
let language = old_buffer.language().cloned();
|
||||
|
@ -303,7 +303,7 @@ impl CodegenAlternative {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn set_active(&mut self, active: bool, cx: &mut ModelContext<Self>) {
|
||||
pub fn set_active(&mut self, active: bool, cx: &mut Context<Self>) {
|
||||
if active != self.active {
|
||||
self.active = active;
|
||||
|
||||
|
@ -327,9 +327,9 @@ impl CodegenAlternative {
|
|||
|
||||
fn handle_buffer_event(
|
||||
&mut self,
|
||||
_buffer: Model<MultiBuffer>,
|
||||
_buffer: Entity<MultiBuffer>,
|
||||
event: &multi_buffer::Event,
|
||||
cx: &mut ModelContext<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let multi_buffer::Event::TransactionUndone { transaction_id } = event {
|
||||
if self.transformation_transaction_id == Some(*transaction_id) {
|
||||
|
@ -348,7 +348,7 @@ impl CodegenAlternative {
|
|||
&mut self,
|
||||
user_prompt: String,
|
||||
model: Arc<dyn LanguageModel>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Result<()> {
|
||||
if let Some(transformation_transaction_id) = self.transformation_transaction_id.take() {
|
||||
self.buffer.update(cx, |buffer, cx| {
|
||||
|
@ -375,11 +375,7 @@ impl CodegenAlternative {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
user_prompt: String,
|
||||
cx: &mut AppContext,
|
||||
) -> Result<LanguageModelRequest> {
|
||||
fn build_request(&self, user_prompt: String, cx: &mut App) -> Result<LanguageModelRequest> {
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
let language = buffer.language_at(self.range.start);
|
||||
let language_name = if let Some(language) = language.as_ref() {
|
||||
|
@ -438,7 +434,7 @@ impl CodegenAlternative {
|
|||
model_provider_id: String,
|
||||
model_api_key: Option<String>,
|
||||
stream: impl 'static + Future<Output = Result<LanguageModelTextStream>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let start_time = Instant::now();
|
||||
let snapshot = self.snapshot.clone();
|
||||
|
@ -696,7 +692,7 @@ impl CodegenAlternative {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn stop(&mut self, cx: &mut ModelContext<Self>) {
|
||||
pub fn stop(&mut self, cx: &mut Context<Self>) {
|
||||
self.last_equal_ranges.clear();
|
||||
if self.diff.is_empty() {
|
||||
self.status = CodegenStatus::Idle;
|
||||
|
@ -708,7 +704,7 @@ impl CodegenAlternative {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
|
||||
pub fn undo(&mut self, cx: &mut Context<Self>) {
|
||||
self.buffer.update(cx, |buffer, cx| {
|
||||
if let Some(transaction_id) = self.transformation_transaction_id.take() {
|
||||
buffer.undo_transaction(transaction_id, cx);
|
||||
|
@ -720,7 +716,7 @@ impl CodegenAlternative {
|
|||
fn apply_edits(
|
||||
&mut self,
|
||||
edits: impl IntoIterator<Item = (Range<Anchor>, String)>,
|
||||
cx: &mut ModelContext<CodegenAlternative>,
|
||||
cx: &mut Context<CodegenAlternative>,
|
||||
) {
|
||||
let transaction = self.buffer.update(cx, |buffer, cx| {
|
||||
// Avoid grouping assistant edits with user edits.
|
||||
|
@ -747,7 +743,7 @@ impl CodegenAlternative {
|
|||
fn reapply_line_based_diff(
|
||||
&mut self,
|
||||
line_operations: impl IntoIterator<Item = LineOperation>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let old_snapshot = self.snapshot.clone();
|
||||
let old_range = self.range.to_point(&old_snapshot);
|
||||
|
@ -803,7 +799,7 @@ impl CodegenAlternative {
|
|||
}
|
||||
}
|
||||
|
||||
fn reapply_batch_diff(&mut self, cx: &mut ModelContext<Self>) -> Task<()> {
|
||||
fn reapply_batch_diff(&mut self, cx: &mut Context<Self>) -> Task<()> {
|
||||
let old_snapshot = self.snapshot.clone();
|
||||
let old_range = self.range.to_point(&old_snapshot);
|
||||
let new_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
|
@ -1081,15 +1077,14 @@ mod tests {
|
|||
}
|
||||
}
|
||||
"};
|
||||
let buffer =
|
||||
cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
|
||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let range = buffer.read_with(cx, |buffer, cx| {
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5))
|
||||
});
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let codegen = cx.new_model(|cx| {
|
||||
let codegen = cx.new(|cx| {
|
||||
CodegenAlternative::new(
|
||||
buffer.clone(),
|
||||
range.clone(),
|
||||
|
@ -1146,15 +1141,14 @@ mod tests {
|
|||
le
|
||||
}
|
||||
"};
|
||||
let buffer =
|
||||
cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
|
||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let range = buffer.read_with(cx, |buffer, cx| {
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
snapshot.anchor_before(Point::new(1, 6))..snapshot.anchor_after(Point::new(1, 6))
|
||||
});
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let codegen = cx.new_model(|cx| {
|
||||
let codegen = cx.new(|cx| {
|
||||
CodegenAlternative::new(
|
||||
buffer.clone(),
|
||||
range.clone(),
|
||||
|
@ -1214,15 +1208,14 @@ mod tests {
|
|||
" \n",
|
||||
"}\n" //
|
||||
);
|
||||
let buffer =
|
||||
cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
|
||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let range = buffer.read_with(cx, |buffer, cx| {
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
snapshot.anchor_before(Point::new(1, 2))..snapshot.anchor_after(Point::new(1, 2))
|
||||
});
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let codegen = cx.new_model(|cx| {
|
||||
let codegen = cx.new(|cx| {
|
||||
CodegenAlternative::new(
|
||||
buffer.clone(),
|
||||
range.clone(),
|
||||
|
@ -1282,14 +1275,14 @@ mod tests {
|
|||
\t}
|
||||
}
|
||||
"};
|
||||
let buffer = cx.new_model(|cx| Buffer::local(text, cx));
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let buffer = cx.new(|cx| Buffer::local(text, cx));
|
||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let range = buffer.read_with(cx, |buffer, cx| {
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
snapshot.anchor_before(Point::new(0, 0))..snapshot.anchor_after(Point::new(4, 2))
|
||||
});
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let codegen = cx.new_model(|cx| {
|
||||
let codegen = cx.new(|cx| {
|
||||
CodegenAlternative::new(
|
||||
buffer.clone(),
|
||||
range.clone(),
|
||||
|
@ -1337,15 +1330,14 @@ mod tests {
|
|||
let x = 0;
|
||||
}
|
||||
"};
|
||||
let buffer =
|
||||
cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
|
||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let range = buffer.read_with(cx, |buffer, cx| {
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 14))
|
||||
});
|
||||
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||
let codegen = cx.new_model(|cx| {
|
||||
let codegen = cx.new(|cx| {
|
||||
CodegenAlternative::new(
|
||||
buffer.clone(),
|
||||
range.clone(),
|
||||
|
@ -1432,7 +1424,7 @@ mod tests {
|
|||
}
|
||||
|
||||
fn simulate_response_stream(
|
||||
codegen: Model<CodegenAlternative>,
|
||||
codegen: Entity<CodegenAlternative>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> mpsc::UnboundedSender<String> {
|
||||
let (chunks_tx, chunks_rx) = mpsc::unbounded();
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::path::Path;
|
|||
use std::rc::Rc;
|
||||
|
||||
use file_icons::FileIcons;
|
||||
use gpui::{AppContext, Model, SharedString};
|
||||
use gpui::{App, Entity, SharedString};
|
||||
use language::Buffer;
|
||||
use language_model::{LanguageModelRequestMessage, MessageContent};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -63,14 +63,14 @@ impl ContextKind {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Context {
|
||||
pub enum AssistantContext {
|
||||
File(FileContext),
|
||||
Directory(DirectoryContext),
|
||||
FetchedUrl(FetchedUrlContext),
|
||||
Thread(ThreadContext),
|
||||
}
|
||||
|
||||
impl Context {
|
||||
impl AssistantContext {
|
||||
pub fn id(&self) -> ContextId {
|
||||
match self {
|
||||
Self::File(file) => file.id,
|
||||
|
@ -107,7 +107,7 @@ pub struct FetchedUrlContext {
|
|||
#[derive(Debug)]
|
||||
pub struct ThreadContext {
|
||||
pub id: ContextId,
|
||||
pub thread: Model<Thread>,
|
||||
pub thread: Entity<Thread>,
|
||||
pub text: SharedString,
|
||||
}
|
||||
|
||||
|
@ -117,13 +117,13 @@ pub struct ThreadContext {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct ContextBuffer {
|
||||
pub id: BufferId,
|
||||
pub buffer: Model<Buffer>,
|
||||
pub buffer: Entity<Buffer>,
|
||||
pub version: clock::Global,
|
||||
pub text: SharedString,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn snapshot(&self, cx: &AppContext) -> Option<ContextSnapshot> {
|
||||
impl AssistantContext {
|
||||
pub fn snapshot(&self, cx: &App) -> Option<ContextSnapshot> {
|
||||
match &self {
|
||||
Self::File(file_context) => file_context.snapshot(cx),
|
||||
Self::Directory(directory_context) => Some(directory_context.snapshot()),
|
||||
|
@ -134,7 +134,7 @@ impl Context {
|
|||
}
|
||||
|
||||
impl FileContext {
|
||||
pub fn snapshot(&self, cx: &AppContext) -> Option<ContextSnapshot> {
|
||||
pub fn snapshot(&self, cx: &App) -> Option<ContextSnapshot> {
|
||||
let buffer = self.context_buffer.buffer.read(cx);
|
||||
let path = buffer_path_log_err(buffer)?;
|
||||
let full_path: SharedString = path.to_string_lossy().into_owned().into();
|
||||
|
@ -221,7 +221,7 @@ impl FetchedUrlContext {
|
|||
}
|
||||
|
||||
impl ThreadContext {
|
||||
pub fn snapshot(&self, cx: &AppContext) -> ContextSnapshot {
|
||||
pub fn snapshot(&self, cx: &App) -> ContextSnapshot {
|
||||
let thread = self.thread.read(cx);
|
||||
ContextSnapshot {
|
||||
id: self.id,
|
||||
|
|
|
@ -9,10 +9,7 @@ use std::sync::Arc;
|
|||
use anyhow::{anyhow, Result};
|
||||
use editor::Editor;
|
||||
use file_context_picker::render_file_context_entry;
|
||||
use gpui::{
|
||||
AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Task, View, WeakModel,
|
||||
WeakView,
|
||||
};
|
||||
use gpui::{App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Task, WeakEntity};
|
||||
use project::ProjectPath;
|
||||
use thread_context_picker::{render_thread_context_entry, ThreadContextEntry};
|
||||
use ui::{prelude::*, ContextMenu, ContextMenuEntry, ContextMenuItem};
|
||||
|
@ -35,33 +32,38 @@ pub enum ConfirmBehavior {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
enum ContextPickerMode {
|
||||
Default(View<ContextMenu>),
|
||||
File(View<FileContextPicker>),
|
||||
Directory(View<DirectoryContextPicker>),
|
||||
Fetch(View<FetchContextPicker>),
|
||||
Thread(View<ThreadContextPicker>),
|
||||
Default(Entity<ContextMenu>),
|
||||
File(Entity<FileContextPicker>),
|
||||
Directory(Entity<DirectoryContextPicker>),
|
||||
Fetch(Entity<FetchContextPicker>),
|
||||
Thread(Entity<ThreadContextPicker>),
|
||||
}
|
||||
|
||||
pub(super) struct ContextPicker {
|
||||
mode: ContextPickerMode,
|
||||
workspace: WeakView<Workspace>,
|
||||
editor: WeakView<Editor>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
editor: WeakEntity<Editor>,
|
||||
context_store: WeakEntity<ContextStore>,
|
||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
}
|
||||
|
||||
impl ContextPicker {
|
||||
pub fn new(
|
||||
workspace: WeakView<Workspace>,
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
editor: WeakView<Editor>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||
context_store: WeakEntity<ContextStore>,
|
||||
editor: WeakEntity<Editor>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
ContextPicker {
|
||||
mode: ContextPickerMode::Default(ContextMenu::build(cx, |menu, _cx| menu)),
|
||||
mode: ContextPickerMode::Default(ContextMenu::build(
|
||||
window,
|
||||
cx,
|
||||
|menu, _window, _cx| menu,
|
||||
)),
|
||||
workspace,
|
||||
context_store,
|
||||
thread_store,
|
||||
|
@ -70,15 +72,15 @@ impl ContextPicker {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.mode = ContextPickerMode::Default(self.build_menu(cx));
|
||||
pub fn init(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.mode = ContextPickerMode::Default(self.build_menu(window, cx));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn build_menu(&mut self, cx: &mut ViewContext<Self>) -> View<ContextMenu> {
|
||||
let context_picker = cx.view().clone();
|
||||
fn build_menu(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Entity<ContextMenu> {
|
||||
let context_picker = cx.model().clone();
|
||||
|
||||
let menu = ContextMenu::build(cx, move |menu, cx| {
|
||||
let menu = ContextMenu::build(window, cx, move |menu, _window, cx| {
|
||||
let recent = self.recent_entries(cx);
|
||||
let has_recent = !recent.is_empty();
|
||||
let recent_entries = recent
|
||||
|
@ -97,7 +99,7 @@ impl ContextPicker {
|
|||
|
||||
let menu = menu
|
||||
.when(has_recent, |menu| {
|
||||
menu.custom_row(|_| {
|
||||
menu.custom_row(|_, _| {
|
||||
div()
|
||||
.mb_1()
|
||||
.child(
|
||||
|
@ -117,8 +119,8 @@ impl ContextPicker {
|
|||
.icon(kind.icon())
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Muted)
|
||||
.handler(move |cx| {
|
||||
context_picker.update(cx, |this, cx| this.select_kind(kind, cx))
|
||||
.handler(move |window, cx| {
|
||||
context_picker.update(cx, |this, cx| this.select_kind(kind, window, cx))
|
||||
})
|
||||
}));
|
||||
|
||||
|
@ -141,52 +143,56 @@ impl ContextPicker {
|
|||
self.thread_store.is_some()
|
||||
}
|
||||
|
||||
fn select_kind(&mut self, kind: ContextKind, cx: &mut ViewContext<Self>) {
|
||||
let context_picker = cx.view().downgrade();
|
||||
fn select_kind(&mut self, kind: ContextKind, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let context_picker = cx.model().downgrade();
|
||||
|
||||
match kind {
|
||||
ContextKind::File => {
|
||||
self.mode = ContextPickerMode::File(cx.new_view(|cx| {
|
||||
self.mode = ContextPickerMode::File(cx.new(|cx| {
|
||||
FileContextPicker::new(
|
||||
context_picker.clone(),
|
||||
self.workspace.clone(),
|
||||
self.editor.clone(),
|
||||
self.context_store.clone(),
|
||||
self.confirm_behavior,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
}
|
||||
ContextKind::Directory => {
|
||||
self.mode = ContextPickerMode::Directory(cx.new_view(|cx| {
|
||||
self.mode = ContextPickerMode::Directory(cx.new(|cx| {
|
||||
DirectoryContextPicker::new(
|
||||
context_picker.clone(),
|
||||
self.workspace.clone(),
|
||||
self.context_store.clone(),
|
||||
self.confirm_behavior,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
}
|
||||
ContextKind::FetchedUrl => {
|
||||
self.mode = ContextPickerMode::Fetch(cx.new_view(|cx| {
|
||||
self.mode = ContextPickerMode::Fetch(cx.new(|cx| {
|
||||
FetchContextPicker::new(
|
||||
context_picker.clone(),
|
||||
self.workspace.clone(),
|
||||
self.context_store.clone(),
|
||||
self.confirm_behavior,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
}
|
||||
ContextKind::Thread => {
|
||||
if let Some(thread_store) = self.thread_store.as_ref() {
|
||||
self.mode = ContextPickerMode::Thread(cx.new_view(|cx| {
|
||||
self.mode = ContextPickerMode::Thread(cx.new(|cx| {
|
||||
ThreadContextPicker::new(
|
||||
thread_store.clone(),
|
||||
context_picker.clone(),
|
||||
self.context_store.clone(),
|
||||
self.confirm_behavior,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
|
@ -195,12 +201,12 @@ impl ContextPicker {
|
|||
}
|
||||
|
||||
cx.notify();
|
||||
cx.focus_self();
|
||||
cx.focus_self(window);
|
||||
}
|
||||
|
||||
fn recent_menu_item(
|
||||
&self,
|
||||
context_picker: View<ContextPicker>,
|
||||
context_picker: Entity<ContextPicker>,
|
||||
ix: usize,
|
||||
entry: RecentEntry,
|
||||
) -> ContextMenuItem {
|
||||
|
@ -213,7 +219,7 @@ impl ContextPicker {
|
|||
let path = project_path.path.clone();
|
||||
|
||||
ContextMenuItem::custom_entry(
|
||||
move |cx| {
|
||||
move |_window, cx| {
|
||||
render_file_context_entry(
|
||||
ElementId::NamedInteger("ctx-recent".into(), ix),
|
||||
&path,
|
||||
|
@ -223,9 +229,9 @@ impl ContextPicker {
|
|||
)
|
||||
.into_any()
|
||||
},
|
||||
move |cx| {
|
||||
move |window, cx| {
|
||||
context_picker.update(cx, |this, cx| {
|
||||
this.add_recent_file(project_path.clone(), cx);
|
||||
this.add_recent_file(project_path.clone(), window, cx);
|
||||
})
|
||||
},
|
||||
)
|
||||
|
@ -235,11 +241,11 @@ impl ContextPicker {
|
|||
let view_thread = thread.clone();
|
||||
|
||||
ContextMenuItem::custom_entry(
|
||||
move |cx| {
|
||||
move |_window, cx| {
|
||||
render_thread_context_entry(&view_thread, context_store.clone(), cx)
|
||||
.into_any()
|
||||
},
|
||||
move |cx| {
|
||||
move |_window, cx| {
|
||||
context_picker.update(cx, |this, cx| {
|
||||
this.add_recent_thread(thread.clone(), cx)
|
||||
.detach_and_log_err(cx);
|
||||
|
@ -250,7 +256,12 @@ impl ContextPicker {
|
|||
}
|
||||
}
|
||||
|
||||
fn add_recent_file(&self, project_path: ProjectPath, cx: &mut ViewContext<Self>) {
|
||||
fn add_recent_file(
|
||||
&self,
|
||||
project_path: ProjectPath,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(context_store) = self.context_store.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
@ -259,8 +270,10 @@ impl ContextPicker {
|
|||
context_store.add_file_from_path(project_path.clone(), cx)
|
||||
});
|
||||
|
||||
cx.spawn(|_, mut cx| async move { task.await.notify_async_err(&mut cx) })
|
||||
.detach();
|
||||
cx.spawn_in(window, |_, mut cx| async move {
|
||||
task.await.notify_async_err(&mut cx)
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
@ -268,7 +281,7 @@ impl ContextPicker {
|
|||
fn add_recent_thread(
|
||||
&self,
|
||||
thread: ThreadContextEntry,
|
||||
cx: &mut ViewContext<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let Some(context_store) = self.context_store.upgrade() else {
|
||||
return Task::ready(Err(anyhow!("context store not available")));
|
||||
|
@ -293,7 +306,7 @@ impl ContextPicker {
|
|||
})
|
||||
}
|
||||
|
||||
fn recent_entries(&self, cx: &mut WindowContext) -> Vec<RecentEntry> {
|
||||
fn recent_entries(&self, cx: &mut App) -> Vec<RecentEntry> {
|
||||
let Some(workspace) = self.workspace.upgrade().map(|w| w.read(cx)) else {
|
||||
return vec![];
|
||||
};
|
||||
|
@ -363,7 +376,7 @@ impl ContextPicker {
|
|||
recent
|
||||
}
|
||||
|
||||
fn active_singleton_buffer_path(workspace: &Workspace, cx: &AppContext) -> Option<PathBuf> {
|
||||
fn active_singleton_buffer_path(workspace: &Workspace, cx: &App) -> Option<PathBuf> {
|
||||
let active_item = workspace.active_item(cx)?;
|
||||
|
||||
let editor = active_item.to_any().downcast::<Editor>().ok()?.read(cx);
|
||||
|
@ -376,8 +389,8 @@ impl ContextPicker {
|
|||
|
||||
impl EventEmitter<DismissEvent> for ContextPicker {}
|
||||
|
||||
impl FocusableView for ContextPicker {
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
impl Focusable for ContextPicker {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
match &self.mode {
|
||||
ContextPickerMode::Default(menu) => menu.focus_handle(cx),
|
||||
ContextPickerMode::File(file_picker) => file_picker.focus_handle(cx),
|
||||
|
@ -389,7 +402,7 @@ impl FocusableView for ContextPicker {
|
|||
}
|
||||
|
||||
impl Render for ContextPicker {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.w(px(400.))
|
||||
.min_w(px(400.))
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::sync::atomic::AtomicBool;
|
|||
use std::sync::Arc;
|
||||
|
||||
use fuzzy::PathMatch;
|
||||
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
|
||||
use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
|
||||
use ui::{prelude::*, ListItem};
|
||||
|
@ -14,16 +14,17 @@ use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
|||
use crate::context_store::ContextStore;
|
||||
|
||||
pub struct DirectoryContextPicker {
|
||||
picker: View<Picker<DirectoryContextPickerDelegate>>,
|
||||
picker: Entity<Picker<DirectoryContextPickerDelegate>>,
|
||||
}
|
||||
|
||||
impl DirectoryContextPicker {
|
||||
pub fn new(
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
context_picker: WeakEntity<ContextPicker>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
context_store: WeakEntity<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let delegate = DirectoryContextPickerDelegate::new(
|
||||
context_picker,
|
||||
|
@ -31,28 +32,28 @@ impl DirectoryContextPicker {
|
|||
context_store,
|
||||
confirm_behavior,
|
||||
);
|
||||
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
|
||||
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
|
||||
|
||||
Self { picker }
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for DirectoryContextPicker {
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
impl Focusable for DirectoryContextPicker {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
self.picker.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for DirectoryContextPicker {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
self.picker.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DirectoryContextPickerDelegate {
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
context_picker: WeakEntity<ContextPicker>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
context_store: WeakEntity<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
matches: Vec<PathMatch>,
|
||||
selected_index: usize,
|
||||
|
@ -60,9 +61,9 @@ pub struct DirectoryContextPickerDelegate {
|
|||
|
||||
impl DirectoryContextPickerDelegate {
|
||||
pub fn new(
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
context_picker: WeakEntity<ContextPicker>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
context_store: WeakEntity<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
) -> Self {
|
||||
Self {
|
||||
|
@ -79,8 +80,8 @@ impl DirectoryContextPickerDelegate {
|
|||
&mut self,
|
||||
query: String,
|
||||
cancellation_flag: Arc<AtomicBool>,
|
||||
workspace: &View<Workspace>,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
workspace: &Entity<Workspace>,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Task<Vec<PathMatch>> {
|
||||
if query.is_empty() {
|
||||
let workspace = workspace.read(cx);
|
||||
|
@ -146,15 +147,25 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
|
|||
self.selected_index
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn set_selected_index(
|
||||
&mut self,
|
||||
ix: usize,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Picker<Self>>,
|
||||
) {
|
||||
self.selected_index = ix;
|
||||
}
|
||||
|
||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
||||
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
||||
"Search folders…".into()
|
||||
}
|
||||
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
query: String,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Task<()> {
|
||||
let Some(workspace) = self.workspace.upgrade() else {
|
||||
return Task::ready(());
|
||||
};
|
||||
|
@ -173,7 +184,7 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
|
|||
})
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
let Some(mat) = self.matches.get(self.selected_index) else {
|
||||
return;
|
||||
};
|
||||
|
@ -194,19 +205,19 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
|
|||
};
|
||||
|
||||
let confirm_behavior = self.confirm_behavior;
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
match task.await.notify_async_err(&mut cx) {
|
||||
None => anyhow::Ok(()),
|
||||
Some(()) => this.update(&mut cx, |this, cx| match confirm_behavior {
|
||||
Some(()) => this.update_in(&mut cx, |this, window, cx| match confirm_behavior {
|
||||
ConfirmBehavior::KeepOpen => {}
|
||||
ConfirmBehavior::Close => this.delegate.dismissed(cx),
|
||||
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
|
||||
}),
|
||||
}
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
self.context_picker
|
||||
.update(cx, |_, cx| {
|
||||
cx.emit(DismissEvent);
|
||||
|
@ -218,7 +229,8 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
|
|||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let path_match = &self.matches[ix];
|
||||
let directory_name = path_match.path.to_string_lossy().to_string();
|
||||
|
|
|
@ -4,27 +4,28 @@ use std::sync::Arc;
|
|||
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
use futures::AsyncReadExt as _;
|
||||
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
|
||||
use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
|
||||
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
|
||||
use http_client::{AsyncBody, HttpClientWithUrl};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use ui::{prelude::*, ListItem, ViewContext};
|
||||
use ui::{prelude::*, Context, ListItem, Window};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
use crate::context_store::ContextStore;
|
||||
|
||||
pub struct FetchContextPicker {
|
||||
picker: View<Picker<FetchContextPickerDelegate>>,
|
||||
picker: Entity<Picker<FetchContextPickerDelegate>>,
|
||||
}
|
||||
|
||||
impl FetchContextPicker {
|
||||
pub fn new(
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
context_picker: WeakEntity<ContextPicker>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
context_store: WeakEntity<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let delegate = FetchContextPickerDelegate::new(
|
||||
context_picker,
|
||||
|
@ -32,20 +33,20 @@ impl FetchContextPicker {
|
|||
context_store,
|
||||
confirm_behavior,
|
||||
);
|
||||
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
|
||||
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
|
||||
|
||||
Self { picker }
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for FetchContextPicker {
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
impl Focusable for FetchContextPicker {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
self.picker.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for FetchContextPicker {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
self.picker.clone()
|
||||
}
|
||||
}
|
||||
|
@ -58,18 +59,18 @@ enum ContentType {
|
|||
}
|
||||
|
||||
pub struct FetchContextPickerDelegate {
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
context_picker: WeakEntity<ContextPicker>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
context_store: WeakEntity<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
url: String,
|
||||
}
|
||||
|
||||
impl FetchContextPickerDelegate {
|
||||
pub fn new(
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
context_picker: WeakEntity<ContextPicker>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
context_store: WeakEntity<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
) -> Self {
|
||||
FetchContextPickerDelegate {
|
||||
|
@ -166,7 +167,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
fn no_matches_text(&self, _cx: &mut WindowContext) -> SharedString {
|
||||
fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> SharedString {
|
||||
"Enter the URL that you would like to fetch".into()
|
||||
}
|
||||
|
||||
|
@ -174,19 +175,30 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
|||
0
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, _ix: usize, _cx: &mut ViewContext<Picker<Self>>) {}
|
||||
fn set_selected_index(
|
||||
&mut self,
|
||||
_ix: usize,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Picker<Self>>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
||||
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
||||
"Enter a URL…".into()
|
||||
}
|
||||
|
||||
fn update_matches(&mut self, query: String, _cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
query: String,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Picker<Self>>,
|
||||
) -> Task<()> {
|
||||
self.url = query;
|
||||
|
||||
Task::ready(())
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
let Some(workspace) = self.workspace.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
@ -194,13 +206,13 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
|||
let http_client = workspace.read(cx).client().http_client().clone();
|
||||
let url = self.url.clone();
|
||||
let confirm_behavior = self.confirm_behavior;
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
let text = cx
|
||||
.background_executor()
|
||||
.spawn(Self::build_message(http_client, url.clone()))
|
||||
.await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.update_in(&mut cx, |this, window, cx| {
|
||||
this.delegate
|
||||
.context_store
|
||||
.update(cx, |context_store, _cx| {
|
||||
|
@ -209,7 +221,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
|||
|
||||
match confirm_behavior {
|
||||
ConfirmBehavior::KeepOpen => {}
|
||||
ConfirmBehavior::Close => this.delegate.dismissed(cx),
|
||||
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
|
@ -220,7 +232,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
|||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
self.context_picker
|
||||
.update(cx, |_, cx| {
|
||||
cx.emit(DismissEvent);
|
||||
|
@ -232,7 +244,8 @@ impl PickerDelegate for FetchContextPickerDelegate {
|
|||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let added = self.context_store.upgrade().map_or(false, |context_store| {
|
||||
context_store.read(cx).includes_url(&self.url).is_some()
|
||||
|
|
|
@ -11,8 +11,8 @@ use editor::{Anchor, Editor, FoldPlaceholder, ToPoint};
|
|||
use file_icons::FileIcons;
|
||||
use fuzzy::PathMatch;
|
||||
use gpui::{
|
||||
AnyElement, AppContext, DismissEvent, Empty, FocusHandle, FocusableView, Stateful, Task, View,
|
||||
WeakModel, WeakView,
|
||||
AnyElement, App, DismissEvent, Empty, Entity, FocusHandle, Focusable, Stateful, Task,
|
||||
WeakEntity,
|
||||
};
|
||||
use multi_buffer::{MultiBufferPoint, MultiBufferRow};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
|
@ -27,17 +27,18 @@ use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
|||
use crate::context_store::{ContextStore, FileInclusion};
|
||||
|
||||
pub struct FileContextPicker {
|
||||
picker: View<Picker<FileContextPickerDelegate>>,
|
||||
picker: Entity<Picker<FileContextPickerDelegate>>,
|
||||
}
|
||||
|
||||
impl FileContextPicker {
|
||||
pub fn new(
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
editor: WeakView<Editor>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
context_picker: WeakEntity<ContextPicker>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
editor: WeakEntity<Editor>,
|
||||
context_store: WeakEntity<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let delegate = FileContextPickerDelegate::new(
|
||||
context_picker,
|
||||
|
@ -46,29 +47,29 @@ impl FileContextPicker {
|
|||
context_store,
|
||||
confirm_behavior,
|
||||
);
|
||||
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
|
||||
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
|
||||
|
||||
Self { picker }
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for FileContextPicker {
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
impl Focusable for FileContextPicker {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
self.picker.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for FileContextPicker {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
self.picker.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FileContextPickerDelegate {
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
editor: WeakView<Editor>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
context_picker: WeakEntity<ContextPicker>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
editor: WeakEntity<Editor>,
|
||||
context_store: WeakEntity<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
matches: Vec<PathMatch>,
|
||||
selected_index: usize,
|
||||
|
@ -76,10 +77,10 @@ pub struct FileContextPickerDelegate {
|
|||
|
||||
impl FileContextPickerDelegate {
|
||||
pub fn new(
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
editor: WeakView<Editor>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
context_picker: WeakEntity<ContextPicker>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
editor: WeakEntity<Editor>,
|
||||
context_store: WeakEntity<ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
) -> Self {
|
||||
Self {
|
||||
|
@ -97,8 +98,9 @@ impl FileContextPickerDelegate {
|
|||
&mut self,
|
||||
query: String,
|
||||
cancellation_flag: Arc<AtomicBool>,
|
||||
workspace: &View<Workspace>,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
workspace: &Entity<Workspace>,
|
||||
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Task<Vec<PathMatch>> {
|
||||
if query.is_empty() {
|
||||
let workspace = workspace.read(cx);
|
||||
|
@ -180,22 +182,32 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||
self.selected_index
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn set_selected_index(
|
||||
&mut self,
|
||||
ix: usize,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Picker<Self>>,
|
||||
) {
|
||||
self.selected_index = ix;
|
||||
}
|
||||
|
||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
||||
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
||||
"Search files…".into()
|
||||
}
|
||||
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
query: String,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Task<()> {
|
||||
let Some(workspace) = self.workspace.upgrade() else {
|
||||
return Task::ready(());
|
||||
};
|
||||
|
||||
let search_task = self.search(query, Arc::<AtomicBool>::default(), &workspace, cx);
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
// TODO: This should be probably be run in the background.
|
||||
let paths = search_task.await;
|
||||
|
||||
|
@ -206,7 +218,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||
})
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
let Some(mat) = self.matches.get(self.selected_index) else {
|
||||
return;
|
||||
};
|
||||
|
@ -231,7 +243,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||
};
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.transact(cx, |editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
// Move empty selections left by 1 column to select the `@`s, so they get overwritten when we insert.
|
||||
{
|
||||
let mut selections = editor.selections.all::<MultiBufferPoint>(cx);
|
||||
|
@ -247,7 +259,9 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
|
||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||
s.select(selections)
|
||||
});
|
||||
}
|
||||
|
||||
let start_anchors = {
|
||||
|
@ -260,7 +274,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
editor.insert(&full_path, cx);
|
||||
editor.insert(&full_path, window, cx);
|
||||
|
||||
let end_anchors = {
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
|
@ -272,14 +286,15 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
editor.insert("\n", cx); // Needed to end the fold
|
||||
editor.insert("\n", window, cx); // Needed to end the fold
|
||||
|
||||
let placeholder = FoldPlaceholder {
|
||||
render: render_fold_icon_button(IconName::File, file_name.into()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let render_trailer = move |_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
|
||||
let render_trailer =
|
||||
move |_row, _unfold, _window: &mut Window, _cx: &mut App| Empty.into_any();
|
||||
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let mut rows_to_fold = BTreeSet::new();
|
||||
|
@ -300,7 +315,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||
editor.insert_creases(crease_iter, cx);
|
||||
|
||||
for buffer_row in rows_to_fold {
|
||||
editor.fold_at(&FoldAt { buffer_row }, cx);
|
||||
editor.fold_at(&FoldAt { buffer_row }, window, cx);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -316,19 +331,19 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||
};
|
||||
|
||||
let confirm_behavior = self.confirm_behavior;
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
match task.await.notify_async_err(&mut cx) {
|
||||
None => anyhow::Ok(()),
|
||||
Some(()) => this.update(&mut cx, |this, cx| match confirm_behavior {
|
||||
Some(()) => this.update_in(&mut cx, |this, window, cx| match confirm_behavior {
|
||||
ConfirmBehavior::KeepOpen => {}
|
||||
ConfirmBehavior::Close => this.delegate.dismissed(cx),
|
||||
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
|
||||
}),
|
||||
}
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
self.context_picker
|
||||
.update(cx, |_, cx| {
|
||||
cx.emit(DismissEvent);
|
||||
|
@ -340,7 +355,8 @@ impl PickerDelegate for FileContextPickerDelegate {
|
|||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let path_match = &self.matches[ix];
|
||||
|
||||
|
@ -363,8 +379,8 @@ pub fn render_file_context_entry(
|
|||
id: ElementId,
|
||||
path: &Path,
|
||||
path_prefix: &Arc<str>,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
cx: &WindowContext,
|
||||
context_store: WeakEntity<ContextStore>,
|
||||
cx: &App,
|
||||
) -> Stateful<Div> {
|
||||
let (file_name, directory) = if path == Path::new("") {
|
||||
(SharedString::from(path_prefix.clone()), None)
|
||||
|
@ -437,7 +453,7 @@ pub fn render_file_context_entry(
|
|||
)
|
||||
.child(Label::new("Included").size(LabelSize::Small)),
|
||||
)
|
||||
.tooltip(move |cx| Tooltip::text(format!("in {dir_name}"), cx))
|
||||
.tooltip(Tooltip::text(format!("in {dir_name}")))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -445,8 +461,8 @@ pub fn render_file_context_entry(
|
|||
fn render_fold_icon_button(
|
||||
icon: IconName,
|
||||
label: SharedString,
|
||||
) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut WindowContext) -> AnyElement> {
|
||||
Arc::new(move |fold_id, _fold_range, _cx| {
|
||||
) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut Window, &mut App) -> AnyElement> {
|
||||
Arc::new(move |fold_id, _fold_range, _window, _cx| {
|
||||
ButtonLike::new(fold_id)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ElevatedSurface)
|
||||
|
@ -461,13 +477,14 @@ fn fold_toggle(
|
|||
) -> impl Fn(
|
||||
MultiBufferRow,
|
||||
bool,
|
||||
Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
|
||||
&mut WindowContext,
|
||||
Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
|
||||
&mut Window,
|
||||
&mut App,
|
||||
) -> AnyElement {
|
||||
move |row, is_folded, fold, _cx| {
|
||||
move |row, is_folded, fold, _window, _cx| {
|
||||
Disclosure::new((name, row.0 as u64), !is_folded)
|
||||
.toggle_state(is_folded)
|
||||
.on_click(move |_e, cx| fold(!is_folded, cx))
|
||||
.on_click(move |_e, window, cx| fold(!is_folded, window, cx))
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use fuzzy::StringMatchCandidate;
|
||||
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
|
||||
use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use ui::{prelude::*, ListItem};
|
||||
|
||||
|
@ -11,16 +11,17 @@ use crate::thread::ThreadId;
|
|||
use crate::thread_store::ThreadStore;
|
||||
|
||||
pub struct ThreadContextPicker {
|
||||
picker: View<Picker<ThreadContextPickerDelegate>>,
|
||||
picker: Entity<Picker<ThreadContextPickerDelegate>>,
|
||||
}
|
||||
|
||||
impl ThreadContextPicker {
|
||||
pub fn new(
|
||||
thread_store: WeakModel<ThreadStore>,
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
context_store: WeakModel<context_store::ContextStore>,
|
||||
thread_store: WeakEntity<ThreadStore>,
|
||||
context_picker: WeakEntity<ContextPicker>,
|
||||
context_store: WeakEntity<context_store::ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let delegate = ThreadContextPickerDelegate::new(
|
||||
thread_store,
|
||||
|
@ -28,20 +29,20 @@ impl ThreadContextPicker {
|
|||
context_store,
|
||||
confirm_behavior,
|
||||
);
|
||||
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
|
||||
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
|
||||
|
||||
ThreadContextPicker { picker }
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for ThreadContextPicker {
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
impl Focusable for ThreadContextPicker {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
self.picker.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ThreadContextPicker {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
self.picker.clone()
|
||||
}
|
||||
}
|
||||
|
@ -53,9 +54,9 @@ pub struct ThreadContextEntry {
|
|||
}
|
||||
|
||||
pub struct ThreadContextPickerDelegate {
|
||||
thread_store: WeakModel<ThreadStore>,
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
context_store: WeakModel<context_store::ContextStore>,
|
||||
thread_store: WeakEntity<ThreadStore>,
|
||||
context_picker: WeakEntity<ContextPicker>,
|
||||
context_store: WeakEntity<context_store::ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
matches: Vec<ThreadContextEntry>,
|
||||
selected_index: usize,
|
||||
|
@ -63,9 +64,9 @@ pub struct ThreadContextPickerDelegate {
|
|||
|
||||
impl ThreadContextPickerDelegate {
|
||||
pub fn new(
|
||||
thread_store: WeakModel<ThreadStore>,
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
context_store: WeakModel<context_store::ContextStore>,
|
||||
thread_store: WeakEntity<ThreadStore>,
|
||||
context_picker: WeakEntity<ContextPicker>,
|
||||
context_store: WeakEntity<context_store::ContextStore>,
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
) -> Self {
|
||||
ThreadContextPickerDelegate {
|
||||
|
@ -90,15 +91,25 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
|||
self.selected_index
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn set_selected_index(
|
||||
&mut self,
|
||||
ix: usize,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Picker<Self>>,
|
||||
) {
|
||||
self.selected_index = ix;
|
||||
}
|
||||
|
||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
||||
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
||||
"Search threads…".into()
|
||||
}
|
||||
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
query: String,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Task<()> {
|
||||
let Ok(threads) = self.thread_store.update(cx, |this, _cx| {
|
||||
this.threads()
|
||||
.into_iter()
|
||||
|
@ -138,7 +149,7 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
|||
}
|
||||
});
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
let matches = search_task.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.delegate.matches = matches;
|
||||
|
@ -149,7 +160,7 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
|||
})
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
let Some(entry) = self.matches.get(self.selected_index) else {
|
||||
return;
|
||||
};
|
||||
|
@ -160,9 +171,9 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
|||
|
||||
let open_thread_task = thread_store.update(cx, |this, cx| this.open_thread(&entry.id, cx));
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
let thread = open_thread_task.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.update_in(&mut cx, |this, window, cx| {
|
||||
this.delegate
|
||||
.context_store
|
||||
.update(cx, |context_store, cx| context_store.add_thread(thread, cx))
|
||||
|
@ -170,14 +181,14 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
|||
|
||||
match this.delegate.confirm_behavior {
|
||||
ConfirmBehavior::KeepOpen => {}
|
||||
ConfirmBehavior::Close => this.delegate.dismissed(cx),
|
||||
ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
|
||||
}
|
||||
})
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
self.context_picker
|
||||
.update(cx, |_, cx| {
|
||||
cx.emit(DismissEvent);
|
||||
|
@ -189,7 +200,8 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
|||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let thread = &self.matches[ix];
|
||||
|
||||
|
@ -201,8 +213,8 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
|||
|
||||
pub fn render_thread_context_entry(
|
||||
thread: &ThreadContextEntry,
|
||||
context_store: WeakModel<ContextStore>,
|
||||
cx: &mut WindowContext,
|
||||
context_store: WeakEntity<ContextStore>,
|
||||
cx: &mut App,
|
||||
) -> Div {
|
||||
let added = context_store.upgrade().map_or(false, |ctx_store| {
|
||||
ctx_store.read(cx).includes_thread(&thread.id).is_some()
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::sync::Arc;
|
|||
use anyhow::{anyhow, bail, Result};
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use futures::{self, future, Future, FutureExt};
|
||||
use gpui::{AppContext, AsyncAppContext, Model, ModelContext, SharedString, Task, WeakView};
|
||||
use gpui::{App, AsyncAppContext, Context, Entity, SharedString, Task, WeakEntity};
|
||||
use language::Buffer;
|
||||
use project::{ProjectPath, Worktree};
|
||||
use rope::Rope;
|
||||
|
@ -12,15 +12,15 @@ use text::BufferId;
|
|||
use workspace::Workspace;
|
||||
|
||||
use crate::context::{
|
||||
Context, ContextBuffer, ContextId, ContextSnapshot, DirectoryContext, FetchedUrlContext,
|
||||
FileContext, ThreadContext,
|
||||
AssistantContext, ContextBuffer, ContextId, ContextSnapshot, DirectoryContext,
|
||||
FetchedUrlContext, FileContext, ThreadContext,
|
||||
};
|
||||
use crate::context_strip::SuggestedContext;
|
||||
use crate::thread::{Thread, ThreadId};
|
||||
|
||||
pub struct ContextStore {
|
||||
workspace: WeakView<Workspace>,
|
||||
context: Vec<Context>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
context: Vec<AssistantContext>,
|
||||
// TODO: If an EntityId is used for all context types (like BufferId), can remove ContextId.
|
||||
next_context_id: ContextId,
|
||||
files: BTreeMap<BufferId, ContextId>,
|
||||
|
@ -30,7 +30,7 @@ pub struct ContextStore {
|
|||
}
|
||||
|
||||
impl ContextStore {
|
||||
pub fn new(workspace: WeakView<Workspace>) -> Self {
|
||||
pub fn new(workspace: WeakEntity<Workspace>) -> Self {
|
||||
Self {
|
||||
workspace,
|
||||
context: Vec::new(),
|
||||
|
@ -42,16 +42,13 @@ impl ContextStore {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn snapshot<'a>(
|
||||
&'a self,
|
||||
cx: &'a AppContext,
|
||||
) -> impl Iterator<Item = ContextSnapshot> + 'a {
|
||||
pub fn snapshot<'a>(&'a self, cx: &'a App) -> impl Iterator<Item = ContextSnapshot> + 'a {
|
||||
self.context()
|
||||
.iter()
|
||||
.flat_map(|context| context.snapshot(cx))
|
||||
}
|
||||
|
||||
pub fn context(&self) -> &Vec<Context> {
|
||||
pub fn context(&self) -> &Vec<AssistantContext> {
|
||||
&self.context
|
||||
}
|
||||
|
||||
|
@ -66,7 +63,7 @@ impl ContextStore {
|
|||
pub fn add_file_from_path(
|
||||
&mut self,
|
||||
project_path: ProjectPath,
|
||||
cx: &mut ModelContext<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let workspace = self.workspace.clone();
|
||||
|
||||
|
@ -122,8 +119,8 @@ impl ContextStore {
|
|||
|
||||
pub fn add_file_from_buffer(
|
||||
&mut self,
|
||||
buffer_model: Model<Buffer>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
buffer_model: Entity<Buffer>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let (buffer_info, text_task) = this.update(&mut cx, |_, cx| {
|
||||
|
@ -153,13 +150,13 @@ impl ContextStore {
|
|||
let id = self.next_context_id.post_inc();
|
||||
self.files.insert(context_buffer.id, id);
|
||||
self.context
|
||||
.push(Context::File(FileContext { id, context_buffer }));
|
||||
.push(AssistantContext::File(FileContext { id, context_buffer }));
|
||||
}
|
||||
|
||||
pub fn add_directory(
|
||||
&mut self,
|
||||
project_path: ProjectPath,
|
||||
cx: &mut ModelContext<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let workspace = self.workspace.clone();
|
||||
let Some(project) = workspace
|
||||
|
@ -244,14 +241,15 @@ impl ContextStore {
|
|||
let id = self.next_context_id.post_inc();
|
||||
self.directories.insert(path.to_path_buf(), id);
|
||||
|
||||
self.context.push(Context::Directory(DirectoryContext::new(
|
||||
id,
|
||||
path,
|
||||
context_buffers,
|
||||
)));
|
||||
self.context
|
||||
.push(AssistantContext::Directory(DirectoryContext::new(
|
||||
id,
|
||||
path,
|
||||
context_buffers,
|
||||
)));
|
||||
}
|
||||
|
||||
pub fn add_thread(&mut self, thread: Model<Thread>, cx: &mut ModelContext<Self>) {
|
||||
pub fn add_thread(&mut self, thread: Entity<Thread>, cx: &mut Context<Self>) {
|
||||
if let Some(context_id) = self.includes_thread(&thread.read(cx).id()) {
|
||||
self.remove_context(context_id);
|
||||
} else {
|
||||
|
@ -259,13 +257,13 @@ impl ContextStore {
|
|||
}
|
||||
}
|
||||
|
||||
fn insert_thread(&mut self, thread: Model<Thread>, cx: &AppContext) {
|
||||
fn insert_thread(&mut self, thread: Entity<Thread>, cx: &App) {
|
||||
let id = self.next_context_id.post_inc();
|
||||
let text = thread.read(cx).text().into();
|
||||
|
||||
self.threads.insert(thread.read(cx).id().clone(), id);
|
||||
self.context
|
||||
.push(Context::Thread(ThreadContext { id, thread, text }));
|
||||
.push(AssistantContext::Thread(ThreadContext { id, thread, text }));
|
||||
}
|
||||
|
||||
pub fn add_fetched_url(&mut self, url: String, text: impl Into<SharedString>) {
|
||||
|
@ -278,17 +276,18 @@ impl ContextStore {
|
|||
let id = self.next_context_id.post_inc();
|
||||
|
||||
self.fetched_urls.insert(url.clone(), id);
|
||||
self.context.push(Context::FetchedUrl(FetchedUrlContext {
|
||||
id,
|
||||
url: url.into(),
|
||||
text: text.into(),
|
||||
}));
|
||||
self.context
|
||||
.push(AssistantContext::FetchedUrl(FetchedUrlContext {
|
||||
id,
|
||||
url: url.into(),
|
||||
text: text.into(),
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn accept_suggested_context(
|
||||
&mut self,
|
||||
suggested: &SuggestedContext,
|
||||
cx: &mut ModelContext<ContextStore>,
|
||||
cx: &mut Context<ContextStore>,
|
||||
) -> Task<Result<()>> {
|
||||
match suggested {
|
||||
SuggestedContext::File {
|
||||
|
@ -315,16 +314,16 @@ impl ContextStore {
|
|||
};
|
||||
|
||||
match self.context.remove(ix) {
|
||||
Context::File(_) => {
|
||||
AssistantContext::File(_) => {
|
||||
self.files.retain(|_, context_id| *context_id != id);
|
||||
}
|
||||
Context::Directory(_) => {
|
||||
AssistantContext::Directory(_) => {
|
||||
self.directories.retain(|_, context_id| *context_id != id);
|
||||
}
|
||||
Context::FetchedUrl(_) => {
|
||||
AssistantContext::FetchedUrl(_) => {
|
||||
self.fetched_urls.retain(|_, context_id| *context_id != id);
|
||||
}
|
||||
Context::Thread(_) => {
|
||||
AssistantContext::Thread(_) => {
|
||||
self.threads.retain(|_, context_id| *context_id != id);
|
||||
}
|
||||
}
|
||||
|
@ -343,10 +342,10 @@ impl ContextStore {
|
|||
|
||||
/// Returns whether this file path is already included directly in the context, or if it will be
|
||||
/// included in the context via a directory.
|
||||
pub fn will_include_file_path(&self, path: &Path, cx: &AppContext) -> Option<FileInclusion> {
|
||||
pub fn will_include_file_path(&self, path: &Path, cx: &App) -> Option<FileInclusion> {
|
||||
if !self.files.is_empty() {
|
||||
let found_file_context = self.context.iter().find(|context| match &context {
|
||||
Context::File(file_context) => {
|
||||
AssistantContext::File(file_context) => {
|
||||
let buffer = file_context.context_buffer.buffer.read(cx);
|
||||
if let Some(file_path) = buffer_path_log_err(buffer) {
|
||||
*file_path == *path
|
||||
|
@ -393,7 +392,7 @@ impl ContextStore {
|
|||
}
|
||||
|
||||
/// Replaces the context that matches the ID of the new context, if any match.
|
||||
fn replace_context(&mut self, new_context: Context) {
|
||||
fn replace_context(&mut self, new_context: AssistantContext) {
|
||||
let id = new_context.id();
|
||||
for context in self.context.iter_mut() {
|
||||
if context.id() == id {
|
||||
|
@ -403,15 +402,17 @@ impl ContextStore {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn file_paths(&self, cx: &AppContext) -> HashSet<PathBuf> {
|
||||
pub fn file_paths(&self, cx: &App) -> HashSet<PathBuf> {
|
||||
self.context
|
||||
.iter()
|
||||
.filter_map(|context| match context {
|
||||
Context::File(file) => {
|
||||
AssistantContext::File(file) => {
|
||||
let buffer = file.context_buffer.buffer.read(cx);
|
||||
buffer_path_log_err(buffer).map(|p| p.to_path_buf())
|
||||
}
|
||||
Context::Directory(_) | Context::FetchedUrl(_) | Context::Thread(_) => None,
|
||||
AssistantContext::Directory(_)
|
||||
| AssistantContext::FetchedUrl(_)
|
||||
| AssistantContext::Thread(_) => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
@ -428,7 +429,7 @@ pub enum FileInclusion {
|
|||
|
||||
// ContextBuffer without text.
|
||||
struct BufferInfo {
|
||||
buffer_model: Model<Buffer>,
|
||||
buffer_model: Entity<Buffer>,
|
||||
id: BufferId,
|
||||
version: clock::Global,
|
||||
}
|
||||
|
@ -444,7 +445,7 @@ fn make_context_buffer(info: BufferInfo, text: SharedString) -> ContextBuffer {
|
|||
|
||||
fn collect_buffer_info_and_text(
|
||||
path: Arc<Path>,
|
||||
buffer_model: Model<Buffer>,
|
||||
buffer_model: Entity<Buffer>,
|
||||
buffer: &Buffer,
|
||||
cx: AsyncAppContext,
|
||||
) -> (BufferInfo, Task<SharedString>) {
|
||||
|
@ -525,32 +526,32 @@ fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<Arc<Path>> {
|
|||
}
|
||||
|
||||
pub fn refresh_context_store_text(
|
||||
context_store: Model<ContextStore>,
|
||||
cx: &AppContext,
|
||||
context_store: Entity<ContextStore>,
|
||||
cx: &App,
|
||||
) -> impl Future<Output = ()> {
|
||||
let mut tasks = Vec::new();
|
||||
for context in &context_store.read(cx).context {
|
||||
match context {
|
||||
Context::File(file_context) => {
|
||||
AssistantContext::File(file_context) => {
|
||||
let context_store = context_store.clone();
|
||||
if let Some(task) = refresh_file_text(context_store, file_context, cx) {
|
||||
tasks.push(task);
|
||||
}
|
||||
}
|
||||
Context::Directory(directory_context) => {
|
||||
AssistantContext::Directory(directory_context) => {
|
||||
let context_store = context_store.clone();
|
||||
if let Some(task) = refresh_directory_text(context_store, directory_context, cx) {
|
||||
tasks.push(task);
|
||||
}
|
||||
}
|
||||
Context::Thread(thread_context) => {
|
||||
AssistantContext::Thread(thread_context) => {
|
||||
let context_store = context_store.clone();
|
||||
tasks.push(refresh_thread_text(context_store, thread_context, cx));
|
||||
}
|
||||
// Intentionally omit refreshing fetched URLs as it doesn't seem all that useful,
|
||||
// and doing the caching properly could be tricky (unless it's already handled by
|
||||
// the HttpClient?).
|
||||
Context::FetchedUrl(_) => {}
|
||||
AssistantContext::FetchedUrl(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -558,9 +559,9 @@ pub fn refresh_context_store_text(
|
|||
}
|
||||
|
||||
fn refresh_file_text(
|
||||
context_store: Model<ContextStore>,
|
||||
context_store: Entity<ContextStore>,
|
||||
file_context: &FileContext,
|
||||
cx: &AppContext,
|
||||
cx: &App,
|
||||
) -> Option<Task<()>> {
|
||||
let id = file_context.id;
|
||||
let task = refresh_context_buffer(&file_context.context_buffer, cx);
|
||||
|
@ -570,7 +571,7 @@ fn refresh_file_text(
|
|||
context_store
|
||||
.update(&mut cx, |context_store, _| {
|
||||
let new_file_context = FileContext { id, context_buffer };
|
||||
context_store.replace_context(Context::File(new_file_context));
|
||||
context_store.replace_context(AssistantContext::File(new_file_context));
|
||||
})
|
||||
.ok();
|
||||
}))
|
||||
|
@ -580,9 +581,9 @@ fn refresh_file_text(
|
|||
}
|
||||
|
||||
fn refresh_directory_text(
|
||||
context_store: Model<ContextStore>,
|
||||
context_store: Entity<ContextStore>,
|
||||
directory_context: &DirectoryContext,
|
||||
cx: &AppContext,
|
||||
cx: &App,
|
||||
) -> Option<Task<()>> {
|
||||
let mut stale = false;
|
||||
let futures = directory_context
|
||||
|
@ -611,16 +612,16 @@ fn refresh_directory_text(
|
|||
context_store
|
||||
.update(&mut cx, |context_store, _| {
|
||||
let new_directory_context = DirectoryContext::new(id, &path, context_buffers);
|
||||
context_store.replace_context(Context::Directory(new_directory_context));
|
||||
context_store.replace_context(AssistantContext::Directory(new_directory_context));
|
||||
})
|
||||
.ok();
|
||||
}))
|
||||
}
|
||||
|
||||
fn refresh_thread_text(
|
||||
context_store: Model<ContextStore>,
|
||||
context_store: Entity<ContextStore>,
|
||||
thread_context: &ThreadContext,
|
||||
cx: &AppContext,
|
||||
cx: &App,
|
||||
) -> Task<()> {
|
||||
let id = thread_context.id;
|
||||
let thread = thread_context.thread.clone();
|
||||
|
@ -628,7 +629,11 @@ fn refresh_thread_text(
|
|||
context_store
|
||||
.update(&mut cx, |context_store, cx| {
|
||||
let text = thread.read(cx).text().into();
|
||||
context_store.replace_context(Context::Thread(ThreadContext { id, thread, text }));
|
||||
context_store.replace_context(AssistantContext::Thread(ThreadContext {
|
||||
id,
|
||||
thread,
|
||||
text,
|
||||
}));
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
|
@ -636,7 +641,7 @@ fn refresh_thread_text(
|
|||
|
||||
fn refresh_context_buffer(
|
||||
context_buffer: &ContextBuffer,
|
||||
cx: &AppContext,
|
||||
cx: &App,
|
||||
) -> Option<impl Future<Output = ContextBuffer>> {
|
||||
let buffer = context_buffer.buffer.read(cx);
|
||||
let path = buffer_path_log_err(buffer)?;
|
||||
|
|
|
@ -4,8 +4,8 @@ use collections::HashSet;
|
|||
use editor::Editor;
|
||||
use file_icons::FileIcons;
|
||||
use gpui::{
|
||||
AppContext, Bounds, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
|
||||
Subscription, View, WeakModel, WeakView,
|
||||
App, Bounds, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription,
|
||||
WeakEntity,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::Buffer;
|
||||
|
@ -24,34 +24,37 @@ use crate::{
|
|||
};
|
||||
|
||||
pub struct ContextStrip {
|
||||
context_store: Model<ContextStore>,
|
||||
pub context_picker: View<ContextPicker>,
|
||||
context_store: Entity<ContextStore>,
|
||||
pub context_picker: Entity<ContextPicker>,
|
||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
focus_handle: FocusHandle,
|
||||
suggest_context_kind: SuggestContextKind,
|
||||
workspace: WeakView<Workspace>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
focused_index: Option<usize>,
|
||||
children_bounds: Option<Vec<Bounds<Pixels>>>,
|
||||
}
|
||||
|
||||
impl ContextStrip {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
context_store: Model<ContextStore>,
|
||||
workspace: WeakView<Workspace>,
|
||||
editor: WeakView<Editor>,
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
context_store: Entity<ContextStore>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
editor: WeakEntity<Editor>,
|
||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
suggest_context_kind: SuggestContextKind,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let context_picker = cx.new_view(|cx| {
|
||||
let context_picker = cx.new(|cx| {
|
||||
ContextPicker::new(
|
||||
workspace.clone(),
|
||||
thread_store.clone(),
|
||||
context_store.downgrade(),
|
||||
editor.clone(),
|
||||
ConfirmBehavior::KeepOpen,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
@ -59,9 +62,9 @@ impl ContextStrip {
|
|||
let focus_handle = cx.focus_handle();
|
||||
|
||||
let subscriptions = vec![
|
||||
cx.subscribe(&context_picker, Self::handle_context_picker_event),
|
||||
cx.on_focus(&focus_handle, Self::handle_focus),
|
||||
cx.on_blur(&focus_handle, Self::handle_blur),
|
||||
cx.subscribe_in(&context_picker, window, Self::handle_context_picker_event),
|
||||
cx.on_focus(&focus_handle, window, Self::handle_focus),
|
||||
cx.on_blur(&focus_handle, window, Self::handle_blur),
|
||||
];
|
||||
|
||||
Self {
|
||||
|
@ -77,14 +80,14 @@ impl ContextStrip {
|
|||
}
|
||||
}
|
||||
|
||||
fn suggested_context(&self, cx: &ViewContext<Self>) -> Option<SuggestedContext> {
|
||||
fn suggested_context(&self, cx: &Context<Self>) -> Option<SuggestedContext> {
|
||||
match self.suggest_context_kind {
|
||||
SuggestContextKind::File => self.suggested_file(cx),
|
||||
SuggestContextKind::Thread => self.suggested_thread(cx),
|
||||
}
|
||||
}
|
||||
|
||||
fn suggested_file(&self, cx: &ViewContext<Self>) -> Option<SuggestedContext> {
|
||||
fn suggested_file(&self, cx: &Context<Self>) -> Option<SuggestedContext> {
|
||||
let workspace = self.workspace.upgrade()?;
|
||||
let active_item = workspace.read(cx).active_item(cx)?;
|
||||
|
||||
|
@ -117,7 +120,7 @@ impl ContextStrip {
|
|||
})
|
||||
}
|
||||
|
||||
fn suggested_thread(&self, cx: &ViewContext<Self>) -> Option<SuggestedContext> {
|
||||
fn suggested_thread(&self, cx: &Context<Self>) -> Option<SuggestedContext> {
|
||||
if !self.context_picker.read(cx).allow_threads() {
|
||||
return None;
|
||||
}
|
||||
|
@ -149,24 +152,25 @@ impl ContextStrip {
|
|||
|
||||
fn handle_context_picker_event(
|
||||
&mut self,
|
||||
_picker: View<ContextPicker>,
|
||||
_picker: &Entity<ContextPicker>,
|
||||
_event: &DismissEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
cx.emit(ContextStripEvent::PickerDismissed);
|
||||
}
|
||||
|
||||
fn handle_focus(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn handle_focus(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.focused_index = self.last_pill_index();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn handle_blur(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn handle_blur(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.focused_index = None;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn focus_left(&mut self, _: &FocusLeft, cx: &mut ViewContext<Self>) {
|
||||
fn focus_left(&mut self, _: &FocusLeft, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.focused_index = match self.focused_index {
|
||||
Some(index) if index > 0 => Some(index - 1),
|
||||
_ => self.last_pill_index(),
|
||||
|
@ -175,7 +179,7 @@ impl ContextStrip {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
fn focus_right(&mut self, _: &FocusRight, cx: &mut ViewContext<Self>) {
|
||||
fn focus_right(&mut self, _: &FocusRight, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(last_index) = self.last_pill_index() else {
|
||||
return;
|
||||
};
|
||||
|
@ -188,7 +192,7 @@ impl ContextStrip {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
fn focus_up(&mut self, _: &FocusUp, cx: &mut ViewContext<Self>) {
|
||||
fn focus_up(&mut self, _: &FocusUp, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(focused_index) = self.focused_index else {
|
||||
return;
|
||||
};
|
||||
|
@ -206,7 +210,7 @@ impl ContextStrip {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
fn focus_down(&mut self, _: &FocusDown, cx: &mut ViewContext<Self>) {
|
||||
fn focus_down(&mut self, _: &FocusDown, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(focused_index) = self.focused_index else {
|
||||
return;
|
||||
};
|
||||
|
@ -276,7 +280,12 @@ impl ContextStrip {
|
|||
best.map(|(index, _, _)| index)
|
||||
}
|
||||
|
||||
fn remove_focused_context(&mut self, _: &RemoveFocusedContext, cx: &mut ViewContext<Self>) {
|
||||
fn remove_focused_context(
|
||||
&mut self,
|
||||
_: &RemoveFocusedContext,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(index) = self.focused_index {
|
||||
let mut is_empty = false;
|
||||
|
||||
|
@ -302,22 +311,32 @@ impl ContextStrip {
|
|||
self.focused_index == Some(context.len())
|
||||
}
|
||||
|
||||
fn accept_suggested_context(&mut self, _: &AcceptSuggestedContext, cx: &mut ViewContext<Self>) {
|
||||
fn accept_suggested_context(
|
||||
&mut self,
|
||||
_: &AcceptSuggestedContext,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(suggested) = self.suggested_context(cx) {
|
||||
let context_store = self.context_store.read(cx);
|
||||
|
||||
if self.is_suggested_focused(context_store.context()) {
|
||||
self.add_suggested_context(&suggested, cx);
|
||||
self.add_suggested_context(&suggested, window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_suggested_context(&mut self, suggested: &SuggestedContext, cx: &mut ViewContext<Self>) {
|
||||
fn add_suggested_context(
|
||||
&mut self,
|
||||
suggested: &SuggestedContext,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let task = self.context_store.update(cx, |context_store, cx| {
|
||||
context_store.accept_suggested_context(&suggested, cx)
|
||||
});
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
match task.await.notify_async_err(&mut cx) {
|
||||
None => {}
|
||||
Some(()) => {
|
||||
|
@ -334,14 +353,14 @@ impl ContextStrip {
|
|||
}
|
||||
}
|
||||
|
||||
impl FocusableView for ContextStrip {
|
||||
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
||||
impl Focusable for ContextStrip {
|
||||
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ContextStrip {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let context_store = self.context_store.read(cx);
|
||||
let context = context_store
|
||||
.context()
|
||||
|
@ -374,19 +393,20 @@ impl Render for ContextStrip {
|
|||
.on_action(cx.listener(Self::remove_focused_context))
|
||||
.on_action(cx.listener(Self::accept_suggested_context))
|
||||
.on_children_prepainted({
|
||||
let view = cx.view().downgrade();
|
||||
move |children_bounds, cx| {
|
||||
view.update(cx, |this, _| {
|
||||
this.children_bounds = Some(children_bounds);
|
||||
})
|
||||
.ok();
|
||||
let model = cx.model().downgrade();
|
||||
move |children_bounds, _window, cx| {
|
||||
model
|
||||
.update(cx, |this, _| {
|
||||
this.children_bounds = Some(children_bounds);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.child(
|
||||
PopoverMenu::new("context-picker")
|
||||
.menu(move |cx| {
|
||||
.menu(move |window, cx| {
|
||||
context_picker.update(cx, |this, cx| {
|
||||
this.init(cx);
|
||||
this.init(window, cx);
|
||||
});
|
||||
|
||||
Some(context_picker.clone())
|
||||
|
@ -397,12 +417,12 @@ impl Render for ContextStrip {
|
|||
.style(ui::ButtonStyle::Filled)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
|
||||
move |cx| {
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Add Context",
|
||||
&ToggleContextPicker,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
@ -429,8 +449,12 @@ impl Render for ContextStrip {
|
|||
)
|
||||
.opacity(0.5)
|
||||
.children(
|
||||
KeyBinding::for_action_in(&ToggleContextPicker, &focus_handle, cx)
|
||||
.map(|binding| binding.into_any_element()),
|
||||
KeyBinding::for_action_in(
|
||||
&ToggleContextPicker,
|
||||
&focus_handle,
|
||||
window,
|
||||
)
|
||||
.map(|binding| binding.into_any_element()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -443,7 +467,7 @@ impl Render for ContextStrip {
|
|||
Some({
|
||||
let id = context.id;
|
||||
let context_store = self.context_store.clone();
|
||||
Rc::new(cx.listener(move |_this, _event, cx| {
|
||||
Rc::new(cx.listener(move |_this, _event, _window, cx| {
|
||||
context_store.update(cx, |this, _cx| {
|
||||
this.remove_context(id);
|
||||
});
|
||||
|
@ -451,7 +475,7 @@ impl Render for ContextStrip {
|
|||
}))
|
||||
}),
|
||||
)
|
||||
.on_click(Rc::new(cx.listener(move |this, _, cx| {
|
||||
.on_click(Rc::new(cx.listener(move |this, _, _window, cx| {
|
||||
this.focused_index = Some(i);
|
||||
cx.notify();
|
||||
})))
|
||||
|
@ -464,9 +488,11 @@ impl Render for ContextStrip {
|
|||
suggested.kind(),
|
||||
self.is_suggested_focused(&context),
|
||||
)
|
||||
.on_click(Rc::new(cx.listener(move |this, _event, cx| {
|
||||
this.add_suggested_context(&suggested, cx);
|
||||
}))),
|
||||
.on_click(Rc::new(cx.listener(
|
||||
move |this, _event, window, cx| {
|
||||
this.add_suggested_context(&suggested, window, cx);
|
||||
},
|
||||
))),
|
||||
)
|
||||
})
|
||||
.when(!context.is_empty(), {
|
||||
|
@ -476,19 +502,20 @@ impl Render for ContextStrip {
|
|||
.icon_size(IconSize::Small)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |cx| {
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Remove All Context",
|
||||
&RemoveAllContext,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(cx.listener({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |_this, _event, cx| {
|
||||
focus_handle.dispatch_action(&RemoveAllContext, cx);
|
||||
move |_this, _event, window, cx| {
|
||||
focus_handle.dispatch_action(&RemoveAllContext, window, cx);
|
||||
}
|
||||
})),
|
||||
)
|
||||
|
@ -516,11 +543,11 @@ pub enum SuggestedContext {
|
|||
File {
|
||||
name: SharedString,
|
||||
icon_path: Option<SharedString>,
|
||||
buffer: WeakModel<Buffer>,
|
||||
buffer: WeakEntity<Buffer>,
|
||||
},
|
||||
Thread {
|
||||
name: SharedString,
|
||||
thread: WeakModel<Thread>,
|
||||
thread: WeakEntity<Thread>,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -16,9 +16,8 @@ use editor::{
|
|||
use feature_flags::{FeatureFlagAppExt as _, ZedPro};
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
anchored, deferred, point, AnyElement, AppContext, ClickEvent, CursorStyle, EventEmitter,
|
||||
FocusHandle, FocusableView, FontWeight, Model, Subscription, TextStyle, View, ViewContext,
|
||||
WeakModel, WeakView, WindowContext,
|
||||
anchored, deferred, point, AnyElement, App, ClickEvent, Context, CursorStyle, Entity,
|
||||
EventEmitter, FocusHandle, Focusable, FontWeight, Subscription, TextStyle, WeakEntity, Window,
|
||||
};
|
||||
use language_model::{LanguageModel, LanguageModelRegistry};
|
||||
use language_model_selector::LanguageModelSelector;
|
||||
|
@ -35,12 +34,12 @@ use util::ResultExt;
|
|||
use workspace::Workspace;
|
||||
|
||||
pub struct PromptEditor<T> {
|
||||
pub editor: View<Editor>,
|
||||
pub editor: Entity<Editor>,
|
||||
mode: PromptEditorMode,
|
||||
context_store: Model<ContextStore>,
|
||||
context_strip: View<ContextStrip>,
|
||||
context_store: Entity<ContextStore>,
|
||||
context_strip: Entity<ContextStrip>,
|
||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
model_selector: View<AssistantModelSelector>,
|
||||
model_selector: Entity<AssistantModelSelector>,
|
||||
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
edited_since_done: bool,
|
||||
prompt_history: VecDeque<String>,
|
||||
|
@ -56,7 +55,7 @@ pub struct PromptEditor<T> {
|
|||
impl<T: 'static> EventEmitter<PromptEditorEvent> for PromptEditor<T> {}
|
||||
|
||||
impl<T: 'static> Render for PromptEditor<T> {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
|
||||
let mut buttons = Vec::new();
|
||||
|
||||
|
@ -87,7 +86,7 @@ impl<T: 'static> Render for PromptEditor<T> {
|
|||
PromptEditorMode::Terminal { .. } => Pixels::from(8.0),
|
||||
};
|
||||
|
||||
buttons.extend(self.render_buttons(cx));
|
||||
buttons.extend(self.render_buttons(window, cx));
|
||||
|
||||
v_flex()
|
||||
.key_context("PromptEditor")
|
||||
|
@ -163,9 +162,7 @@ impl<T: 'static> Render for PromptEditor<T> {
|
|||
el.child(
|
||||
div()
|
||||
.id("error")
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(error_message.clone(), cx)
|
||||
})
|
||||
.tooltip(Tooltip::text(error_message))
|
||||
.child(
|
||||
Icon::new(IconName::XCircle)
|
||||
.size(IconSize::Small)
|
||||
|
@ -179,7 +176,7 @@ impl<T: 'static> Render for PromptEditor<T> {
|
|||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.child(div().flex_1().child(self.render_editor(cx)))
|
||||
.child(div().flex_1().child(self.render_editor(window, cx)))
|
||||
.child(
|
||||
WithRemSize::new(ui_font_size)
|
||||
.flex()
|
||||
|
@ -209,8 +206,8 @@ impl<T: 'static> Render for PromptEditor<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> FocusableView for PromptEditor<T> {
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
impl<T: 'static> Focusable for PromptEditor<T> {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
self.editor.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
@ -218,47 +215,50 @@ impl<T: 'static> FocusableView for PromptEditor<T> {
|
|||
impl<T: 'static> PromptEditor<T> {
|
||||
const MAX_LINES: u8 = 8;
|
||||
|
||||
fn codegen_status<'a>(&'a self, cx: &'a AppContext) -> &'a CodegenStatus {
|
||||
fn codegen_status<'a>(&'a self, cx: &'a App) -> &'a CodegenStatus {
|
||||
match &self.mode {
|
||||
PromptEditorMode::Buffer { codegen, .. } => codegen.read(cx).status(cx),
|
||||
PromptEditorMode::Terminal { codegen, .. } => &codegen.read(cx).status,
|
||||
}
|
||||
}
|
||||
|
||||
fn subscribe_to_editor(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn subscribe_to_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.editor_subscriptions.clear();
|
||||
self.editor_subscriptions
|
||||
.push(cx.subscribe(&self.editor, Self::handle_prompt_editor_events));
|
||||
self.editor_subscriptions.push(cx.subscribe_in(
|
||||
&self.editor,
|
||||
window,
|
||||
Self::handle_prompt_editor_events,
|
||||
));
|
||||
}
|
||||
|
||||
pub fn set_show_cursor_when_unfocused(
|
||||
&mut self,
|
||||
show_cursor_when_unfocused: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.set_show_cursor_when_unfocused(show_cursor_when_unfocused, cx)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn unlink(&mut self, cx: &mut ViewContext<Self>) {
|
||||
pub fn unlink(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let prompt = self.prompt(cx);
|
||||
let focus = self.editor.focus_handle(cx).contains_focused(cx);
|
||||
self.editor = cx.new_view(|cx| {
|
||||
let mut editor = Editor::auto_height(Self::MAX_LINES as usize, cx);
|
||||
let focus = self.editor.focus_handle(cx).contains_focused(window, cx);
|
||||
self.editor = cx.new(|cx| {
|
||||
let mut editor = Editor::auto_height(Self::MAX_LINES as usize, window, cx);
|
||||
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
|
||||
editor.set_placeholder_text(Self::placeholder_text(&self.mode, cx), cx);
|
||||
editor.set_placeholder_text(Self::placeholder_text(&self.mode, window, cx), cx);
|
||||
editor.set_placeholder_text("Add a prompt…", cx);
|
||||
editor.set_text(prompt, cx);
|
||||
editor.set_text(prompt, window, cx);
|
||||
if focus {
|
||||
editor.focus(cx);
|
||||
window.focus(&editor.focus_handle(cx));
|
||||
}
|
||||
editor
|
||||
});
|
||||
self.subscribe_to_editor(cx);
|
||||
self.subscribe_to_editor(window, cx);
|
||||
}
|
||||
|
||||
pub fn placeholder_text(mode: &PromptEditorMode, cx: &WindowContext) -> String {
|
||||
pub fn placeholder_text(mode: &PromptEditorMode, window: &mut Window, cx: &mut App) -> String {
|
||||
let action = match mode {
|
||||
PromptEditorMode::Buffer { codegen, .. } => {
|
||||
if codegen.read(cx).is_insertion {
|
||||
|
@ -271,36 +271,42 @@ impl<T: 'static> PromptEditor<T> {
|
|||
};
|
||||
|
||||
let assistant_panel_keybinding =
|
||||
ui::text_for_action(&zed_actions::assistant::ToggleFocus, cx)
|
||||
ui::text_for_action(&zed_actions::assistant::ToggleFocus, window)
|
||||
.map(|keybinding| format!("{keybinding} to chat ― "))
|
||||
.unwrap_or_default();
|
||||
|
||||
format!("{action}… ({assistant_panel_keybinding}↓↑ for history)")
|
||||
}
|
||||
|
||||
pub fn prompt(&self, cx: &AppContext) -> String {
|
||||
pub fn prompt(&self, cx: &App) -> String {
|
||||
self.editor.read(cx).text(cx)
|
||||
}
|
||||
|
||||
fn toggle_rate_limit_notice(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
|
||||
fn toggle_rate_limit_notice(
|
||||
&mut self,
|
||||
_: &ClickEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.show_rate_limit_notice = !self.show_rate_limit_notice;
|
||||
if self.show_rate_limit_notice {
|
||||
cx.focus_view(&self.editor);
|
||||
window.focus(&self.editor.focus_handle(cx));
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn handle_prompt_editor_events(
|
||||
&mut self,
|
||||
_: View<Editor>,
|
||||
_: &Entity<Editor>,
|
||||
event: &EditorEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match event {
|
||||
EditorEvent::Edited { .. } => {
|
||||
if let Some(workspace) = cx.window_handle().downcast::<Workspace>() {
|
||||
if let Some(workspace) = window.window_handle().downcast::<Workspace>() {
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
.update(cx, |workspace, _, cx| {
|
||||
let is_via_ssh = workspace
|
||||
.project()
|
||||
.update(cx, |project, _| project.is_via_ssh());
|
||||
|
@ -334,20 +340,40 @@ impl<T: 'static> PromptEditor<T> {
|
|||
}
|
||||
}
|
||||
|
||||
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
|
||||
self.context_picker_menu_handle.toggle(cx);
|
||||
fn toggle_context_picker(
|
||||
&mut self,
|
||||
_: &ToggleContextPicker,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.context_picker_menu_handle.toggle(window, cx);
|
||||
}
|
||||
|
||||
fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
|
||||
self.model_selector_menu_handle.toggle(cx);
|
||||
fn toggle_model_selector(
|
||||
&mut self,
|
||||
_: &ToggleModelSelector,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.model_selector_menu_handle.toggle(window, cx);
|
||||
}
|
||||
|
||||
pub fn remove_all_context(&mut self, _: &RemoveAllContext, cx: &mut ViewContext<Self>) {
|
||||
pub fn remove_all_context(
|
||||
&mut self,
|
||||
_: &RemoveAllContext,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.context_store.update(cx, |store, _cx| store.clear());
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
|
||||
fn cancel(
|
||||
&mut self,
|
||||
_: &editor::actions::Cancel,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match self.codegen_status(cx) {
|
||||
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
|
||||
cx.emit(PromptEditorEvent::CancelRequested);
|
||||
|
@ -358,7 +384,7 @@ impl<T: 'static> PromptEditor<T> {
|
|||
}
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
|
||||
fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
match self.codegen_status(cx) {
|
||||
CodegenStatus::Idle => {
|
||||
cx.emit(PromptEditorEvent::StartRequested);
|
||||
|
@ -379,49 +405,49 @@ impl<T: 'static> PromptEditor<T> {
|
|||
}
|
||||
}
|
||||
|
||||
fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
|
||||
fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some(ix) = self.prompt_history_ix {
|
||||
if ix > 0 {
|
||||
self.prompt_history_ix = Some(ix - 1);
|
||||
let prompt = self.prompt_history[ix - 1].as_str();
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.set_text(prompt, cx);
|
||||
editor.move_to_beginning(&Default::default(), cx);
|
||||
editor.set_text(prompt, window, cx);
|
||||
editor.move_to_beginning(&Default::default(), window, cx);
|
||||
});
|
||||
}
|
||||
} else if !self.prompt_history.is_empty() {
|
||||
self.prompt_history_ix = Some(self.prompt_history.len() - 1);
|
||||
let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str();
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.set_text(prompt, cx);
|
||||
editor.move_to_beginning(&Default::default(), cx);
|
||||
editor.set_text(prompt, window, cx);
|
||||
editor.move_to_beginning(&Default::default(), window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
|
||||
fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some(ix) = self.prompt_history_ix {
|
||||
if ix < self.prompt_history.len() - 1 {
|
||||
self.prompt_history_ix = Some(ix + 1);
|
||||
let prompt = self.prompt_history[ix + 1].as_str();
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.set_text(prompt, cx);
|
||||
editor.move_to_end(&Default::default(), cx)
|
||||
editor.set_text(prompt, window, cx);
|
||||
editor.move_to_end(&Default::default(), window, cx)
|
||||
});
|
||||
} else {
|
||||
self.prompt_history_ix = None;
|
||||
let prompt = self.pending_prompt.as_str();
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.set_text(prompt, cx);
|
||||
editor.move_to_end(&Default::default(), cx)
|
||||
editor.set_text(prompt, window, cx);
|
||||
editor.move_to_end(&Default::default(), window, cx)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
cx.focus_view(&self.context_strip);
|
||||
self.context_strip.focus_handle(cx).focus(window);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_buttons(&self, cx: &mut ViewContext<Self>) -> Vec<AnyElement> {
|
||||
fn render_buttons(&self, _window: &mut Window, cx: &mut Context<Self>) -> Vec<AnyElement> {
|
||||
let mode = match &self.mode {
|
||||
PromptEditorMode::Buffer { codegen, .. } => {
|
||||
let codegen = codegen.read(cx);
|
||||
|
@ -443,21 +469,22 @@ impl<T: 'static> PromptEditor<T> {
|
|||
.icon(IconName::Return)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Muted)
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StartRequested)))
|
||||
.on_click(cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StartRequested)))
|
||||
.into_any_element()]
|
||||
}
|
||||
CodegenStatus::Pending => vec![IconButton::new("stop", IconName::Stop)
|
||||
.icon_color(Color::Error)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |cx| {
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
mode.tooltip_interrupt(),
|
||||
Some(&menu::Cancel),
|
||||
"Changes won't be discarded",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
|
||||
.on_click(cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
|
||||
.into_any_element()],
|
||||
CodegenStatus::Done | CodegenStatus::Error(_) => {
|
||||
let has_error = matches!(codegen_status, CodegenStatus::Error(_));
|
||||
|
@ -465,15 +492,16 @@ impl<T: 'static> PromptEditor<T> {
|
|||
vec![IconButton::new("restart", IconName::RotateCw)
|
||||
.icon_color(Color::Info)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |cx| {
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
mode.tooltip_restart(),
|
||||
Some(&menu::Confirm),
|
||||
"Changes will be discarded",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(|_, _, cx| {
|
||||
.on_click(cx.listener(|_, _, _, cx| {
|
||||
cx.emit(PromptEditorEvent::StartRequested);
|
||||
}))
|
||||
.into_any_element()]
|
||||
|
@ -481,10 +509,10 @@ impl<T: 'static> PromptEditor<T> {
|
|||
let accept = IconButton::new("accept", IconName::Check)
|
||||
.icon_color(Color::Info)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, cx)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, window, cx)
|
||||
})
|
||||
.on_click(cx.listener(|_, _, cx| {
|
||||
.on_click(cx.listener(|_, _, _, cx| {
|
||||
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
|
||||
}))
|
||||
.into_any_element();
|
||||
|
@ -495,14 +523,15 @@ impl<T: 'static> PromptEditor<T> {
|
|||
IconButton::new("confirm", IconName::Play)
|
||||
.icon_color(Color::Info)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(|cx| {
|
||||
.tooltip(|window, cx| {
|
||||
Tooltip::for_action(
|
||||
"Execute Generated Command",
|
||||
&menu::SecondaryConfirm,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(|_, _, cx| {
|
||||
.on_click(cx.listener(|_, _, _, cx| {
|
||||
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
|
||||
}))
|
||||
.into_any_element(),
|
||||
|
@ -514,7 +543,12 @@ impl<T: 'static> PromptEditor<T> {
|
|||
}
|
||||
}
|
||||
|
||||
fn cycle_prev(&mut self, _: &CyclePreviousInlineAssist, cx: &mut ViewContext<Self>) {
|
||||
fn cycle_prev(
|
||||
&mut self,
|
||||
_: &CyclePreviousInlineAssist,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match &self.mode {
|
||||
PromptEditorMode::Buffer { codegen, .. } => {
|
||||
codegen.update(cx, |codegen, cx| codegen.cycle_prev(cx));
|
||||
|
@ -525,7 +559,7 @@ impl<T: 'static> PromptEditor<T> {
|
|||
}
|
||||
}
|
||||
|
||||
fn cycle_next(&mut self, _: &CycleNextInlineAssist, cx: &mut ViewContext<Self>) {
|
||||
fn cycle_next(&mut self, _: &CycleNextInlineAssist, _: &mut Window, cx: &mut Context<Self>) {
|
||||
match &self.mode {
|
||||
PromptEditorMode::Buffer { codegen, .. } => {
|
||||
codegen.update(cx, |codegen, cx| codegen.cycle_next(cx));
|
||||
|
@ -536,16 +570,16 @@ impl<T: 'static> PromptEditor<T> {
|
|||
}
|
||||
}
|
||||
|
||||
fn render_close_button(&self, cx: &ViewContext<Self>) -> AnyElement {
|
||||
fn render_close_button(&self, cx: &mut Context<Self>) -> AnyElement {
|
||||
IconButton::new("cancel", IconName::Close)
|
||||
.icon_color(Color::Muted)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(|cx| Tooltip::text("Close Assistant", cx))
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
|
||||
.tooltip(Tooltip::text("Close Assistant"))
|
||||
.on_click(cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_cycle_controls(&self, codegen: &BufferCodegen, cx: &ViewContext<Self>) -> AnyElement {
|
||||
fn render_cycle_controls(&self, codegen: &BufferCodegen, cx: &Context<Self>) -> AnyElement {
|
||||
let disabled = matches!(codegen.status(cx), CodegenStatus::Idle);
|
||||
|
||||
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||
|
@ -585,13 +619,13 @@ impl<T: 'static> PromptEditor<T> {
|
|||
.shape(IconButtonShape::Square)
|
||||
.tooltip({
|
||||
let focus_handle = self.editor.focus_handle(cx);
|
||||
move |cx| {
|
||||
cx.new_view(|cx| {
|
||||
move |window, cx| {
|
||||
cx.new(|_| {
|
||||
let mut tooltip = Tooltip::new("Previous Alternative").key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&CyclePreviousInlineAssist,
|
||||
&focus_handle,
|
||||
cx,
|
||||
window,
|
||||
),
|
||||
);
|
||||
if !disabled && current_index != 0 {
|
||||
|
@ -602,8 +636,8 @@ impl<T: 'static> PromptEditor<T> {
|
|||
.into()
|
||||
}
|
||||
})
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
this.cycle_prev(&CyclePreviousInlineAssist, cx);
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.cycle_prev(&CyclePreviousInlineAssist, window, cx);
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
|
@ -626,13 +660,13 @@ impl<T: 'static> PromptEditor<T> {
|
|||
.shape(IconButtonShape::Square)
|
||||
.tooltip({
|
||||
let focus_handle = self.editor.focus_handle(cx);
|
||||
move |cx| {
|
||||
cx.new_view(|cx| {
|
||||
move |window, cx| {
|
||||
cx.new(|_| {
|
||||
let mut tooltip = Tooltip::new("Next Alternative").key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&CycleNextInlineAssist,
|
||||
&focus_handle,
|
||||
cx,
|
||||
window,
|
||||
),
|
||||
);
|
||||
if !disabled && current_index != total_models - 1 {
|
||||
|
@ -643,14 +677,14 @@ impl<T: 'static> PromptEditor<T> {
|
|||
.into()
|
||||
}
|
||||
})
|
||||
.on_click(
|
||||
cx.listener(|this, _, cx| this.cycle_next(&CycleNextInlineAssist, cx)),
|
||||
),
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.cycle_next(&CycleNextInlineAssist, window, cx)
|
||||
})),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_rate_limit_notice(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render_rate_limit_notice(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
Popover::new().child(
|
||||
v_flex()
|
||||
.occlude()
|
||||
|
@ -674,7 +708,7 @@ impl<T: 'static> PromptEditor<T> {
|
|||
} else {
|
||||
ui::ToggleState::Unselected
|
||||
},
|
||||
|selection, cx| {
|
||||
|selection, _, cx| {
|
||||
let is_dismissed = match selection {
|
||||
ui::ToggleState::Unselected => false,
|
||||
ui::ToggleState::Indeterminate => return,
|
||||
|
@ -693,10 +727,11 @@ impl<T: 'static> PromptEditor<T> {
|
|||
.on_click(cx.listener(Self::toggle_rate_limit_notice)),
|
||||
)
|
||||
.child(Button::new("more-info", "More Info").on_click(
|
||||
|_event, cx| {
|
||||
cx.dispatch_action(Box::new(
|
||||
zed_actions::OpenAccountSettings,
|
||||
))
|
||||
|_event, window, cx| {
|
||||
window.dispatch_action(
|
||||
Box::new(zed_actions::OpenAccountSettings),
|
||||
cx,
|
||||
)
|
||||
},
|
||||
)),
|
||||
),
|
||||
|
@ -704,9 +739,9 @@ impl<T: 'static> PromptEditor<T> {
|
|||
)
|
||||
}
|
||||
|
||||
fn render_editor(&mut self, cx: &mut ViewContext<Self>) -> AnyElement {
|
||||
fn render_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
|
||||
let font_size = TextSize::Default.rems(cx);
|
||||
let line_height = font_size.to_pixels(cx.rem_size()) * 1.3;
|
||||
let line_height = font_size.to_pixels(window.rem_size()) * 1.3;
|
||||
|
||||
div()
|
||||
.key_context("InlineAssistEditor")
|
||||
|
@ -740,17 +775,15 @@ impl<T: 'static> PromptEditor<T> {
|
|||
|
||||
fn handle_context_strip_event(
|
||||
&mut self,
|
||||
_context_strip: View<ContextStrip>,
|
||||
_context_strip: &Entity<ContextStrip>,
|
||||
event: &ContextStripEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match event {
|
||||
ContextStripEvent::PickerDismissed
|
||||
| ContextStripEvent::BlurredEmpty
|
||||
| ContextStripEvent::BlurredUp => {
|
||||
let editor_focus_handle = self.editor.focus_handle(cx);
|
||||
cx.focus(&editor_focus_handle);
|
||||
}
|
||||
| ContextStripEvent::BlurredUp => self.editor.focus_handle(cx).focus(window),
|
||||
ContextStripEvent::BlurredDown => {}
|
||||
}
|
||||
}
|
||||
|
@ -759,12 +792,12 @@ impl<T: 'static> PromptEditor<T> {
|
|||
pub enum PromptEditorMode {
|
||||
Buffer {
|
||||
id: InlineAssistId,
|
||||
codegen: Model<BufferCodegen>,
|
||||
codegen: Entity<BufferCodegen>,
|
||||
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
|
||||
},
|
||||
Terminal {
|
||||
id: TerminalInlineAssistId,
|
||||
codegen: Model<TerminalCodegen>,
|
||||
codegen: Entity<TerminalCodegen>,
|
||||
height_in_lines: u8,
|
||||
},
|
||||
}
|
||||
|
@ -795,13 +828,14 @@ impl PromptEditor<BufferCodegen> {
|
|||
id: InlineAssistId,
|
||||
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
|
||||
prompt_history: VecDeque<String>,
|
||||
prompt_buffer: Model<MultiBuffer>,
|
||||
codegen: Model<BufferCodegen>,
|
||||
prompt_buffer: Entity<MultiBuffer>,
|
||||
codegen: Entity<BufferCodegen>,
|
||||
fs: Arc<dyn Fs>,
|
||||
context_store: Model<ContextStore>,
|
||||
workspace: WeakView<Workspace>,
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
cx: &mut ViewContext<PromptEditor<BufferCodegen>>,
|
||||
context_store: Entity<ContextStore>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<PromptEditor<BufferCodegen>>,
|
||||
) -> PromptEditor<BufferCodegen> {
|
||||
let codegen_subscription = cx.observe(&codegen, Self::handle_codegen_changed);
|
||||
let mode = PromptEditorMode::Buffer {
|
||||
|
@ -810,7 +844,7 @@ impl PromptEditor<BufferCodegen> {
|
|||
gutter_dimensions,
|
||||
};
|
||||
|
||||
let prompt_editor = cx.new_view(|cx| {
|
||||
let prompt_editor = cx.new(|cx| {
|
||||
let mut editor = Editor::new(
|
||||
EditorMode::AutoHeight {
|
||||
max_lines: Self::MAX_LINES as usize,
|
||||
|
@ -818,6 +852,7 @@ impl PromptEditor<BufferCodegen> {
|
|||
prompt_buffer,
|
||||
None,
|
||||
false,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
|
||||
|
@ -825,13 +860,13 @@ impl PromptEditor<BufferCodegen> {
|
|||
// always show the cursor (even when it isn't focused) because
|
||||
// typing in one will make what you typed appear in all of them.
|
||||
editor.set_show_cursor_when_unfocused(true, cx);
|
||||
editor.set_placeholder_text(Self::placeholder_text(&mode, cx), cx);
|
||||
editor.set_placeholder_text(Self::placeholder_text(&mode, window, cx), cx);
|
||||
editor
|
||||
});
|
||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let model_selector_menu_handle = PopoverMenuHandle::default();
|
||||
|
||||
let context_strip = cx.new_view(|cx| {
|
||||
let context_strip = cx.new(|cx| {
|
||||
ContextStrip::new(
|
||||
context_store.clone(),
|
||||
workspace.clone(),
|
||||
|
@ -839,23 +874,25 @@ impl PromptEditor<BufferCodegen> {
|
|||
thread_store.clone(),
|
||||
context_picker_menu_handle.clone(),
|
||||
SuggestContextKind::Thread,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let context_strip_subscription =
|
||||
cx.subscribe(&context_strip, Self::handle_context_strip_event);
|
||||
cx.subscribe_in(&context_strip, window, Self::handle_context_strip_event);
|
||||
|
||||
let mut this: PromptEditor<BufferCodegen> = PromptEditor {
|
||||
editor: prompt_editor.clone(),
|
||||
context_store,
|
||||
context_strip,
|
||||
context_picker_menu_handle,
|
||||
model_selector: cx.new_view(|cx| {
|
||||
model_selector: cx.new(|cx| {
|
||||
AssistantModelSelector::new(
|
||||
fs,
|
||||
model_selector_menu_handle.clone(),
|
||||
prompt_editor.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
|
@ -872,14 +909,14 @@ impl PromptEditor<BufferCodegen> {
|
|||
_phantom: Default::default(),
|
||||
};
|
||||
|
||||
this.subscribe_to_editor(cx);
|
||||
this.subscribe_to_editor(window, cx);
|
||||
this
|
||||
}
|
||||
|
||||
fn handle_codegen_changed(
|
||||
&mut self,
|
||||
_: Model<BufferCodegen>,
|
||||
cx: &mut ViewContext<PromptEditor<BufferCodegen>>,
|
||||
_: Entity<BufferCodegen>,
|
||||
cx: &mut Context<PromptEditor<BufferCodegen>>,
|
||||
) {
|
||||
match self.codegen_status(cx) {
|
||||
CodegenStatus::Idle => {
|
||||
|
@ -918,7 +955,7 @@ impl PromptEditor<BufferCodegen> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn codegen(&self) -> &Model<BufferCodegen> {
|
||||
pub fn codegen(&self) -> &Entity<BufferCodegen> {
|
||||
match &self.mode {
|
||||
PromptEditorMode::Buffer { codegen, .. } => codegen,
|
||||
PromptEditorMode::Terminal { .. } => unreachable!(),
|
||||
|
@ -951,13 +988,14 @@ impl PromptEditor<TerminalCodegen> {
|
|||
pub fn new_terminal(
|
||||
id: TerminalInlineAssistId,
|
||||
prompt_history: VecDeque<String>,
|
||||
prompt_buffer: Model<MultiBuffer>,
|
||||
codegen: Model<TerminalCodegen>,
|
||||
prompt_buffer: Entity<MultiBuffer>,
|
||||
codegen: Entity<TerminalCodegen>,
|
||||
fs: Arc<dyn Fs>,
|
||||
context_store: Model<ContextStore>,
|
||||
workspace: WeakView<Workspace>,
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
context_store: Entity<ContextStore>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let codegen_subscription = cx.observe(&codegen, Self::handle_codegen_changed);
|
||||
let mode = PromptEditorMode::Terminal {
|
||||
|
@ -966,7 +1004,7 @@ impl PromptEditor<TerminalCodegen> {
|
|||
height_in_lines: 1,
|
||||
};
|
||||
|
||||
let prompt_editor = cx.new_view(|cx| {
|
||||
let prompt_editor = cx.new(|cx| {
|
||||
let mut editor = Editor::new(
|
||||
EditorMode::AutoHeight {
|
||||
max_lines: Self::MAX_LINES as usize,
|
||||
|
@ -974,16 +1012,17 @@ impl PromptEditor<TerminalCodegen> {
|
|||
prompt_buffer,
|
||||
None,
|
||||
false,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
|
||||
editor.set_placeholder_text(Self::placeholder_text(&mode, cx), cx);
|
||||
editor.set_placeholder_text(Self::placeholder_text(&mode, window, cx), cx);
|
||||
editor
|
||||
});
|
||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let model_selector_menu_handle = PopoverMenuHandle::default();
|
||||
|
||||
let context_strip = cx.new_view(|cx| {
|
||||
let context_strip = cx.new(|cx| {
|
||||
ContextStrip::new(
|
||||
context_store.clone(),
|
||||
workspace.clone(),
|
||||
|
@ -991,23 +1030,25 @@ impl PromptEditor<TerminalCodegen> {
|
|||
thread_store.clone(),
|
||||
context_picker_menu_handle.clone(),
|
||||
SuggestContextKind::Thread,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let context_strip_subscription =
|
||||
cx.subscribe(&context_strip, Self::handle_context_strip_event);
|
||||
cx.subscribe_in(&context_strip, window, Self::handle_context_strip_event);
|
||||
|
||||
let mut this = Self {
|
||||
editor: prompt_editor.clone(),
|
||||
context_store,
|
||||
context_strip,
|
||||
context_picker_menu_handle,
|
||||
model_selector: cx.new_view(|cx| {
|
||||
model_selector: cx.new(|cx| {
|
||||
AssistantModelSelector::new(
|
||||
fs,
|
||||
model_selector_menu_handle.clone(),
|
||||
prompt_editor.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
|
@ -1024,11 +1065,11 @@ impl PromptEditor<TerminalCodegen> {
|
|||
_phantom: Default::default(),
|
||||
};
|
||||
this.count_lines(cx);
|
||||
this.subscribe_to_editor(cx);
|
||||
this.subscribe_to_editor(window, cx);
|
||||
this
|
||||
}
|
||||
|
||||
fn count_lines(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn count_lines(&mut self, cx: &mut Context<Self>) {
|
||||
let height_in_lines = cmp::max(
|
||||
2, // Make the editor at least two lines tall, to account for padding and buttons.
|
||||
cmp::min(
|
||||
|
@ -1052,7 +1093,7 @@ impl PromptEditor<TerminalCodegen> {
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_codegen_changed(&mut self, _: Model<TerminalCodegen>, cx: &mut ViewContext<Self>) {
|
||||
fn handle_codegen_changed(&mut self, _: Entity<TerminalCodegen>, cx: &mut Context<Self>) {
|
||||
match &self.codegen().read(cx).status {
|
||||
CodegenStatus::Idle => {
|
||||
self.editor
|
||||
|
@ -1070,7 +1111,7 @@ impl PromptEditor<TerminalCodegen> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn codegen(&self) -> &Model<TerminalCodegen> {
|
||||
pub fn codegen(&self) -> &Entity<TerminalCodegen> {
|
||||
match &self.mode {
|
||||
PromptEditorMode::Buffer { .. } => unreachable!(),
|
||||
PromptEditorMode::Terminal { codegen, .. } => codegen,
|
||||
|
@ -1094,7 +1135,7 @@ fn dismissed_rate_limit_notice() -> bool {
|
|||
.map_or(false, |s| s.is_some())
|
||||
}
|
||||
|
||||
fn set_rate_limit_notice_dismissed(is_dismissed: bool, cx: &mut AppContext) {
|
||||
fn set_rate_limit_notice_dismissed(is_dismissed: bool, cx: &mut App) {
|
||||
db::write_and_log(cx, move || async move {
|
||||
if is_dismissed {
|
||||
db::kvp::KEY_VALUE_STORE
|
||||
|
|
|
@ -4,8 +4,8 @@ use editor::actions::MoveUp;
|
|||
use editor::{Editor, EditorElement, EditorEvent, EditorStyle};
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
pulsating_between, Animation, AnimationExt, AppContext, DismissEvent, FocusableView, Model,
|
||||
Subscription, TextStyle, View, WeakModel, WeakView,
|
||||
pulsating_between, Animation, AnimationExt, App, DismissEvent, Entity, Focusable, Subscription,
|
||||
TextStyle, WeakEntity,
|
||||
};
|
||||
use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
|
||||
use language_model_selector::LanguageModelSelector;
|
||||
|
@ -27,14 +27,14 @@ use crate::thread_store::ThreadStore;
|
|||
use crate::{Chat, ChatMode, RemoveAllContext, ToggleContextPicker, ToggleModelSelector};
|
||||
|
||||
pub struct MessageEditor {
|
||||
thread: Model<Thread>,
|
||||
editor: View<Editor>,
|
||||
context_store: Model<ContextStore>,
|
||||
context_strip: View<ContextStrip>,
|
||||
thread: Entity<Thread>,
|
||||
editor: Entity<Editor>,
|
||||
context_store: Entity<ContextStore>,
|
||||
context_strip: Entity<ContextStrip>,
|
||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
inline_context_picker: View<ContextPicker>,
|
||||
inline_context_picker: Entity<ContextPicker>,
|
||||
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
model_selector: View<AssistantModelSelector>,
|
||||
model_selector: Entity<AssistantModelSelector>,
|
||||
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
use_tools: bool,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
|
@ -43,36 +43,38 @@ pub struct MessageEditor {
|
|||
impl MessageEditor {
|
||||
pub fn new(
|
||||
fs: Arc<dyn Fs>,
|
||||
workspace: WeakView<Workspace>,
|
||||
thread_store: WeakModel<ThreadStore>,
|
||||
thread: Model<Thread>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
thread_store: WeakEntity<ThreadStore>,
|
||||
thread: Entity<Thread>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let context_store = cx.new_model(|_cx| ContextStore::new(workspace.clone()));
|
||||
let context_store = cx.new(|_cx| ContextStore::new(workspace.clone()));
|
||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let inline_context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let model_selector_menu_handle = PopoverMenuHandle::default();
|
||||
|
||||
let editor = cx.new_view(|cx| {
|
||||
let mut editor = Editor::auto_height(10, cx);
|
||||
let editor = cx.new(|cx| {
|
||||
let mut editor = Editor::auto_height(10, window, cx);
|
||||
editor.set_placeholder_text("Ask anything, @ to mention, ↑ to select", cx);
|
||||
editor.set_show_indent_guides(false, cx);
|
||||
|
||||
editor
|
||||
});
|
||||
|
||||
let inline_context_picker = cx.new_view(|cx| {
|
||||
let inline_context_picker = cx.new(|cx| {
|
||||
ContextPicker::new(
|
||||
workspace.clone(),
|
||||
Some(thread_store.clone()),
|
||||
context_store.downgrade(),
|
||||
editor.downgrade(),
|
||||
ConfirmBehavior::Close,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let context_strip = cx.new_view(|cx| {
|
||||
let context_strip = cx.new(|cx| {
|
||||
ContextStrip::new(
|
||||
context_store.clone(),
|
||||
workspace.clone(),
|
||||
|
@ -80,17 +82,19 @@ impl MessageEditor {
|
|||
Some(thread_store.clone()),
|
||||
context_picker_menu_handle.clone(),
|
||||
SuggestContextKind::File,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let subscriptions = vec![
|
||||
cx.subscribe(&editor, Self::handle_editor_event),
|
||||
cx.subscribe(
|
||||
cx.subscribe_in(&editor, window, Self::handle_editor_event),
|
||||
cx.subscribe_in(
|
||||
&inline_context_picker,
|
||||
window,
|
||||
Self::handle_inline_context_picker_event,
|
||||
),
|
||||
cx.subscribe(&context_strip, Self::handle_context_strip_event),
|
||||
cx.subscribe_in(&context_strip, window, Self::handle_context_strip_event),
|
||||
];
|
||||
|
||||
Self {
|
||||
|
@ -101,11 +105,12 @@ impl MessageEditor {
|
|||
context_picker_menu_handle,
|
||||
inline_context_picker,
|
||||
inline_context_picker_menu_handle,
|
||||
model_selector: cx.new_view(|cx| {
|
||||
model_selector: cx.new(|cx| {
|
||||
AssistantModelSelector::new(
|
||||
fs,
|
||||
model_selector_menu_handle.clone(),
|
||||
editor.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
|
@ -115,39 +120,59 @@ impl MessageEditor {
|
|||
}
|
||||
}
|
||||
|
||||
fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
|
||||
self.model_selector_menu_handle.toggle(cx)
|
||||
fn toggle_model_selector(
|
||||
&mut self,
|
||||
_: &ToggleModelSelector,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.model_selector_menu_handle.toggle(window, cx)
|
||||
}
|
||||
|
||||
fn toggle_chat_mode(&mut self, _: &ChatMode, cx: &mut ViewContext<Self>) {
|
||||
fn toggle_chat_mode(&mut self, _: &ChatMode, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.use_tools = !self.use_tools;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
|
||||
self.context_picker_menu_handle.toggle(cx);
|
||||
fn toggle_context_picker(
|
||||
&mut self,
|
||||
_: &ToggleContextPicker,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.context_picker_menu_handle.toggle(window, cx);
|
||||
}
|
||||
|
||||
pub fn remove_all_context(&mut self, _: &RemoveAllContext, cx: &mut ViewContext<Self>) {
|
||||
pub fn remove_all_context(
|
||||
&mut self,
|
||||
_: &RemoveAllContext,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.context_store.update(cx, |store, _cx| store.clear());
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn chat(&mut self, _: &Chat, cx: &mut ViewContext<Self>) {
|
||||
self.send_to_model(RequestKind::Chat, cx);
|
||||
fn chat(&mut self, _: &Chat, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.send_to_model(RequestKind::Chat, window, cx);
|
||||
}
|
||||
|
||||
fn is_editor_empty(&self, cx: &AppContext) -> bool {
|
||||
fn is_editor_empty(&self, cx: &App) -> bool {
|
||||
self.editor.read(cx).text(cx).is_empty()
|
||||
}
|
||||
|
||||
fn is_model_selected(&self, cx: &AppContext) -> bool {
|
||||
fn is_model_selected(&self, cx: &App) -> bool {
|
||||
LanguageModelRegistry::read_global(cx)
|
||||
.active_model()
|
||||
.is_some()
|
||||
}
|
||||
|
||||
fn send_to_model(&mut self, request_kind: RequestKind, cx: &mut ViewContext<Self>) {
|
||||
fn send_to_model(
|
||||
&mut self,
|
||||
request_kind: RequestKind,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let provider = LanguageModelRegistry::read_global(cx).active_provider();
|
||||
if provider
|
||||
.as_ref()
|
||||
|
@ -164,7 +189,7 @@ impl MessageEditor {
|
|||
|
||||
let user_message = self.editor.update(cx, |editor, cx| {
|
||||
let text = editor.text(cx);
|
||||
editor.clear(cx);
|
||||
editor.clear(window, cx);
|
||||
text
|
||||
});
|
||||
|
||||
|
@ -203,9 +228,10 @@ impl MessageEditor {
|
|||
|
||||
fn handle_editor_event(
|
||||
&mut self,
|
||||
editor: View<Editor>,
|
||||
editor: &Entity<Editor>,
|
||||
event: &EditorEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match event {
|
||||
EditorEvent::SelectionsChanged { .. } => {
|
||||
|
@ -216,7 +242,7 @@ impl MessageEditor {
|
|||
let behind_cursor = Point::new(newest_cursor.row, newest_cursor.column - 1);
|
||||
let char_behind_cursor = snapshot.chars_at(behind_cursor).next();
|
||||
if char_behind_cursor == Some('@') {
|
||||
self.inline_context_picker_menu_handle.show(cx);
|
||||
self.inline_context_picker_menu_handle.show(window, cx);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -227,52 +253,54 @@ impl MessageEditor {
|
|||
|
||||
fn handle_inline_context_picker_event(
|
||||
&mut self,
|
||||
_inline_context_picker: View<ContextPicker>,
|
||||
_inline_context_picker: &Entity<ContextPicker>,
|
||||
_event: &DismissEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let editor_focus_handle = self.editor.focus_handle(cx);
|
||||
cx.focus(&editor_focus_handle);
|
||||
window.focus(&editor_focus_handle);
|
||||
}
|
||||
|
||||
fn handle_context_strip_event(
|
||||
&mut self,
|
||||
_context_strip: View<ContextStrip>,
|
||||
_context_strip: &Entity<ContextStrip>,
|
||||
event: &ContextStripEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match event {
|
||||
ContextStripEvent::PickerDismissed
|
||||
| ContextStripEvent::BlurredEmpty
|
||||
| ContextStripEvent::BlurredDown => {
|
||||
let editor_focus_handle = self.editor.focus_handle(cx);
|
||||
cx.focus(&editor_focus_handle);
|
||||
window.focus(&editor_focus_handle);
|
||||
}
|
||||
ContextStripEvent::BlurredUp => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
|
||||
fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.context_picker_menu_handle.is_deployed()
|
||||
|| self.inline_context_picker_menu_handle.is_deployed()
|
||||
{
|
||||
cx.propagate();
|
||||
} else {
|
||||
cx.focus_view(&self.context_strip);
|
||||
self.context_strip.focus_handle(cx).focus(window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for MessageEditor {
|
||||
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
|
||||
impl Focusable for MessageEditor {
|
||||
fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
|
||||
self.editor.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for MessageEditor {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let font_size = TextSize::Default.rems(cx);
|
||||
let line_height = font_size.to_pixels(cx.rem_size()) * 1.5;
|
||||
let line_height = font_size.to_pixels(window.rem_size()) * 1.5;
|
||||
let focus_handle = self.editor.focus_handle(cx);
|
||||
let inline_context_picker = self.inline_context_picker.clone();
|
||||
let bg_color = cx.theme().colors().editor_background;
|
||||
|
@ -326,9 +354,9 @@ impl Render for MessageEditor {
|
|||
})
|
||||
.child(
|
||||
PopoverMenu::new("inline-context-picker")
|
||||
.menu(move |cx| {
|
||||
.menu(move |window, cx| {
|
||||
inline_context_picker.update(cx, |this, cx| {
|
||||
this.init(cx);
|
||||
this.init(window, cx);
|
||||
});
|
||||
|
||||
Some(inline_context_picker.clone())
|
||||
|
@ -351,7 +379,7 @@ impl Render for MessageEditor {
|
|||
.child(
|
||||
Switch::new("use-tools", self.use_tools.into())
|
||||
.label("Tools")
|
||||
.on_click(cx.listener(|this, selection, _cx| {
|
||||
.on_click(cx.listener(|this, selection, _window, _cx| {
|
||||
this.use_tools = match selection {
|
||||
ToggleState::Selected => true,
|
||||
ToggleState::Unselected
|
||||
|
@ -361,7 +389,7 @@ impl Render for MessageEditor {
|
|||
.key_binding(KeyBinding::for_action_in(
|
||||
&ChatMode,
|
||||
&focus_handle,
|
||||
cx,
|
||||
window,
|
||||
)),
|
||||
)
|
||||
.child(h_flex().gap_1().child(self.model_selector.clone()).child(
|
||||
|
@ -390,14 +418,17 @@ impl Render for MessageEditor {
|
|||
KeyBinding::for_action_in(
|
||||
&editor::actions::Cancel,
|
||||
&focus_handle,
|
||||
cx,
|
||||
window,
|
||||
)
|
||||
.map(|binding| binding.into_any_element()),
|
||||
),
|
||||
)
|
||||
.on_click(move |_event, cx| {
|
||||
focus_handle
|
||||
.dispatch_action(&editor::actions::Cancel, cx);
|
||||
.on_click(move |_event, window, cx| {
|
||||
focus_handle.dispatch_action(
|
||||
&editor::actions::Cancel,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
} else {
|
||||
ButtonLike::new("submit-message")
|
||||
|
@ -417,23 +448,22 @@ impl Render for MessageEditor {
|
|||
KeyBinding::for_action_in(
|
||||
&Chat,
|
||||
&focus_handle,
|
||||
cx,
|
||||
window,
|
||||
)
|
||||
.map(|binding| binding.into_any_element()),
|
||||
),
|
||||
)
|
||||
.on_click(move |_event, cx| {
|
||||
focus_handle.dispatch_action(&Chat, cx);
|
||||
.on_click(move |_event, window, cx| {
|
||||
focus_handle.dispatch_action(&Chat, window, cx);
|
||||
})
|
||||
.when(is_editor_empty, |button| {
|
||||
button.tooltip(|cx| {
|
||||
Tooltip::text("Type a message to submit", cx)
|
||||
})
|
||||
button
|
||||
.tooltip(Tooltip::text("Type a message to submit"))
|
||||
})
|
||||
.when(!is_model_selected, |button| {
|
||||
button.tooltip(|cx| {
|
||||
Tooltip::text("Select a model to continue", cx)
|
||||
})
|
||||
button.tooltip(Tooltip::text(
|
||||
"Select a model to continue",
|
||||
))
|
||||
})
|
||||
},
|
||||
)),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::inline_prompt_editor::CodegenStatus;
|
||||
use client::telemetry::Telemetry;
|
||||
use futures::{channel::mpsc, SinkExt, StreamExt};
|
||||
use gpui::{AppContext, EventEmitter, Model, ModelContext, Task};
|
||||
use gpui::{App, Context, Entity, EventEmitter, Task};
|
||||
use language_model::{LanguageModelRegistry, LanguageModelRequest};
|
||||
use language_models::report_assistant_event;
|
||||
use std::{sync::Arc, time::Instant};
|
||||
|
@ -11,7 +11,7 @@ use terminal::Terminal;
|
|||
pub struct TerminalCodegen {
|
||||
pub status: CodegenStatus,
|
||||
pub telemetry: Option<Arc<Telemetry>>,
|
||||
terminal: Model<Terminal>,
|
||||
terminal: Entity<Terminal>,
|
||||
generation: Task<()>,
|
||||
pub message_id: Option<String>,
|
||||
transaction: Option<TerminalTransaction>,
|
||||
|
@ -20,7 +20,7 @@ pub struct TerminalCodegen {
|
|||
impl EventEmitter<CodegenEvent> for TerminalCodegen {}
|
||||
|
||||
impl TerminalCodegen {
|
||||
pub fn new(terminal: Model<Terminal>, telemetry: Option<Arc<Telemetry>>) -> Self {
|
||||
pub fn new(terminal: Entity<Terminal>, telemetry: Option<Arc<Telemetry>>) -> Self {
|
||||
Self {
|
||||
terminal,
|
||||
telemetry,
|
||||
|
@ -31,7 +31,7 @@ impl TerminalCodegen {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self, prompt: LanguageModelRequest, cx: &mut ModelContext<Self>) {
|
||||
pub fn start(&mut self, prompt: LanguageModelRequest, cx: &mut Context<Self>) {
|
||||
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
|
||||
return;
|
||||
};
|
||||
|
@ -131,20 +131,20 @@ impl TerminalCodegen {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn stop(&mut self, cx: &mut ModelContext<Self>) {
|
||||
pub fn stop(&mut self, cx: &mut Context<Self>) {
|
||||
self.status = CodegenStatus::Done;
|
||||
self.generation = Task::ready(());
|
||||
cx.emit(CodegenEvent::Finished);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn complete(&mut self, cx: &mut ModelContext<Self>) {
|
||||
pub fn complete(&mut self, cx: &mut Context<Self>) {
|
||||
if let Some(transaction) = self.transaction.take() {
|
||||
transaction.complete(cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
|
||||
pub fn undo(&mut self, cx: &mut Context<Self>) {
|
||||
if let Some(transaction) = self.transaction.take() {
|
||||
transaction.undo(cx);
|
||||
}
|
||||
|
@ -160,27 +160,27 @@ pub const CLEAR_INPUT: &str = "\x15";
|
|||
const CARRIAGE_RETURN: &str = "\x0d";
|
||||
|
||||
struct TerminalTransaction {
|
||||
terminal: Model<Terminal>,
|
||||
terminal: Entity<Terminal>,
|
||||
}
|
||||
|
||||
impl TerminalTransaction {
|
||||
pub fn start(terminal: Model<Terminal>) -> Self {
|
||||
pub fn start(terminal: Entity<Terminal>) -> Self {
|
||||
Self { terminal }
|
||||
}
|
||||
|
||||
pub fn push(&mut self, hunk: String, cx: &mut AppContext) {
|
||||
pub fn push(&mut self, hunk: String, cx: &mut App) {
|
||||
// Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
|
||||
let input = Self::sanitize_input(hunk);
|
||||
self.terminal
|
||||
.update(cx, |terminal, _| terminal.input(input));
|
||||
}
|
||||
|
||||
pub fn undo(&self, cx: &mut AppContext) {
|
||||
pub fn undo(&self, cx: &mut App) {
|
||||
self.terminal
|
||||
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.to_string()));
|
||||
}
|
||||
|
||||
pub fn complete(&self, cx: &mut AppContext) {
|
||||
pub fn complete(&self, cx: &mut App) {
|
||||
self.terminal.update(cx, |terminal, _| {
|
||||
terminal.input(CARRIAGE_RETURN.to_string())
|
||||
});
|
||||
|
|
|
@ -10,10 +10,7 @@ use client::telemetry::Telemetry;
|
|||
use collections::{HashMap, VecDeque};
|
||||
use editor::{actions::SelectAll, MultiBuffer};
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
AppContext, Context, FocusableView, Global, Model, Subscription, UpdateGlobal, View, WeakModel,
|
||||
WeakView,
|
||||
};
|
||||
use gpui::{App, Entity, Focusable, Global, Subscription, UpdateGlobal, WeakEntity};
|
||||
use language::Buffer;
|
||||
use language_model::{
|
||||
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
||||
|
@ -31,7 +28,7 @@ pub fn init(
|
|||
fs: Arc<dyn Fs>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
cx: &mut AppContext,
|
||||
cx: &mut App,
|
||||
) {
|
||||
cx.set_global(TerminalInlineAssistant::new(fs, prompt_builder, telemetry));
|
||||
}
|
||||
|
@ -68,20 +65,20 @@ impl TerminalInlineAssistant {
|
|||
|
||||
pub fn assist(
|
||||
&mut self,
|
||||
terminal_view: &View<TerminalView>,
|
||||
workspace: WeakView<Workspace>,
|
||||
thread_store: Option<WeakModel<ThreadStore>>,
|
||||
cx: &mut WindowContext,
|
||||
terminal_view: &Entity<TerminalView>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let terminal = terminal_view.read(cx).terminal().clone();
|
||||
let assist_id = self.next_assist_id.post_inc();
|
||||
let prompt_buffer = cx.new_model(|cx| {
|
||||
MultiBuffer::singleton(cx.new_model(|cx| Buffer::local(String::new(), cx)), cx)
|
||||
});
|
||||
let context_store = cx.new_model(|_cx| ContextStore::new(workspace.clone()));
|
||||
let codegen = cx.new_model(|_| TerminalCodegen::new(terminal, self.telemetry.clone()));
|
||||
let prompt_buffer =
|
||||
cx.new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(String::new(), cx)), cx));
|
||||
let context_store = cx.new(|_cx| ContextStore::new(workspace.clone()));
|
||||
let codegen = cx.new(|_| TerminalCodegen::new(terminal, self.telemetry.clone()));
|
||||
|
||||
let prompt_editor = cx.new_view(|cx| {
|
||||
let prompt_editor = cx.new(|cx| {
|
||||
PromptEditor::new_terminal(
|
||||
assist_id,
|
||||
self.prompt_history.clone(),
|
||||
|
@ -91,6 +88,7 @@ impl TerminalInlineAssistant {
|
|||
context_store.clone(),
|
||||
workspace.clone(),
|
||||
thread_store.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
@ -100,7 +98,7 @@ impl TerminalInlineAssistant {
|
|||
render: Box::new(move |_| prompt_editor_render.clone().into_any_element()),
|
||||
};
|
||||
terminal_view.update(cx, |terminal_view, cx| {
|
||||
terminal_view.set_block_below_cursor(block, cx);
|
||||
terminal_view.set_block_below_cursor(block, window, cx);
|
||||
});
|
||||
|
||||
let terminal_assistant = TerminalInlineAssist::new(
|
||||
|
@ -109,21 +107,27 @@ impl TerminalInlineAssistant {
|
|||
prompt_editor,
|
||||
workspace.clone(),
|
||||
context_store,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
self.assists.insert(assist_id, terminal_assistant);
|
||||
|
||||
self.focus_assist(assist_id, cx);
|
||||
self.focus_assist(assist_id, window, cx);
|
||||
}
|
||||
|
||||
fn focus_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
|
||||
fn focus_assist(
|
||||
&mut self,
|
||||
assist_id: TerminalInlineAssistId,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let assist = &self.assists[&assist_id];
|
||||
if let Some(prompt_editor) = assist.prompt_editor.as_ref() {
|
||||
prompt_editor.update(cx, |this, cx| {
|
||||
this.editor.update(cx, |editor, cx| {
|
||||
editor.focus(cx);
|
||||
editor.select_all(&SelectAll, cx);
|
||||
window.focus(&editor.focus_handle(cx));
|
||||
editor.select_all(&SelectAll, window, cx);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -131,9 +135,10 @@ impl TerminalInlineAssistant {
|
|||
|
||||
fn handle_prompt_editor_event(
|
||||
&mut self,
|
||||
prompt_editor: View<PromptEditor<TerminalCodegen>>,
|
||||
prompt_editor: Entity<PromptEditor<TerminalCodegen>>,
|
||||
event: &PromptEditorEvent,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let assist_id = prompt_editor.read(cx).id();
|
||||
match event {
|
||||
|
@ -144,21 +149,21 @@ impl TerminalInlineAssistant {
|
|||
self.stop_assist(assist_id, cx);
|
||||
}
|
||||
PromptEditorEvent::ConfirmRequested { execute } => {
|
||||
self.finish_assist(assist_id, false, *execute, cx);
|
||||
self.finish_assist(assist_id, false, *execute, window, cx);
|
||||
}
|
||||
PromptEditorEvent::CancelRequested => {
|
||||
self.finish_assist(assist_id, true, false, cx);
|
||||
self.finish_assist(assist_id, true, false, window, cx);
|
||||
}
|
||||
PromptEditorEvent::DismissRequested => {
|
||||
self.dismiss_assist(assist_id, cx);
|
||||
self.dismiss_assist(assist_id, window, cx);
|
||||
}
|
||||
PromptEditorEvent::Resized { height_in_lines } => {
|
||||
self.insert_prompt_editor_into_terminal(assist_id, *height_in_lines, cx);
|
||||
self.insert_prompt_editor_into_terminal(assist_id, *height_in_lines, window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
|
||||
fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut App) {
|
||||
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
|
||||
assist
|
||||
} else {
|
||||
|
@ -196,7 +201,7 @@ impl TerminalInlineAssistant {
|
|||
codegen.update(cx, |codegen, cx| codegen.start(request, cx));
|
||||
}
|
||||
|
||||
fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) {
|
||||
fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut App) {
|
||||
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
|
||||
assist
|
||||
} else {
|
||||
|
@ -209,7 +214,7 @@ impl TerminalInlineAssistant {
|
|||
fn request_for_inline_assist(
|
||||
&self,
|
||||
assist_id: TerminalInlineAssistId,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut App,
|
||||
) -> Result<LanguageModelRequest> {
|
||||
let assist = self.assists.get(&assist_id).context("invalid assist")?;
|
||||
|
||||
|
@ -265,16 +270,17 @@ impl TerminalInlineAssistant {
|
|||
assist_id: TerminalInlineAssistId,
|
||||
undo: bool,
|
||||
execute: bool,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
self.dismiss_assist(assist_id, cx);
|
||||
self.dismiss_assist(assist_id, window, cx);
|
||||
|
||||
if let Some(assist) = self.assists.remove(&assist_id) {
|
||||
assist
|
||||
.terminal
|
||||
.update(cx, |this, cx| {
|
||||
this.clear_block_below_cursor(cx);
|
||||
this.focus_handle(cx).focus(cx);
|
||||
this.focus_handle(cx).focus(window);
|
||||
})
|
||||
.log_err();
|
||||
|
||||
|
@ -317,7 +323,8 @@ impl TerminalInlineAssistant {
|
|||
fn dismiss_assist(
|
||||
&mut self,
|
||||
assist_id: TerminalInlineAssistId,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> bool {
|
||||
let Some(assist) = self.assists.get_mut(&assist_id) else {
|
||||
return false;
|
||||
|
@ -330,7 +337,7 @@ impl TerminalInlineAssistant {
|
|||
.terminal
|
||||
.update(cx, |this, cx| {
|
||||
this.clear_block_below_cursor(cx);
|
||||
this.focus_handle(cx).focus(cx);
|
||||
this.focus_handle(cx).focus(window);
|
||||
})
|
||||
.is_ok()
|
||||
}
|
||||
|
@ -339,7 +346,8 @@ impl TerminalInlineAssistant {
|
|||
&mut self,
|
||||
assist_id: TerminalInlineAssistId,
|
||||
height: u8,
|
||||
cx: &mut WindowContext,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
if let Some(assist) = self.assists.get_mut(&assist_id) {
|
||||
if let Some(prompt_editor) = assist.prompt_editor.as_ref().cloned() {
|
||||
|
@ -351,7 +359,7 @@ impl TerminalInlineAssistant {
|
|||
height,
|
||||
render: Box::new(move |_| prompt_editor.clone().into_any_element()),
|
||||
};
|
||||
terminal.set_block_below_cursor(block, cx);
|
||||
terminal.set_block_below_cursor(block, window, cx);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
@ -360,22 +368,23 @@ impl TerminalInlineAssistant {
|
|||
}
|
||||
|
||||
struct TerminalInlineAssist {
|
||||
terminal: WeakView<TerminalView>,
|
||||
prompt_editor: Option<View<PromptEditor<TerminalCodegen>>>,
|
||||
codegen: Model<TerminalCodegen>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: Model<ContextStore>,
|
||||
terminal: WeakEntity<TerminalView>,
|
||||
prompt_editor: Option<Entity<PromptEditor<TerminalCodegen>>>,
|
||||
codegen: Entity<TerminalCodegen>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
context_store: Entity<ContextStore>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
impl TerminalInlineAssist {
|
||||
pub fn new(
|
||||
assist_id: TerminalInlineAssistId,
|
||||
terminal: &View<TerminalView>,
|
||||
prompt_editor: View<PromptEditor<TerminalCodegen>>,
|
||||
workspace: WeakView<Workspace>,
|
||||
context_store: Model<ContextStore>,
|
||||
cx: &mut WindowContext,
|
||||
terminal: &Entity<TerminalView>,
|
||||
prompt_editor: Entity<PromptEditor<TerminalCodegen>>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
context_store: Entity<ContextStore>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
let codegen = prompt_editor.read(cx).codegen().clone();
|
||||
Self {
|
||||
|
@ -385,12 +394,12 @@ impl TerminalInlineAssist {
|
|||
workspace: workspace.clone(),
|
||||
context_store,
|
||||
_subscriptions: vec![
|
||||
cx.subscribe(&prompt_editor, |prompt_editor, event, cx| {
|
||||
window.subscribe(&prompt_editor, cx, |prompt_editor, event, window, cx| {
|
||||
TerminalInlineAssistant::update_global(cx, |this, cx| {
|
||||
this.handle_prompt_editor_event(prompt_editor, event, cx)
|
||||
this.handle_prompt_editor_event(prompt_editor, event, window, cx)
|
||||
})
|
||||
}),
|
||||
cx.subscribe(&codegen, move |codegen, event, cx| {
|
||||
window.subscribe(&codegen, cx, move |codegen, event, window, cx| {
|
||||
TerminalInlineAssistant::update_global(cx, |this, cx| match event {
|
||||
CodegenEvent::Finished => {
|
||||
let assist = if let Some(assist) = this.assists.get(&assist_id) {
|
||||
|
@ -419,7 +428,7 @@ impl TerminalInlineAssist {
|
|||
}
|
||||
|
||||
if assist.prompt_editor.is_none() {
|
||||
this.finish_assist(assist_id, false, false, cx);
|
||||
this.finish_assist(assist_id, false, false, window, cx);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -6,7 +6,7 @@ use chrono::{DateTime, Utc};
|
|||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use futures::future::Shared;
|
||||
use futures::{FutureExt as _, StreamExt as _};
|
||||
use gpui::{AppContext, EventEmitter, ModelContext, SharedString, Task};
|
||||
use gpui::{App, Context, EventEmitter, SharedString, Task};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse,
|
||||
|
@ -76,7 +76,7 @@ pub struct Thread {
|
|||
}
|
||||
|
||||
impl Thread {
|
||||
pub fn new(tools: Arc<ToolWorkingSet>, _cx: &mut ModelContext<Self>) -> Self {
|
||||
pub fn new(tools: Arc<ToolWorkingSet>, _cx: &mut Context<Self>) -> Self {
|
||||
Self {
|
||||
id: ThreadId::new(),
|
||||
updated_at: Utc::now(),
|
||||
|
@ -99,7 +99,7 @@ impl Thread {
|
|||
id: ThreadId,
|
||||
saved: SavedThread,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
_cx: &mut ModelContext<Self>,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let next_message_id = MessageId(saved.messages.len());
|
||||
|
||||
|
@ -154,7 +154,7 @@ impl Thread {
|
|||
self.summary.clone().unwrap_or(DEFAULT)
|
||||
}
|
||||
|
||||
pub fn set_summary(&mut self, summary: impl Into<SharedString>, cx: &mut ModelContext<Self>) {
|
||||
pub fn set_summary(&mut self, summary: impl Into<SharedString>, cx: &mut Context<Self>) {
|
||||
self.summary = Some(summary.into());
|
||||
cx.emit(ThreadEvent::SummaryChanged);
|
||||
}
|
||||
|
@ -194,7 +194,7 @@ impl Thread {
|
|||
&mut self,
|
||||
text: impl Into<String>,
|
||||
context: Vec<ContextSnapshot>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let message_id = self.insert_message(Role::User, text, cx);
|
||||
let context_ids = context.iter().map(|context| context.id).collect::<Vec<_>>();
|
||||
|
@ -207,7 +207,7 @@ impl Thread {
|
|||
&mut self,
|
||||
role: Role,
|
||||
text: impl Into<String>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> MessageId {
|
||||
let id = self.next_message_id.post_inc();
|
||||
self.messages.push(Message {
|
||||
|
@ -244,7 +244,7 @@ impl Thread {
|
|||
pub fn to_completion_request(
|
||||
&self,
|
||||
_request_kind: RequestKind,
|
||||
_cx: &AppContext,
|
||||
_cx: &App,
|
||||
) -> LanguageModelRequest {
|
||||
let mut request = LanguageModelRequest {
|
||||
messages: vec![],
|
||||
|
@ -314,7 +314,7 @@ impl Thread {
|
|||
&mut self,
|
||||
request: LanguageModelRequest,
|
||||
model: Arc<dyn LanguageModel>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let pending_completion_id = post_inc(&mut self.completion_count);
|
||||
|
||||
|
@ -439,7 +439,7 @@ impl Thread {
|
|||
});
|
||||
}
|
||||
|
||||
pub fn summarize(&mut self, cx: &mut ModelContext<Self>) {
|
||||
pub fn summarize(&mut self, cx: &mut Context<Self>) {
|
||||
let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
|
||||
return;
|
||||
};
|
||||
|
@ -497,7 +497,7 @@ impl Thread {
|
|||
assistant_message_id: MessageId,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
output: Task<Result<String>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let insert_output_task = cx.spawn(|thread, mut cx| {
|
||||
let tool_use_id = tool_use_id.clone();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use gpui::{
|
||||
uniform_list, AppContext, FocusHandle, FocusableView, Model, ScrollStrategy,
|
||||
UniformListScrollHandle, WeakView,
|
||||
uniform_list, App, Entity, FocusHandle, Focusable, ScrollStrategy, UniformListScrollHandle,
|
||||
WeakEntity,
|
||||
};
|
||||
use time::{OffsetDateTime, UtcOffset};
|
||||
use ui::{prelude::*, IconButtonShape, ListItem, ListItemSpacing, Tooltip};
|
||||
|
@ -10,17 +10,18 @@ use crate::{AssistantPanel, RemoveSelectedThread};
|
|||
|
||||
pub struct ThreadHistory {
|
||||
focus_handle: FocusHandle,
|
||||
assistant_panel: WeakView<AssistantPanel>,
|
||||
thread_store: Model<ThreadStore>,
|
||||
assistant_panel: WeakEntity<AssistantPanel>,
|
||||
thread_store: Entity<ThreadStore>,
|
||||
scroll_handle: UniformListScrollHandle,
|
||||
selected_index: usize,
|
||||
}
|
||||
|
||||
impl ThreadHistory {
|
||||
pub(crate) fn new(
|
||||
assistant_panel: WeakView<AssistantPanel>,
|
||||
thread_store: Model<ThreadStore>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
assistant_panel: WeakEntity<AssistantPanel>,
|
||||
thread_store: Entity<ThreadStore>,
|
||||
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
|
@ -31,62 +32,77 @@ impl ThreadHistory {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn select_prev(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext<Self>) {
|
||||
pub fn select_prev(
|
||||
&mut self,
|
||||
_: &menu::SelectPrev,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let count = self.thread_store.read(cx).thread_count();
|
||||
if count > 0 {
|
||||
if self.selected_index == 0 {
|
||||
self.set_selected_index(count - 1, cx);
|
||||
self.set_selected_index(count - 1, window, cx);
|
||||
} else {
|
||||
self.set_selected_index(self.selected_index - 1, cx);
|
||||
self.set_selected_index(self.selected_index - 1, window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_next(&mut self, _: &menu::SelectNext, cx: &mut ViewContext<Self>) {
|
||||
pub fn select_next(
|
||||
&mut self,
|
||||
_: &menu::SelectNext,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let count = self.thread_store.read(cx).thread_count();
|
||||
if count > 0 {
|
||||
if self.selected_index == count - 1 {
|
||||
self.set_selected_index(0, cx);
|
||||
self.set_selected_index(0, window, cx);
|
||||
} else {
|
||||
self.set_selected_index(self.selected_index + 1, cx);
|
||||
self.set_selected_index(self.selected_index + 1, window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn select_first(&mut self, _: &menu::SelectFirst, cx: &mut ViewContext<Self>) {
|
||||
fn select_first(&mut self, _: &menu::SelectFirst, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let count = self.thread_store.read(cx).thread_count();
|
||||
if count > 0 {
|
||||
self.set_selected_index(0, cx);
|
||||
self.set_selected_index(0, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn select_last(&mut self, _: &menu::SelectLast, cx: &mut ViewContext<Self>) {
|
||||
fn select_last(&mut self, _: &menu::SelectLast, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let count = self.thread_store.read(cx).thread_count();
|
||||
if count > 0 {
|
||||
self.set_selected_index(count - 1, cx);
|
||||
self.set_selected_index(count - 1, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, index: usize, cx: &mut ViewContext<Self>) {
|
||||
fn set_selected_index(&mut self, index: usize, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.selected_index = index;
|
||||
self.scroll_handle
|
||||
.scroll_to_item(index, ScrollStrategy::Top);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
|
||||
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let threads = self.thread_store.update(cx, |this, _cx| this.threads());
|
||||
|
||||
if let Some(thread) = threads.get(self.selected_index) {
|
||||
self.assistant_panel
|
||||
.update(cx, move |this, cx| this.open_thread(&thread.id, cx))
|
||||
.update(cx, move |this, cx| this.open_thread(&thread.id, window, cx))
|
||||
.ok();
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_selected_thread(&mut self, _: &RemoveSelectedThread, cx: &mut ViewContext<Self>) {
|
||||
fn remove_selected_thread(
|
||||
&mut self,
|
||||
_: &RemoveSelectedThread,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let threads = self.thread_store.update(cx, |this, _cx| this.threads());
|
||||
|
||||
if let Some(thread) = threads.get(self.selected_index) {
|
||||
|
@ -101,14 +117,14 @@ impl ThreadHistory {
|
|||
}
|
||||
}
|
||||
|
||||
impl FocusableView for ThreadHistory {
|
||||
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
||||
impl Focusable for ThreadHistory {
|
||||
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ThreadHistory {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let threads = self.thread_store.update(cx, |this, _cx| this.threads());
|
||||
let selected_index = self.selected_index;
|
||||
|
||||
|
@ -138,10 +154,10 @@ impl Render for ThreadHistory {
|
|||
} else {
|
||||
history.child(
|
||||
uniform_list(
|
||||
cx.view().clone(),
|
||||
cx.model().clone(),
|
||||
"thread-history",
|
||||
threads.len(),
|
||||
move |history, range, _cx| {
|
||||
move |history, range, _window, _cx| {
|
||||
threads[range]
|
||||
.iter()
|
||||
.enumerate()
|
||||
|
@ -166,14 +182,14 @@ impl Render for ThreadHistory {
|
|||
#[derive(IntoElement)]
|
||||
pub struct PastThread {
|
||||
thread: SavedThreadMetadata,
|
||||
assistant_panel: WeakView<AssistantPanel>,
|
||||
assistant_panel: WeakEntity<AssistantPanel>,
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
impl PastThread {
|
||||
pub fn new(
|
||||
thread: SavedThreadMetadata,
|
||||
assistant_panel: WeakView<AssistantPanel>,
|
||||
assistant_panel: WeakEntity<AssistantPanel>,
|
||||
selected: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
|
@ -185,7 +201,7 @@ impl PastThread {
|
|||
}
|
||||
|
||||
impl RenderOnce for PastThread {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let summary = self.thread.summary;
|
||||
|
||||
let thread_timestamp = time_format::format_localized_timestamp(
|
||||
|
@ -219,11 +235,11 @@ impl RenderOnce for PastThread {
|
|||
IconButton::new("delete", IconName::TrashAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(|cx| Tooltip::text("Delete Thread", cx))
|
||||
.tooltip(Tooltip::text("Delete Thread"))
|
||||
.on_click({
|
||||
let assistant_panel = self.assistant_panel.clone();
|
||||
let id = self.thread.id.clone();
|
||||
move |_event, cx| {
|
||||
move |_event, _window, cx| {
|
||||
assistant_panel
|
||||
.update(cx, |this, cx| {
|
||||
this.delete_thread(&id, cx);
|
||||
|
@ -236,10 +252,10 @@ impl RenderOnce for PastThread {
|
|||
.on_click({
|
||||
let assistant_panel = self.assistant_panel.clone();
|
||||
let id = self.thread.id.clone();
|
||||
move |_event, cx| {
|
||||
move |_event, window, cx| {
|
||||
assistant_panel
|
||||
.update(cx, |this, cx| {
|
||||
this.open_thread(&id, cx).detach_and_log_err(cx);
|
||||
this.open_thread(&id, window, cx).detach_and_log_err(cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use context_server::manager::ContextServerManager;
|
|||
use context_server::{ContextServerFactoryRegistry, ContextServerTool};
|
||||
use futures::future::{self, BoxFuture, Shared};
|
||||
use futures::FutureExt as _;
|
||||
use gpui::{prelude::*, AppContext, BackgroundExecutor, Model, ModelContext, SharedString, Task};
|
||||
use gpui::{prelude::*, App, BackgroundExecutor, Context, Entity, SharedString, Task};
|
||||
use heed::types::SerdeBincode;
|
||||
use heed::Database;
|
||||
use language_model::Role;
|
||||
|
@ -21,9 +21,9 @@ use crate::thread::{MessageId, Thread, ThreadId};
|
|||
|
||||
pub struct ThreadStore {
|
||||
#[allow(unused)]
|
||||
project: Model<Project>,
|
||||
project: Entity<Project>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
context_server_manager: Model<ContextServerManager>,
|
||||
context_server_manager: Entity<ContextServerManager>,
|
||||
context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>,
|
||||
threads: Vec<SavedThreadMetadata>,
|
||||
database_future: Shared<BoxFuture<'static, Result<Arc<ThreadsDatabase>, Arc<anyhow::Error>>>>,
|
||||
|
@ -31,15 +31,15 @@ pub struct ThreadStore {
|
|||
|
||||
impl ThreadStore {
|
||||
pub fn new(
|
||||
project: Model<Project>,
|
||||
project: Entity<Project>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<Model<Self>>> {
|
||||
cx: &mut App,
|
||||
) -> Task<Result<Entity<Self>>> {
|
||||
cx.spawn(|mut cx| async move {
|
||||
let this = cx.new_model(|cx: &mut ModelContext<Self>| {
|
||||
let this = cx.new(|cx: &mut Context<Self>| {
|
||||
let context_server_factory_registry =
|
||||
ContextServerFactoryRegistry::default_global(cx);
|
||||
let context_server_manager = cx.new_model(|cx| {
|
||||
let context_server_manager = cx.new(|cx| {
|
||||
ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
|
||||
});
|
||||
|
||||
|
@ -88,15 +88,15 @@ impl ThreadStore {
|
|||
self.threads().into_iter().take(limit).collect()
|
||||
}
|
||||
|
||||
pub fn create_thread(&mut self, cx: &mut ModelContext<Self>) -> Model<Thread> {
|
||||
cx.new_model(|cx| Thread::new(self.tools.clone(), cx))
|
||||
pub fn create_thread(&mut self, cx: &mut Context<Self>) -> Entity<Thread> {
|
||||
cx.new(|cx| Thread::new(self.tools.clone(), cx))
|
||||
}
|
||||
|
||||
pub fn open_thread(
|
||||
&self,
|
||||
id: &ThreadId,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Model<Thread>>> {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<Thread>>> {
|
||||
let id = id.clone();
|
||||
let database_future = self.database_future.clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
|
@ -107,16 +107,12 @@ impl ThreadStore {
|
|||
.ok_or_else(|| anyhow!("no thread found with ID: {id:?}"))?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
cx.new_model(|cx| Thread::from_saved(id.clone(), thread, this.tools.clone(), cx))
|
||||
cx.new(|cx| Thread::from_saved(id.clone(), thread, this.tools.clone(), cx))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn save_thread(
|
||||
&self,
|
||||
thread: &Model<Thread>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
pub fn save_thread(&self, thread: &Entity<Thread>, cx: &mut Context<Self>) -> Task<Result<()>> {
|
||||
let (metadata, thread) = thread.update(cx, |thread, _cx| {
|
||||
let id = thread.id().clone();
|
||||
let thread = SavedThread {
|
||||
|
@ -144,11 +140,7 @@ impl ThreadStore {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn delete_thread(
|
||||
&mut self,
|
||||
id: &ThreadId,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
pub fn delete_thread(&mut self, id: &ThreadId, cx: &mut Context<Self>) -> Task<Result<()>> {
|
||||
let id = id.clone();
|
||||
let database_future = self.database_future.clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
|
@ -161,7 +153,7 @@ impl ThreadStore {
|
|||
})
|
||||
}
|
||||
|
||||
fn reload(&self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
fn reload(&self, cx: &mut Context<Self>) -> Task<Result<()>> {
|
||||
let database_future = self.database_future.clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let threads = database_future
|
||||
|
@ -177,7 +169,7 @@ impl ThreadStore {
|
|||
})
|
||||
}
|
||||
|
||||
fn register_context_server_handlers(&self, cx: &mut ModelContext<Self>) {
|
||||
fn register_context_server_handlers(&self, cx: &mut Context<Self>) {
|
||||
cx.subscribe(
|
||||
&self.context_server_manager.clone(),
|
||||
Self::handle_context_server_event,
|
||||
|
@ -187,9 +179,9 @@ impl ThreadStore {
|
|||
|
||||
fn handle_context_server_event(
|
||||
&mut self,
|
||||
context_server_manager: Model<ContextServerManager>,
|
||||
context_server_manager: Entity<ContextServerManager>,
|
||||
event: &context_server::manager::Event,
|
||||
cx: &mut ModelContext<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let tool_working_set = self.tools.clone();
|
||||
match event {
|
||||
|
|
|
@ -11,15 +11,15 @@ pub enum ContextPill {
|
|||
context: ContextSnapshot,
|
||||
dupe_name: bool,
|
||||
focused: bool,
|
||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
|
||||
on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
|
||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
||||
on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
||||
},
|
||||
Suggested {
|
||||
name: SharedString,
|
||||
icon_path: Option<SharedString>,
|
||||
kind: ContextKind,
|
||||
focused: bool,
|
||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
|
||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ impl ContextPill {
|
|||
context: ContextSnapshot,
|
||||
dupe_name: bool,
|
||||
focused: bool,
|
||||
on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
|
||||
on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
||||
) -> Self {
|
||||
Self::Added {
|
||||
context,
|
||||
|
@ -54,7 +54,7 @@ impl ContextPill {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn on_click(mut self, listener: Rc<dyn Fn(&ClickEvent, &mut WindowContext)>) -> Self {
|
||||
pub fn on_click(mut self, listener: Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>) -> Self {
|
||||
match &mut self {
|
||||
ContextPill::Added { on_click, .. } => {
|
||||
*on_click = Some(listener);
|
||||
|
@ -95,7 +95,7 @@ impl ContextPill {
|
|||
}
|
||||
|
||||
impl RenderOnce for ContextPill {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let color = cx.theme().colors();
|
||||
|
||||
let base_pill = h_flex()
|
||||
|
@ -139,7 +139,7 @@ impl RenderOnce for ContextPill {
|
|||
}
|
||||
})
|
||||
.when_some(context.tooltip.clone(), |element, tooltip| {
|
||||
element.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx))
|
||||
element.tooltip(Tooltip::text(tooltip.clone()))
|
||||
}),
|
||||
)
|
||||
.when_some(on_remove.as_ref(), |element, on_remove| {
|
||||
|
@ -147,16 +147,16 @@ impl RenderOnce for ContextPill {
|
|||
IconButton::new(("remove", context.id.0), IconName::Close)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.tooltip(|cx| Tooltip::text("Remove Context", cx))
|
||||
.tooltip(Tooltip::text("Remove Context"))
|
||||
.on_click({
|
||||
let on_remove = on_remove.clone();
|
||||
move |event, cx| on_remove(event, cx)
|
||||
move |event, window, cx| on_remove(event, window, cx)
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when_some(on_click.as_ref(), |element, on_click| {
|
||||
let on_click = on_click.clone();
|
||||
element.on_click(move |event, cx| on_click(event, cx))
|
||||
element.on_click(move |event, window, cx| on_click(event, window, cx))
|
||||
}),
|
||||
ContextPill::Suggested {
|
||||
name,
|
||||
|
@ -195,10 +195,12 @@ impl RenderOnce for ContextPill {
|
|||
.size(IconSize::XSmall)
|
||||
.into_any_element(),
|
||||
)
|
||||
.tooltip(|cx| Tooltip::with_meta("Suggested Context", None, "Click to add it", cx))
|
||||
.tooltip(|window, cx| {
|
||||
Tooltip::with_meta("Suggested Context", None, "Click to add it", window, cx)
|
||||
})
|
||||
.when_some(on_click.as_ref(), |element, on_click| {
|
||||
let on_click = on_click.clone();
|
||||
element.on_click(move |event, cx| on_click(event, cx))
|
||||
element.on_click(move |event, window, cx| on_click(event, window, cx))
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue