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:
Nathan Sobo 2025-01-25 20:02:45 -07:00 committed by GitHub
parent 21b4a0d50e
commit 6fca1d2b0b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
648 changed files with 36248 additions and 28208 deletions

View file

@ -3,9 +3,9 @@ use editor::Editor;
use extension_host::ExtensionStore; use extension_host::ExtensionStore;
use futures::StreamExt; use futures::StreamExt;
use gpui::{ use gpui::{
actions, percentage, Animation, AnimationExt as _, AppContext, CursorStyle, EventEmitter, actions, percentage, Animation, AnimationExt as _, App, Context, CursorStyle, Entity,
InteractiveElement as _, Model, ParentElement as _, Render, SharedString, EventEmitter, InteractiveElement as _, ParentElement as _, Render, SharedString,
StatefulInteractiveElement, Styled, Transformation, View, ViewContext, VisualContext as _, StatefulInteractiveElement, Styled, Transformation, Window,
}; };
use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId}; use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId};
use lsp::LanguageServerName; use lsp::LanguageServerName;
@ -27,8 +27,8 @@ pub enum Event {
pub struct ActivityIndicator { pub struct ActivityIndicator {
statuses: Vec<LspStatus>, statuses: Vec<LspStatus>,
project: Model<Project>, project: Entity<Project>,
auto_updater: Option<Model<AutoUpdater>>, auto_updater: Option<Entity<AutoUpdater>>,
context_menu_handle: PopoverMenuHandle<ContextMenu>, context_menu_handle: PopoverMenuHandle<ContextMenu>,
} }
@ -46,22 +46,24 @@ struct PendingWork<'a> {
struct Content { struct Content {
icon: Option<gpui::AnyElement>, icon: Option<gpui::AnyElement>,
message: String, message: String,
on_click: Option<Arc<dyn Fn(&mut ActivityIndicator, &mut ViewContext<ActivityIndicator>)>>, on_click:
Option<Arc<dyn Fn(&mut ActivityIndicator, &mut Window, &mut Context<ActivityIndicator>)>>,
} }
impl ActivityIndicator { impl ActivityIndicator {
pub fn new( pub fn new(
workspace: &mut Workspace, workspace: &mut Workspace,
languages: Arc<LanguageRegistry>, languages: Arc<LanguageRegistry>,
cx: &mut ViewContext<Workspace>, window: &mut Window,
) -> View<ActivityIndicator> { cx: &mut Context<Workspace>,
) -> Entity<ActivityIndicator> {
let project = workspace.project().clone(); let project = workspace.project().clone();
let auto_updater = AutoUpdater::get(cx); let auto_updater = AutoUpdater::get(cx);
let this = cx.new_view(|cx: &mut ViewContext<Self>| { let this = cx.new(|cx| {
let mut status_events = languages.language_server_binary_statuses(); let mut status_events = languages.language_server_binary_statuses();
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
while let Some((name, status)) = status_events.next().await { while let Some((name, status)) = status_events.next().await {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this: &mut ActivityIndicator, cx| {
this.statuses.retain(|s| s.name != name); this.statuses.retain(|s| s.name != name);
this.statuses.push(LspStatus { name, status }); this.statuses.push(LspStatus { name, status });
cx.notify(); cx.notify();
@ -70,6 +72,7 @@ impl ActivityIndicator {
anyhow::Ok(()) anyhow::Ok(())
}) })
.detach(); .detach();
cx.observe(&project, |_, _, cx| cx.notify()).detach(); cx.observe(&project, |_, _, cx| cx.notify()).detach();
if let Some(auto_updater) = auto_updater.as_ref() { if let Some(auto_updater) = auto_updater.as_ref() {
@ -84,13 +87,13 @@ impl ActivityIndicator {
} }
}); });
cx.subscribe(&this, move |_, _, event, cx| match event { cx.subscribe_in(&this, window, move |_, _, event, window, cx| match event {
Event::ShowError { lsp_name, error } => { Event::ShowError { lsp_name, error } => {
let create_buffer = project.update(cx, |project, cx| project.create_buffer(cx)); let create_buffer = project.update(cx, |project, cx| project.create_buffer(cx));
let project = project.clone(); let project = project.clone();
let error = error.clone(); let error = error.clone();
let lsp_name = lsp_name.clone(); let lsp_name = lsp_name.clone();
cx.spawn(|workspace, mut cx| async move { cx.spawn_in(window, |workspace, mut cx| async move {
let buffer = create_buffer.await?; let buffer = create_buffer.await?;
buffer.update(&mut cx, |buffer, cx| { buffer.update(&mut cx, |buffer, cx| {
buffer.edit( buffer.edit(
@ -103,13 +106,14 @@ impl ActivityIndicator {
); );
buffer.set_capability(language::Capability::ReadOnly, cx); buffer.set_capability(language::Capability::ReadOnly, cx);
})?; })?;
workspace.update(&mut cx, |workspace, cx| { workspace.update_in(&mut cx, |workspace, window, cx| {
workspace.add_item_to_active_pane( workspace.add_item_to_active_pane(
Box::new(cx.new_view(|cx| { Box::new(cx.new(|cx| {
Editor::for_buffer(buffer, Some(project.clone()), cx) Editor::for_buffer(buffer, Some(project.clone()), window, cx)
})), })),
None, None,
true, true,
window,
cx, cx,
); );
})?; })?;
@ -123,7 +127,7 @@ impl ActivityIndicator {
this this
} }
fn show_error_message(&mut self, _: &ShowErrorMessage, cx: &mut ViewContext<Self>) { fn show_error_message(&mut self, _: &ShowErrorMessage, _: &mut Window, cx: &mut Context<Self>) {
self.statuses.retain(|status| { self.statuses.retain(|status| {
if let LanguageServerBinaryStatus::Failed { error } = &status.status { if let LanguageServerBinaryStatus::Failed { error } = &status.status {
cx.emit(Event::ShowError { cx.emit(Event::ShowError {
@ -139,7 +143,12 @@ impl ActivityIndicator {
cx.notify(); cx.notify();
} }
fn dismiss_error_message(&mut self, _: &DismissErrorMessage, cx: &mut ViewContext<Self>) { fn dismiss_error_message(
&mut self,
_: &DismissErrorMessage,
_: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(updater) = &self.auto_updater { if let Some(updater) = &self.auto_updater {
updater.update(cx, |updater, cx| { updater.update(cx, |updater, cx| {
updater.dismiss_error(cx); updater.dismiss_error(cx);
@ -150,7 +159,7 @@ impl ActivityIndicator {
fn pending_language_server_work<'a>( fn pending_language_server_work<'a>(
&self, &self,
cx: &'a AppContext, cx: &'a App,
) -> impl Iterator<Item = PendingWork<'a>> { ) -> impl Iterator<Item = PendingWork<'a>> {
self.project self.project
.read(cx) .read(cx)
@ -178,12 +187,12 @@ impl ActivityIndicator {
fn pending_environment_errors<'a>( fn pending_environment_errors<'a>(
&'a self, &'a self,
cx: &'a AppContext, cx: &'a App,
) -> impl Iterator<Item = (&'a WorktreeId, &'a EnvironmentErrorMessage)> { ) -> impl Iterator<Item = (&'a WorktreeId, &'a EnvironmentErrorMessage)> {
self.project.read(cx).shell_environment_errors(cx) self.project.read(cx).shell_environment_errors(cx)
} }
fn content_to_render(&mut self, cx: &mut ViewContext<Self>) -> Option<Content> { fn content_to_render(&mut self, cx: &mut Context<Self>) -> Option<Content> {
// Show if any direnv calls failed // Show if any direnv calls failed
if let Some((&worktree_id, error)) = self.pending_environment_errors(cx).next() { if let Some((&worktree_id, error)) = self.pending_environment_errors(cx).next() {
return Some(Content { return Some(Content {
@ -193,11 +202,11 @@ impl ActivityIndicator {
.into_any_element(), .into_any_element(),
), ),
message: error.0.clone(), message: error.0.clone(),
on_click: Some(Arc::new(move |this, cx| { on_click: Some(Arc::new(move |this, window, cx| {
this.project.update(cx, |project, cx| { this.project.update(cx, |project, cx| {
project.remove_environment_error(cx, worktree_id); project.remove_environment_error(cx, worktree_id);
}); });
cx.dispatch_action(Box::new(workspace::OpenLog)); window.dispatch_action(Box::new(workspace::OpenLog), cx);
})), })),
}); });
} }
@ -280,10 +289,10 @@ impl ActivityIndicator {
} }
) )
), ),
on_click: Some(Arc::new(move |this, cx| { on_click: Some(Arc::new(move |this, window, cx| {
this.statuses this.statuses
.retain(|status| !downloading.contains(&status.name)); .retain(|status| !downloading.contains(&status.name));
this.dismiss_error_message(&DismissErrorMessage, cx) this.dismiss_error_message(&DismissErrorMessage, window, cx)
})), })),
}); });
} }
@ -308,10 +317,10 @@ impl ActivityIndicator {
} }
), ),
), ),
on_click: Some(Arc::new(move |this, cx| { on_click: Some(Arc::new(move |this, window, cx| {
this.statuses this.statuses
.retain(|status| !checking_for_update.contains(&status.name)); .retain(|status| !checking_for_update.contains(&status.name));
this.dismiss_error_message(&DismissErrorMessage, cx) this.dismiss_error_message(&DismissErrorMessage, window, cx)
})), })),
}); });
} }
@ -336,8 +345,8 @@ impl ActivityIndicator {
acc acc
}), }),
), ),
on_click: Some(Arc::new(|this, cx| { on_click: Some(Arc::new(|this, window, cx| {
this.show_error_message(&Default::default(), cx) this.show_error_message(&Default::default(), window, cx)
})), })),
}); });
} }
@ -351,11 +360,11 @@ impl ActivityIndicator {
.into_any_element(), .into_any_element(),
), ),
message: format!("Formatting failed: {}. Click to see logs.", failure), message: format!("Formatting failed: {}. Click to see logs.", failure),
on_click: Some(Arc::new(|indicator, cx| { on_click: Some(Arc::new(|indicator, window, cx| {
indicator.project.update(cx, |project, cx| { indicator.project.update(cx, |project, cx| {
project.reset_last_formatting_failure(cx); project.reset_last_formatting_failure(cx);
}); });
cx.dispatch_action(Box::new(workspace::OpenLog)); window.dispatch_action(Box::new(workspace::OpenLog), cx);
})), })),
}); });
} }
@ -370,8 +379,8 @@ impl ActivityIndicator {
.into_any_element(), .into_any_element(),
), ),
message: "Checking for Zed updates…".to_string(), message: "Checking for Zed updates…".to_string(),
on_click: Some(Arc::new(|this, cx| { on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, cx) this.dismiss_error_message(&DismissErrorMessage, window, cx)
})), })),
}), }),
AutoUpdateStatus::Downloading => Some(Content { AutoUpdateStatus::Downloading => Some(Content {
@ -381,8 +390,8 @@ impl ActivityIndicator {
.into_any_element(), .into_any_element(),
), ),
message: "Downloading Zed update…".to_string(), message: "Downloading Zed update…".to_string(),
on_click: Some(Arc::new(|this, cx| { on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, cx) this.dismiss_error_message(&DismissErrorMessage, window, cx)
})), })),
}), }),
AutoUpdateStatus::Installing => Some(Content { AutoUpdateStatus::Installing => Some(Content {
@ -392,8 +401,8 @@ impl ActivityIndicator {
.into_any_element(), .into_any_element(),
), ),
message: "Installing Zed update…".to_string(), message: "Installing Zed update…".to_string(),
on_click: Some(Arc::new(|this, cx| { on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, cx) this.dismiss_error_message(&DismissErrorMessage, window, cx)
})), })),
}), }),
AutoUpdateStatus::Updated { binary_path } => Some(Content { AutoUpdateStatus::Updated { binary_path } => Some(Content {
@ -403,7 +412,7 @@ impl ActivityIndicator {
let reload = workspace::Reload { let reload = workspace::Reload {
binary_path: Some(binary_path.clone()), binary_path: Some(binary_path.clone()),
}; };
move |_, cx| workspace::reload(&reload, cx) move |_, _, cx| workspace::reload(&reload, cx)
})), })),
}), }),
AutoUpdateStatus::Errored => Some(Content { AutoUpdateStatus::Errored => Some(Content {
@ -413,8 +422,8 @@ impl ActivityIndicator {
.into_any_element(), .into_any_element(),
), ),
message: "Auto update failed".to_string(), message: "Auto update failed".to_string(),
on_click: Some(Arc::new(|this, cx| { on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, cx) this.dismiss_error_message(&DismissErrorMessage, window, cx)
})), })),
}), }),
AutoUpdateStatus::Idle => None, AutoUpdateStatus::Idle => None,
@ -432,8 +441,8 @@ impl ActivityIndicator {
.into_any_element(), .into_any_element(),
), ),
message: format!("Updating {extension_id} extension…"), message: format!("Updating {extension_id} extension…"),
on_click: Some(Arc::new(|this, cx| { on_click: Some(Arc::new(|this, window, cx| {
this.dismiss_error_message(&DismissErrorMessage, cx) this.dismiss_error_message(&DismissErrorMessage, window, cx)
})), })),
}); });
} }
@ -442,8 +451,12 @@ impl ActivityIndicator {
None None
} }
fn toggle_language_server_work_context_menu(&mut self, cx: &mut ViewContext<Self>) { fn toggle_language_server_work_context_menu(
self.context_menu_handle.toggle(cx); &mut self,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.context_menu_handle.toggle(window, cx);
} }
} }
@ -452,7 +465,7 @@ impl EventEmitter<Event> for ActivityIndicator {}
const MAX_MESSAGE_LEN: usize = 50; const MAX_MESSAGE_LEN: usize = 50;
impl Render for ActivityIndicator { impl Render for ActivityIndicator {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let result = h_flex() let result = h_flex()
.id("activity-indicator") .id("activity-indicator")
.on_action(cx.listener(Self::show_error_message)) .on_action(cx.listener(Self::show_error_message))
@ -460,7 +473,7 @@ impl Render for ActivityIndicator {
let Some(content) = self.content_to_render(cx) else { let Some(content) = self.content_to_render(cx) else {
return result; return result;
}; };
let this = cx.view().downgrade(); let this = cx.model().downgrade();
let truncate_content = content.message.len() > MAX_MESSAGE_LEN; let truncate_content = content.message.len() > MAX_MESSAGE_LEN;
result.gap_2().child( result.gap_2().child(
PopoverMenu::new("activity-indicator-popover") PopoverMenu::new("activity-indicator-popover")
@ -480,24 +493,24 @@ impl Render for ActivityIndicator {
)) ))
.size(LabelSize::Small), .size(LabelSize::Small),
) )
.tooltip(move |cx| Tooltip::text(&content.message, cx)) .tooltip(Tooltip::text(content.message))
} else { } else {
button.child(Label::new(content.message).size(LabelSize::Small)) button.child(Label::new(content.message).size(LabelSize::Small))
} }
}) })
.when_some(content.on_click, |this, handler| { .when_some(content.on_click, |this, handler| {
this.on_click(cx.listener(move |this, _, cx| { this.on_click(cx.listener(move |this, _, window, cx| {
handler(this, cx); handler(this, window, cx);
})) }))
.cursor(CursorStyle::PointingHand) .cursor(CursorStyle::PointingHand)
}), }),
), ),
) )
.anchor(gpui::Corner::BottomLeft) .anchor(gpui::Corner::BottomLeft)
.menu(move |cx| { .menu(move |window, cx| {
let strong_this = this.upgrade()?; let strong_this = this.upgrade()?;
let mut has_work = false; let mut has_work = false;
let menu = ContextMenu::build(cx, |mut menu, cx| { let menu = ContextMenu::build(window, cx, |mut menu, _, cx| {
for work in strong_this.read(cx).pending_language_server_work(cx) { for work in strong_this.read(cx).pending_language_server_work(cx) {
has_work = true; has_work = true;
let this = this.clone(); let this = this.clone();
@ -513,7 +526,7 @@ impl Render for ActivityIndicator {
let token = work.progress_token.to_string(); let token = work.progress_token.to_string();
let title = SharedString::from(title); let title = SharedString::from(title);
menu = menu.custom_entry( menu = menu.custom_entry(
move |_| { move |_, _| {
h_flex() h_flex()
.w_full() .w_full()
.justify_between() .justify_between()
@ -521,7 +534,7 @@ impl Render for ActivityIndicator {
.child(Icon::new(IconName::XCircle)) .child(Icon::new(IconName::XCircle))
.into_any_element() .into_any_element()
}, },
move |cx| { move |_, cx| {
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
this.project.update(cx, |project, cx| { this.project.update(cx, |project, cx| {
project.cancel_language_server_work( project.cancel_language_server_work(
@ -554,5 +567,11 @@ impl Render for ActivityIndicator {
} }
impl StatusItemView for ActivityIndicator { impl StatusItemView for ActivityIndicator {
fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext<Self>) {} fn set_active_pane_item(
&mut self,
_: Option<&dyn ItemHandle>,
_window: &mut Window,
_: &mut Context<Self>,
) {
}
} }

View file

@ -2,7 +2,7 @@ mod supported_countries;
use std::{pin::Pin, str::FromStr}; use std::{pin::Pin, str::FromStr};
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context as _, Result};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt}; use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt};
use http_client::http::{HeaderMap, HeaderValue}; use http_client::http::{HeaderMap, HeaderValue};

View file

@ -1,7 +1,7 @@
// This crate was essentially pulled out verbatim from main `zed` crate to avoid having to run RustEmbed macro whenever zed has to be rebuilt. It saves a second or two on an incremental build. // This crate was essentially pulled out verbatim from main `zed` crate to avoid having to run RustEmbed macro whenever zed has to be rebuilt. It saves a second or two on an incremental build.
use anyhow::anyhow; use anyhow::anyhow;
use gpui::{AppContext, AssetSource, Result, SharedString}; use gpui::{App, AssetSource, Result, SharedString};
use rust_embed::RustEmbed; use rust_embed::RustEmbed;
#[derive(RustEmbed)] #[derive(RustEmbed)]
@ -39,7 +39,7 @@ impl AssetSource for Assets {
impl Assets { impl Assets {
/// Populate the [`TextSystem`] of the given [`AppContext`] with all `.ttf` fonts in the `fonts` directory. /// Populate the [`TextSystem`] of the given [`AppContext`] with all `.ttf` fonts in the `fonts` directory.
pub fn load_fonts(&self, cx: &AppContext) -> gpui::Result<()> { pub fn load_fonts(&self, cx: &App) -> gpui::Result<()> {
let font_paths = self.list("fonts")?; let font_paths = self.list("fonts")?;
let mut embedded_fonts = Vec::new(); let mut embedded_fonts = Vec::new();
for font_path in font_paths { for font_path in font_paths {
@ -55,7 +55,7 @@ impl Assets {
cx.text_system().add_fonts(embedded_fonts) cx.text_system().add_fonts(embedded_fonts)
} }
pub fn load_test_fonts(&self, cx: &AppContext) { pub fn load_test_fonts(&self, cx: &App) {
cx.text_system() cx.text_system()
.add_fonts(vec![self .add_fonts(vec![self
.load("fonts/plex-mono/ZedPlexMono-Regular.ttf") .load("fonts/plex-mono/ZedPlexMono-Regular.ttf")

View file

@ -15,7 +15,7 @@ use client::Client;
use command_palette_hooks::CommandPaletteFilter; use command_palette_hooks::CommandPaletteFilter;
use feature_flags::FeatureFlagAppExt; use feature_flags::FeatureFlagAppExt;
use fs::Fs; use fs::Fs;
use gpui::{actions, AppContext, Global, UpdateGlobal}; use gpui::{actions, App, Global, UpdateGlobal};
use language_model::{ use language_model::{
LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage, LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage,
}; };
@ -67,7 +67,7 @@ impl Global for Assistant {}
impl Assistant { impl Assistant {
const NAMESPACE: &'static str = "assistant"; const NAMESPACE: &'static str = "assistant";
fn set_enabled(&mut self, enabled: bool, cx: &mut AppContext) { fn set_enabled(&mut self, enabled: bool, cx: &mut App) {
if self.enabled == enabled { if self.enabled == enabled {
return; return;
} }
@ -92,7 +92,7 @@ pub fn init(
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
client: Arc<Client>, client: Arc<Client>,
prompt_builder: Arc<PromptBuilder>, prompt_builder: Arc<PromptBuilder>,
cx: &mut AppContext, cx: &mut App,
) { ) {
cx.set_global(Assistant::default()); cx.set_global(Assistant::default());
AssistantSettings::register(cx); AssistantSettings::register(cx);
@ -165,7 +165,7 @@ pub fn init(
.detach(); .detach();
} }
fn init_language_model_settings(cx: &mut AppContext) { fn init_language_model_settings(cx: &mut App) {
update_active_language_model_from_settings(cx); update_active_language_model_from_settings(cx);
cx.observe_global::<SettingsStore>(update_active_language_model_from_settings) cx.observe_global::<SettingsStore>(update_active_language_model_from_settings)
@ -184,7 +184,7 @@ fn init_language_model_settings(cx: &mut AppContext) {
.detach(); .detach();
} }
fn update_active_language_model_from_settings(cx: &mut AppContext) { fn update_active_language_model_from_settings(cx: &mut App) {
let settings = AssistantSettings::get_global(cx); let settings = AssistantSettings::get_global(cx);
let provider_name = LanguageModelProviderId::from(settings.default_model.provider.clone()); let provider_name = LanguageModelProviderId::from(settings.default_model.provider.clone());
let model_id = LanguageModelId::from(settings.default_model.model.clone()); let model_id = LanguageModelId::from(settings.default_model.model.clone());
@ -204,7 +204,7 @@ fn update_active_language_model_from_settings(cx: &mut AppContext) {
}); });
} }
fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut AppContext) { fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut App) {
let slash_command_registry = SlashCommandRegistry::global(cx); let slash_command_registry = SlashCommandRegistry::global(cx);
slash_command_registry.register_command(assistant_slash_commands::FileSlashCommand, true); slash_command_registry.register_command(assistant_slash_commands::FileSlashCommand, true);
@ -278,7 +278,7 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
.detach(); .detach();
} }
fn update_slash_commands_from_settings(cx: &mut AppContext) { fn update_slash_commands_from_settings(cx: &mut App) {
let slash_command_registry = SlashCommandRegistry::global(cx); let slash_command_registry = SlashCommandRegistry::global(cx);
let settings = SlashCommandSettings::get_global(cx); let settings = SlashCommandSettings::get_global(cx);

View file

@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use collections::HashMap; use collections::HashMap;
use gpui::{canvas, AnyView, AppContext, EventEmitter, FocusHandle, FocusableView, Subscription}; use gpui::{canvas, AnyView, App, EventEmitter, FocusHandle, Focusable, Subscription};
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry}; use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
use ui::{prelude::*, ElevationIndex}; use ui::{prelude::*, ElevationIndex};
use workspace::Item; use workspace::Item;
@ -13,16 +13,17 @@ pub struct ConfigurationView {
} }
impl ConfigurationView { impl ConfigurationView {
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 focus_handle = cx.focus_handle();
let registry_subscription = cx.subscribe( let registry_subscription = cx.subscribe_in(
&LanguageModelRegistry::global(cx), &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) => { language_model::Event::AddedProvider(provider_id) => {
let provider = LanguageModelRegistry::read_global(cx).provider(provider_id); let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
if let Some(provider) = provider { if let Some(provider) = provider {
this.add_configuration_view(&provider, cx); this.add_configuration_view(&provider, window, cx);
} }
} }
language_model::Event::RemovedProvider(provider_id) => { language_model::Event::RemovedProvider(provider_id) => {
@ -37,14 +38,14 @@ impl ConfigurationView {
configuration_views: HashMap::default(), configuration_views: HashMap::default(),
_registry_subscription: registry_subscription, _registry_subscription: registry_subscription,
}; };
this.build_configuration_views(cx); this.build_configuration_views(window, cx);
this this
} }
fn build_configuration_views(&mut self, cx: &mut ViewContext<Self>) { fn build_configuration_views(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let providers = LanguageModelRegistry::read_global(cx).providers(); let providers = LanguageModelRegistry::read_global(cx).providers();
for provider in providers { for provider in providers {
self.add_configuration_view(&provider, cx); self.add_configuration_view(&provider, window, cx);
} }
} }
@ -55,9 +56,10 @@ impl ConfigurationView {
fn add_configuration_view( fn add_configuration_view(
&mut self, &mut self,
provider: &Arc<dyn LanguageModelProvider>, 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 self.configuration_views
.insert(provider.id(), configuration_view); .insert(provider.id(), configuration_view);
} }
@ -65,7 +67,7 @@ impl ConfigurationView {
fn render_provider_view( fn render_provider_view(
&mut self, &mut self,
provider: &Arc<dyn LanguageModelProvider>, provider: &Arc<dyn LanguageModelProvider>,
cx: &mut ViewContext<Self>, cx: &mut Context<Self>,
) -> Div { ) -> Div {
let provider_id = provider.id().0.clone(); let provider_id = provider.id().0.clone();
let provider_name = provider.name().0.clone(); let provider_name = provider.name().0.clone();
@ -73,7 +75,7 @@ impl ConfigurationView {
let open_new_context = cx.listener({ let open_new_context = cx.listener({
let provider = provider.clone(); let provider = provider.clone();
move |_, _, cx| { move |_, _, _window, cx| {
cx.emit(ConfigurationViewEvent::NewProviderContextEditor( cx.emit(ConfigurationViewEvent::NewProviderContextEditor(
provider.clone(), provider.clone(),
)) ))
@ -123,7 +125,7 @@ impl ConfigurationView {
} }
impl Render for ConfigurationView { impl Render for ConfigurationView {
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(); let providers = LanguageModelRegistry::read_global(cx).providers();
let provider_views = providers let provider_views = providers
.into_iter() .into_iter()
@ -163,12 +165,12 @@ impl Render for ConfigurationView {
// We use a canvas here to get scrolling to work in the ConfigurationView. It's a workaround // We use a canvas here to get scrolling to work in the ConfigurationView. It's a workaround
// because we couldn't the element to take up the size of the parent. // because we couldn't the element to take up the size of the parent.
canvas( canvas(
move |bounds, cx| { move |bounds, window, cx| {
element.prepaint_as_root(bounds.origin, bounds.size.into(), cx); element.prepaint_as_root(bounds.origin, bounds.size.into(), window, cx);
element element
}, },
|_, mut element, cx| { |_, mut element, window, cx| {
element.paint(cx); element.paint(window, cx);
}, },
) )
.flex_1() .flex_1()
@ -182,8 +184,8 @@ pub enum ConfigurationViewEvent {
impl EventEmitter<ConfigurationViewEvent> for ConfigurationView {} impl EventEmitter<ConfigurationViewEvent> for ConfigurationView {}
impl FocusableView for ConfigurationView { impl Focusable for ConfigurationView {
fn focus_handle(&self, _: &AppContext) -> FocusHandle { fn focus_handle(&self, _: &App) -> FocusHandle {
self.focus_handle.clone() self.focus_handle.clone()
} }
} }
@ -191,7 +193,7 @@ impl FocusableView for ConfigurationView {
impl Item for ConfigurationView { impl Item for ConfigurationView {
type Event = ConfigurationViewEvent; type Event = ConfigurationViewEvent;
fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> { fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
Some("Configuration".into()) Some("Configuration".into())
} }
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
use anyhow::Result; use anyhow::Result;
use gpui::AppContext; use gpui::App;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{Settings, SettingsSources};
@ -36,7 +36,7 @@ impl Settings for SlashCommandSettings {
type FileContent = Self; type FileContent = Self;
fn load(sources: SettingsSources<Self::FileContent>, _cx: &mut AppContext) -> Result<Self> { fn load(sources: SettingsSources<Self::FileContent>, _cx: &mut App) -> Result<Self> {
SettingsSources::<Self::FileContent>::json_merge_with( SettingsSources::<Self::FileContent>::json_merge_with(
[sources.default] [sources.default]
.into_iter() .into_iter()

View file

@ -11,8 +11,8 @@ use editor::{
use fs::Fs; use fs::Fs;
use futures::{channel::mpsc, SinkExt, StreamExt}; use futures::{channel::mpsc, SinkExt, StreamExt};
use gpui::{ use gpui::{
AppContext, Context, EventEmitter, FocusHandle, FocusableView, Global, Model, ModelContext, App, Context, Entity, EventEmitter, FocusHandle, Focusable, Global, Subscription, Task,
Subscription, Task, TextStyle, UpdateGlobal, View, WeakView, TextStyle, UpdateGlobal, WeakEntity,
}; };
use language::Buffer; use language::Buffer;
use language_model::{ use language_model::{
@ -39,7 +39,7 @@ pub fn init(
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
prompt_builder: Arc<PromptBuilder>, prompt_builder: Arc<PromptBuilder>,
telemetry: Arc<Telemetry>, telemetry: Arc<Telemetry>,
cx: &mut AppContext, cx: &mut App,
) { ) {
cx.set_global(TerminalInlineAssistant::new(fs, prompt_builder, telemetry)); cx.set_global(TerminalInlineAssistant::new(fs, prompt_builder, telemetry));
} }
@ -86,20 +86,20 @@ impl TerminalInlineAssistant {
pub fn assist( pub fn assist(
&mut self, &mut self,
terminal_view: &View<TerminalView>, terminal_view: &Entity<TerminalView>,
workspace: Option<WeakView<Workspace>>, workspace: Option<WeakEntity<Workspace>>,
assistant_panel: Option<&View<AssistantPanel>>, assistant_panel: Option<&Entity<AssistantPanel>>,
initial_prompt: Option<String>, initial_prompt: Option<String>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) { ) {
let terminal = terminal_view.read(cx).terminal().clone(); let terminal = terminal_view.read(cx).terminal().clone();
let assist_id = self.next_assist_id.post_inc(); let assist_id = self.next_assist_id.post_inc();
let prompt_buffer = let prompt_buffer = cx.new(|cx| Buffer::local(initial_prompt.unwrap_or_default(), cx));
cx.new_model(|cx| Buffer::local(initial_prompt.unwrap_or_default(), cx)); let prompt_buffer = cx.new(|cx| MultiBuffer::singleton(prompt_buffer, cx));
let prompt_buffer = cx.new_model(|cx| MultiBuffer::singleton(prompt_buffer, cx)); let codegen = cx.new(|_| Codegen::new(terminal, self.telemetry.clone()));
let codegen = cx.new_model(|_| Codegen::new(terminal, self.telemetry.clone()));
let prompt_editor = cx.new_view(|cx| { let prompt_editor = cx.new(|cx| {
PromptEditor::new( PromptEditor::new(
assist_id, assist_id,
self.prompt_history.clone(), self.prompt_history.clone(),
@ -108,6 +108,7 @@ impl TerminalInlineAssistant {
assistant_panel, assistant_panel,
workspace.clone(), workspace.clone(),
self.fs.clone(), self.fs.clone(),
window,
cx, cx,
) )
}); });
@ -117,7 +118,7 @@ impl TerminalInlineAssistant {
render: Box::new(move |_| prompt_editor_render.clone().into_any_element()), render: Box::new(move |_| prompt_editor_render.clone().into_any_element()),
}; };
terminal_view.update(cx, |terminal_view, cx| { 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( let terminal_assistant = TerminalInlineAssist::new(
@ -126,21 +127,27 @@ impl TerminalInlineAssistant {
assistant_panel.is_some(), assistant_panel.is_some(),
prompt_editor, prompt_editor,
workspace.clone(), workspace.clone(),
window,
cx, cx,
); );
self.assists.insert(assist_id, terminal_assistant); 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]; let assist = &self.assists[&assist_id];
if let Some(prompt_editor) = assist.prompt_editor.as_ref() { if let Some(prompt_editor) = assist.prompt_editor.as_ref() {
prompt_editor.update(cx, |this, cx| { prompt_editor.update(cx, |this, cx| {
this.editor.update(cx, |editor, cx| { this.editor.update(cx, |editor, cx| {
editor.focus(cx); window.focus(&editor.focus_handle(cx));
editor.select_all(&SelectAll, cx); editor.select_all(&SelectAll, window, cx);
}); });
}); });
} }
@ -148,9 +155,10 @@ impl TerminalInlineAssistant {
fn handle_prompt_editor_event( fn handle_prompt_editor_event(
&mut self, &mut self,
prompt_editor: View<PromptEditor>, prompt_editor: Entity<PromptEditor>,
event: &PromptEditorEvent, event: &PromptEditorEvent,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) { ) {
let assist_id = prompt_editor.read(cx).id; let assist_id = prompt_editor.read(cx).id;
match event { match event {
@ -161,21 +169,21 @@ impl TerminalInlineAssistant {
self.stop_assist(assist_id, cx); self.stop_assist(assist_id, cx);
} }
PromptEditorEvent::ConfirmRequested { execute } => { PromptEditorEvent::ConfirmRequested { execute } => {
self.finish_assist(assist_id, false, *execute, cx); self.finish_assist(assist_id, false, *execute, window, cx);
} }
PromptEditorEvent::CancelRequested => { PromptEditorEvent::CancelRequested => {
self.finish_assist(assist_id, true, false, cx); self.finish_assist(assist_id, true, false, window, cx);
} }
PromptEditorEvent::DismissRequested => { PromptEditorEvent::DismissRequested => {
self.dismiss_assist(assist_id, cx); self.dismiss_assist(assist_id, window, cx);
} }
PromptEditorEvent::Resized { height_in_lines } => { 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) { let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
assist assist
} else { } else {
@ -213,7 +221,7 @@ impl TerminalInlineAssistant {
codegen.update(cx, |codegen, cx| codegen.start(request, cx)); 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) { let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
assist assist
} else { } else {
@ -226,7 +234,7 @@ impl TerminalInlineAssistant {
fn request_for_inline_assist( fn request_for_inline_assist(
&self, &self,
assist_id: TerminalInlineAssistId, assist_id: TerminalInlineAssistId,
cx: &mut WindowContext, cx: &mut App,
) -> Result<LanguageModelRequest> { ) -> Result<LanguageModelRequest> {
let assist = self.assists.get(&assist_id).context("invalid assist")?; let assist = self.assists.get(&assist_id).context("invalid assist")?;
@ -296,16 +304,17 @@ impl TerminalInlineAssistant {
assist_id: TerminalInlineAssistId, assist_id: TerminalInlineAssistId,
undo: bool, undo: bool,
execute: 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) { if let Some(assist) = self.assists.remove(&assist_id) {
assist assist
.terminal .terminal
.update(cx, |this, cx| { .update(cx, |this, cx| {
this.clear_block_below_cursor(cx); this.clear_block_below_cursor(cx);
this.focus_handle(cx).focus(cx); this.focus_handle(cx).focus(window);
}) })
.log_err(); .log_err();
@ -348,7 +357,8 @@ impl TerminalInlineAssistant {
fn dismiss_assist( fn dismiss_assist(
&mut self, &mut self,
assist_id: TerminalInlineAssistId, assist_id: TerminalInlineAssistId,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> bool { ) -> bool {
let Some(assist) = self.assists.get_mut(&assist_id) else { let Some(assist) = self.assists.get_mut(&assist_id) else {
return false; return false;
@ -361,7 +371,7 @@ impl TerminalInlineAssistant {
.terminal .terminal
.update(cx, |this, cx| { .update(cx, |this, cx| {
this.clear_block_below_cursor(cx); this.clear_block_below_cursor(cx);
this.focus_handle(cx).focus(cx); this.focus_handle(cx).focus(window);
}) })
.is_ok() .is_ok()
} }
@ -370,7 +380,8 @@ impl TerminalInlineAssistant {
&mut self, &mut self,
assist_id: TerminalInlineAssistId, assist_id: TerminalInlineAssistId,
height: u8, height: u8,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) { ) {
if let Some(assist) = self.assists.get_mut(&assist_id) { if let Some(assist) = self.assists.get_mut(&assist_id) {
if let Some(prompt_editor) = assist.prompt_editor.as_ref().cloned() { if let Some(prompt_editor) = assist.prompt_editor.as_ref().cloned() {
@ -382,7 +393,7 @@ impl TerminalInlineAssistant {
height, height,
render: Box::new(move |_| prompt_editor.clone().into_any_element()), 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(); .log_err();
} }
@ -391,10 +402,10 @@ impl TerminalInlineAssistant {
} }
struct TerminalInlineAssist { struct TerminalInlineAssist {
terminal: WeakView<TerminalView>, terminal: WeakEntity<TerminalView>,
prompt_editor: Option<View<PromptEditor>>, prompt_editor: Option<Entity<PromptEditor>>,
codegen: Model<Codegen>, codegen: Entity<Codegen>,
workspace: Option<WeakView<Workspace>>, workspace: Option<WeakEntity<Workspace>>,
include_context: bool, include_context: bool,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
@ -402,11 +413,12 @@ struct TerminalInlineAssist {
impl TerminalInlineAssist { impl TerminalInlineAssist {
pub fn new( pub fn new(
assist_id: TerminalInlineAssistId, assist_id: TerminalInlineAssistId,
terminal: &View<TerminalView>, terminal: &Entity<TerminalView>,
include_context: bool, include_context: bool,
prompt_editor: View<PromptEditor>, prompt_editor: Entity<PromptEditor>,
workspace: Option<WeakView<Workspace>>, workspace: Option<WeakEntity<Workspace>>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Self { ) -> Self {
let codegen = prompt_editor.read(cx).codegen.clone(); let codegen = prompt_editor.read(cx).codegen.clone();
Self { Self {
@ -416,12 +428,12 @@ impl TerminalInlineAssist {
workspace: workspace.clone(), workspace: workspace.clone(),
include_context, include_context,
_subscriptions: vec![ _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| { 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 { TerminalInlineAssistant::update_global(cx, |this, cx| match event {
CodegenEvent::Finished => { CodegenEvent::Finished => {
let assist = if let Some(assist) = this.assists.get(&assist_id) { let assist = if let Some(assist) = this.assists.get(&assist_id) {
@ -454,7 +466,7 @@ impl TerminalInlineAssist {
} }
if assist.prompt_editor.is_none() { if assist.prompt_editor.is_none() {
this.finish_assist(assist_id, false, false, cx); this.finish_assist(assist_id, false, false, window, cx);
} }
} }
}) })
@ -476,25 +488,25 @@ enum PromptEditorEvent {
struct PromptEditor { struct PromptEditor {
id: TerminalInlineAssistId, id: TerminalInlineAssistId,
height_in_lines: u8, height_in_lines: u8,
editor: View<Editor>, editor: Entity<Editor>,
language_model_selector: View<LanguageModelSelector>, language_model_selector: Entity<LanguageModelSelector>,
edited_since_done: bool, edited_since_done: bool,
prompt_history: VecDeque<String>, prompt_history: VecDeque<String>,
prompt_history_ix: Option<usize>, prompt_history_ix: Option<usize>,
pending_prompt: String, pending_prompt: String,
codegen: Model<Codegen>, codegen: Entity<Codegen>,
_codegen_subscription: Subscription, _codegen_subscription: Subscription,
editor_subscriptions: Vec<Subscription>, editor_subscriptions: Vec<Subscription>,
pending_token_count: Task<Result<()>>, pending_token_count: Task<Result<()>>,
token_count: Option<usize>, token_count: Option<usize>,
_token_count_subscriptions: Vec<Subscription>, _token_count_subscriptions: Vec<Subscription>,
workspace: Option<WeakView<Workspace>>, workspace: Option<WeakEntity<Workspace>>,
} }
impl EventEmitter<PromptEditorEvent> for PromptEditor {} impl EventEmitter<PromptEditorEvent> for PromptEditor {}
impl Render for PromptEditor { impl Render for PromptEditor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let status = &self.codegen.read(cx).status; let status = &self.codegen.read(cx).status;
let buttons = match status { let buttons = match status {
CodegenStatus::Idle => { CodegenStatus::Idle => {
@ -502,16 +514,20 @@ impl Render for PromptEditor {
IconButton::new("cancel", IconName::Close) IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted) .icon_color(Color::Muted)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx)) .tooltip(|window, cx| {
Tooltip::for_action("Cancel Assist", &menu::Cancel, window, cx)
})
.on_click( .on_click(
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)), cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
), ),
IconButton::new("start", IconName::SparkleAlt) IconButton::new("start", IconName::SparkleAlt)
.icon_color(Color::Muted) .icon_color(Color::Muted)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::for_action("Generate", &menu::Confirm, cx)) .tooltip(|window, cx| {
Tooltip::for_action("Generate", &menu::Confirm, window, cx)
})
.on_click( .on_click(
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StartRequested)), cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StartRequested)),
), ),
] ]
} }
@ -520,23 +536,24 @@ impl Render for PromptEditor {
IconButton::new("cancel", IconName::Close) IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted) .icon_color(Color::Muted)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::text("Cancel Assist", cx)) .tooltip(Tooltip::text("Cancel Assist"))
.on_click( .on_click(
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)), cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
), ),
IconButton::new("stop", IconName::Stop) IconButton::new("stop", IconName::Stop)
.icon_color(Color::Error) .icon_color(Color::Error)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.tooltip(|cx| { .tooltip(|window, cx| {
Tooltip::with_meta( Tooltip::with_meta(
"Interrupt Generation", "Interrupt Generation",
Some(&menu::Cancel), Some(&menu::Cancel),
"Changes won't be discarded", "Changes won't be discarded",
window,
cx, cx,
) )
}) })
.on_click( .on_click(
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StopRequested)), cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StopRequested)),
), ),
] ]
} }
@ -544,8 +561,12 @@ impl Render for PromptEditor {
let cancel = IconButton::new("cancel", IconName::Close) let cancel = IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted) .icon_color(Color::Muted)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx)) .tooltip(|window, cx| {
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested))); Tooltip::for_action("Cancel Assist", &menu::Cancel, window, cx)
})
.on_click(
cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
);
let has_error = matches!(status, CodegenStatus::Error(_)); let has_error = matches!(status, CodegenStatus::Error(_));
if has_error || self.edited_since_done { if has_error || self.edited_since_done {
@ -554,15 +575,16 @@ impl Render for PromptEditor {
IconButton::new("restart", IconName::RotateCw) IconButton::new("restart", IconName::RotateCw)
.icon_color(Color::Info) .icon_color(Color::Info)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.tooltip(|cx| { .tooltip(|window, cx| {
Tooltip::with_meta( Tooltip::with_meta(
"Restart Generation", "Restart Generation",
Some(&menu::Confirm), Some(&menu::Confirm),
"Changes will be discarded", "Changes will be discarded",
window,
cx, cx,
) )
}) })
.on_click(cx.listener(|_, _, cx| { .on_click(cx.listener(|_, _, _, cx| {
cx.emit(PromptEditorEvent::StartRequested); cx.emit(PromptEditorEvent::StartRequested);
})), })),
] ]
@ -572,23 +594,29 @@ impl Render for PromptEditor {
IconButton::new("accept", IconName::Check) IconButton::new("accept", IconName::Check)
.icon_color(Color::Info) .icon_color(Color::Info)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.tooltip(|cx| { .tooltip(|window, cx| {
Tooltip::for_action("Accept Generated Command", &menu::Confirm, cx) Tooltip::for_action(
"Accept Generated Command",
&menu::Confirm,
window,
cx,
)
}) })
.on_click(cx.listener(|_, _, cx| { .on_click(cx.listener(|_, _, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false }); cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
})), })),
IconButton::new("confirm", IconName::Play) IconButton::new("confirm", IconName::Play)
.icon_color(Color::Info) .icon_color(Color::Info)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.tooltip(|cx| { .tooltip(|window, cx| {
Tooltip::for_action( Tooltip::for_action(
"Execute Generated Command", "Execute Generated Command",
&menu::SecondaryConfirm, &menu::SecondaryConfirm,
window,
cx, cx,
) )
}) })
.on_click(cx.listener(|_, _, cx| { .on_click(cx.listener(|_, _, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true }); cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
})), })),
] ]
@ -619,7 +647,7 @@ impl Render for PromptEditor {
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.icon_color(Color::Muted) .icon_color(Color::Muted)
.tooltip(move |cx| { .tooltip(move |window, cx| {
Tooltip::with_meta( Tooltip::with_meta(
format!( format!(
"Using {}", "Using {}",
@ -630,6 +658,7 @@ impl Render for PromptEditor {
), ),
None, None,
"Change Model", "Change Model",
window,
cx, cx,
) )
}), }),
@ -640,7 +669,7 @@ impl Render for PromptEditor {
Some( Some(
div() div()
.id("error") .id("error")
.tooltip(move |cx| Tooltip::text(error_message.clone(), cx)) .tooltip(Tooltip::text(error_message))
.child( .child(
Icon::new(IconName::XCircle) Icon::new(IconName::XCircle)
.size(IconSize::Small) .size(IconSize::Small)
@ -663,8 +692,8 @@ impl Render for PromptEditor {
} }
} }
impl FocusableView for PromptEditor { impl Focusable for PromptEditor {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle { fn focus_handle(&self, cx: &App) -> FocusHandle {
self.editor.focus_handle(cx) self.editor.focus_handle(cx)
} }
} }
@ -676,14 +705,15 @@ impl PromptEditor {
fn new( fn new(
id: TerminalInlineAssistId, id: TerminalInlineAssistId,
prompt_history: VecDeque<String>, prompt_history: VecDeque<String>,
prompt_buffer: Model<MultiBuffer>, prompt_buffer: Entity<MultiBuffer>,
codegen: Model<Codegen>, codegen: Entity<Codegen>,
assistant_panel: Option<&View<AssistantPanel>>, assistant_panel: Option<&Entity<AssistantPanel>>,
workspace: Option<WeakView<Workspace>>, workspace: Option<WeakEntity<Workspace>>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) -> Self { ) -> Self {
let prompt_editor = cx.new_view(|cx| { let prompt_editor = cx.new(|cx| {
let mut editor = Editor::new( let mut editor = Editor::new(
EditorMode::AutoHeight { EditorMode::AutoHeight {
max_lines: Self::MAX_LINES as usize, max_lines: Self::MAX_LINES as usize,
@ -691,24 +721,28 @@ impl PromptEditor {
prompt_buffer, prompt_buffer,
None, None,
false, false,
window,
cx, cx,
); );
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx); editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
editor.set_placeholder_text(Self::placeholder_text(cx), cx); editor.set_placeholder_text(Self::placeholder_text(window), cx);
editor editor
}); });
let mut token_count_subscriptions = Vec::new(); let mut token_count_subscriptions = Vec::new();
if let Some(assistant_panel) = assistant_panel { if let Some(assistant_panel) = assistant_panel {
token_count_subscriptions token_count_subscriptions.push(cx.subscribe_in(
.push(cx.subscribe(assistant_panel, Self::handle_assistant_panel_event)); assistant_panel,
window,
Self::handle_assistant_panel_event,
));
} }
let mut this = Self { let mut this = Self {
id, id,
height_in_lines: 1, height_in_lines: 1,
editor: prompt_editor, editor: prompt_editor,
language_model_selector: cx.new_view(|cx| { language_model_selector: cx.new(|cx| {
let fs = fs.clone(); let fs = fs.clone();
LanguageModelSelector::new( LanguageModelSelector::new(
move |model, cx| { move |model, cx| {
@ -718,6 +752,7 @@ impl PromptEditor {
move |settings, _| settings.set_model(model.clone()), move |settings, _| settings.set_model(model.clone()),
); );
}, },
window,
cx, cx,
) )
}), }),
@ -725,7 +760,7 @@ impl PromptEditor {
prompt_history, prompt_history,
prompt_history_ix: None, prompt_history_ix: None,
pending_prompt: String::new(), pending_prompt: String::new(),
_codegen_subscription: cx.observe(&codegen, Self::handle_codegen_changed), _codegen_subscription: cx.observe_in(&codegen, window, Self::handle_codegen_changed),
editor_subscriptions: Vec::new(), editor_subscriptions: Vec::new(),
codegen, codegen,
pending_token_count: Task::ready(Ok(())), pending_token_count: Task::ready(Ok(())),
@ -739,15 +774,15 @@ impl PromptEditor {
this this
} }
fn placeholder_text(cx: &WindowContext) -> String { fn placeholder_text(window: &Window) -> String {
let context_keybinding = text_for_action(&zed_actions::assistant::ToggleFocus, cx) let context_keybinding = text_for_action(&zed_actions::assistant::ToggleFocus, window)
.map(|keybinding| format!("{keybinding} for context")) .map(|keybinding| format!("{keybinding} for context"))
.unwrap_or_default(); .unwrap_or_default();
format!("Generate…{context_keybinding} • ↓↑ for history") format!("Generate…{context_keybinding} • ↓↑ for history")
} }
fn subscribe_to_editor(&mut self, cx: &mut ViewContext<Self>) { fn subscribe_to_editor(&mut self, cx: &mut Context<Self>) {
self.editor_subscriptions.clear(); self.editor_subscriptions.clear();
self.editor_subscriptions self.editor_subscriptions
.push(cx.observe(&self.editor, Self::handle_prompt_editor_changed)); .push(cx.observe(&self.editor, Self::handle_prompt_editor_changed));
@ -755,11 +790,11 @@ impl PromptEditor {
.push(cx.subscribe(&self.editor, Self::handle_prompt_editor_events)); .push(cx.subscribe(&self.editor, Self::handle_prompt_editor_events));
} }
fn prompt(&self, cx: &AppContext) -> String { fn prompt(&self, cx: &App) -> String {
self.editor.read(cx).text(cx) self.editor.read(cx).text(cx)
} }
fn count_lines(&mut self, cx: &mut ViewContext<Self>) { fn count_lines(&mut self, cx: &mut Context<Self>) {
let height_in_lines = cmp::max( let height_in_lines = cmp::max(
2, // Make the editor at least two lines tall, to account for padding and buttons. 2, // Make the editor at least two lines tall, to account for padding and buttons.
cmp::min( cmp::min(
@ -777,15 +812,16 @@ impl PromptEditor {
fn handle_assistant_panel_event( fn handle_assistant_panel_event(
&mut self, &mut self,
_: View<AssistantPanel>, _: &Entity<AssistantPanel>,
event: &AssistantPanelEvent, event: &AssistantPanelEvent,
cx: &mut ViewContext<Self>, _: &mut Window,
cx: &mut Context<Self>,
) { ) {
let AssistantPanelEvent::ContextEdited { .. } = event; let AssistantPanelEvent::ContextEdited { .. } = event;
self.count_tokens(cx); self.count_tokens(cx);
} }
fn count_tokens(&mut self, cx: &mut ViewContext<Self>) { fn count_tokens(&mut self, cx: &mut Context<Self>) {
let assist_id = self.id; let assist_id = self.id;
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else { let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
return; return;
@ -805,15 +841,15 @@ impl PromptEditor {
}) })
} }
fn handle_prompt_editor_changed(&mut self, _: View<Editor>, cx: &mut ViewContext<Self>) { fn handle_prompt_editor_changed(&mut self, _: Entity<Editor>, cx: &mut Context<Self>) {
self.count_lines(cx); self.count_lines(cx);
} }
fn handle_prompt_editor_events( fn handle_prompt_editor_events(
&mut self, &mut self,
_: View<Editor>, _: Entity<Editor>,
event: &EditorEvent, event: &EditorEvent,
cx: &mut ViewContext<Self>, cx: &mut Context<Self>,
) { ) {
match event { match event {
EditorEvent::Edited { .. } => { EditorEvent::Edited { .. } => {
@ -836,7 +872,12 @@ impl PromptEditor {
} }
} }
fn handle_codegen_changed(&mut self, _: Model<Codegen>, cx: &mut ViewContext<Self>) { fn handle_codegen_changed(
&mut self,
_: Entity<Codegen>,
_: &mut Window,
cx: &mut Context<Self>,
) {
match &self.codegen.read(cx).status { match &self.codegen.read(cx).status {
CodegenStatus::Idle => { CodegenStatus::Idle => {
self.editor self.editor
@ -854,7 +895,7 @@ impl PromptEditor {
} }
} }
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) { fn cancel(&mut self, _: &editor::actions::Cancel, _: &mut Window, cx: &mut Context<Self>) {
match &self.codegen.read(cx).status { match &self.codegen.read(cx).status {
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => { CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
cx.emit(PromptEditorEvent::CancelRequested); cx.emit(PromptEditorEvent::CancelRequested);
@ -865,7 +906,7 @@ impl PromptEditor {
} }
} }
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) { fn confirm(&mut self, _: &menu::Confirm, _: &mut Window, cx: &mut Context<Self>) {
match &self.codegen.read(cx).status { match &self.codegen.read(cx).status {
CodegenStatus::Idle => { CodegenStatus::Idle => {
if !self.editor.read(cx).text(cx).trim().is_empty() { if !self.editor.read(cx).text(cx).trim().is_empty() {
@ -888,53 +929,58 @@ impl PromptEditor {
} }
} }
fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) { fn secondary_confirm(
&mut self,
_: &menu::SecondaryConfirm,
_: &mut Window,
cx: &mut Context<Self>,
) {
if matches!(self.codegen.read(cx).status, CodegenStatus::Done) { if matches!(self.codegen.read(cx).status, CodegenStatus::Done) {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true }); cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
} }
} }
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 let Some(ix) = self.prompt_history_ix {
if ix > 0 { if ix > 0 {
self.prompt_history_ix = Some(ix - 1); self.prompt_history_ix = Some(ix - 1);
let prompt = self.prompt_history[ix - 1].as_str(); let prompt = self.prompt_history[ix - 1].as_str();
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx); editor.set_text(prompt, window, cx);
editor.move_to_beginning(&Default::default(), cx); editor.move_to_beginning(&Default::default(), window, cx);
}); });
} }
} else if !self.prompt_history.is_empty() { } else if !self.prompt_history.is_empty() {
self.prompt_history_ix = Some(self.prompt_history.len() - 1); self.prompt_history_ix = Some(self.prompt_history.len() - 1);
let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str(); let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str();
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx); editor.set_text(prompt, window, cx);
editor.move_to_beginning(&Default::default(), 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 let Some(ix) = self.prompt_history_ix {
if ix < self.prompt_history.len() - 1 { if ix < self.prompt_history.len() - 1 {
self.prompt_history_ix = Some(ix + 1); self.prompt_history_ix = Some(ix + 1);
let prompt = self.prompt_history[ix + 1].as_str(); let prompt = self.prompt_history[ix + 1].as_str();
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx); editor.set_text(prompt, window, cx);
editor.move_to_end(&Default::default(), cx) editor.move_to_end(&Default::default(), window, cx)
}); });
} else { } else {
self.prompt_history_ix = None; self.prompt_history_ix = None;
let prompt = self.pending_prompt.as_str(); let prompt = self.pending_prompt.as_str();
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx); editor.set_text(prompt, window, cx);
editor.move_to_end(&Default::default(), cx) editor.move_to_end(&Default::default(), window, cx)
}); });
} }
} }
} }
fn render_token_count(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> { fn render_token_count(&self, cx: &mut Context<Self>) -> Option<impl IntoElement> {
let model = LanguageModelRegistry::read_global(cx).active_model()?; let model = LanguageModelRegistry::read_global(cx).active_model()?;
let token_count = self.token_count?; let token_count = self.token_count?;
let max_token_count = model.max_token_count(); let max_token_count = model.max_token_count();
@ -964,34 +1010,35 @@ impl PromptEditor {
); );
if let Some(workspace) = self.workspace.clone() { if let Some(workspace) = self.workspace.clone() {
token_count = token_count token_count = token_count
.tooltip(|cx| { .tooltip(|window, cx| {
Tooltip::with_meta( Tooltip::with_meta(
"Tokens Used by Inline Assistant", "Tokens Used by Inline Assistant",
None, None,
"Click to Open Assistant Panel", "Click to Open Assistant Panel",
window,
cx, cx,
) )
}) })
.cursor_pointer() .cursor_pointer()
.on_mouse_down(gpui::MouseButton::Left, |_, cx| cx.stop_propagation()) .on_mouse_down(gpui::MouseButton::Left, |_, _, cx| cx.stop_propagation())
.on_click(move |_, cx| { .on_click(move |_, window, cx| {
cx.stop_propagation(); cx.stop_propagation();
workspace workspace
.update(cx, |workspace, cx| { .update(cx, |workspace, cx| {
workspace.focus_panel::<AssistantPanel>(cx) workspace.focus_panel::<AssistantPanel>(window, cx)
}) })
.ok(); .ok();
}); });
} else { } else {
token_count = token_count token_count = token_count
.cursor_default() .cursor_default()
.tooltip(|cx| Tooltip::text("Tokens Used by Inline Assistant", cx)); .tooltip(Tooltip::text("Tokens Used by Inline Assistant"));
} }
Some(token_count) Some(token_count)
} }
fn render_prompt_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx); let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle { let text_style = TextStyle {
color: if self.editor.read(cx).read_only(cx) { color: if self.editor.read(cx).read_only(cx) {
@ -1029,27 +1076,27 @@ const CLEAR_INPUT: &str = "\x15";
const CARRIAGE_RETURN: &str = "\x0d"; const CARRIAGE_RETURN: &str = "\x0d";
struct TerminalTransaction { struct TerminalTransaction {
terminal: Model<Terminal>, terminal: Entity<Terminal>,
} }
impl TerminalTransaction { impl TerminalTransaction {
pub fn start(terminal: Model<Terminal>) -> Self { pub fn start(terminal: Entity<Terminal>) -> Self {
Self { terminal } 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 // Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
let input = Self::sanitize_input(hunk); let input = Self::sanitize_input(hunk);
self.terminal self.terminal
.update(cx, |terminal, _| terminal.input(input)); .update(cx, |terminal, _| terminal.input(input));
} }
pub fn undo(&self, cx: &mut AppContext) { pub fn undo(&self, cx: &mut App) {
self.terminal self.terminal
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.to_string())); .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, _| { self.terminal.update(cx, |terminal, _| {
terminal.input(CARRIAGE_RETURN.to_string()) terminal.input(CARRIAGE_RETURN.to_string())
}); });
@ -1063,14 +1110,14 @@ impl TerminalTransaction {
pub struct Codegen { pub struct Codegen {
status: CodegenStatus, status: CodegenStatus,
telemetry: Option<Arc<Telemetry>>, telemetry: Option<Arc<Telemetry>>,
terminal: Model<Terminal>, terminal: Entity<Terminal>,
generation: Task<()>, generation: Task<()>,
message_id: Option<String>, message_id: Option<String>,
transaction: Option<TerminalTransaction>, transaction: Option<TerminalTransaction>,
} }
impl Codegen { impl Codegen {
pub fn new(terminal: Model<Terminal>, telemetry: Option<Arc<Telemetry>>) -> Self { pub fn new(terminal: Entity<Terminal>, telemetry: Option<Arc<Telemetry>>) -> Self {
Self { Self {
terminal, terminal,
telemetry, telemetry,
@ -1081,7 +1128,7 @@ impl Codegen {
} }
} }
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 { let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
return; return;
}; };
@ -1181,20 +1228,20 @@ impl Codegen {
cx.notify(); 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.status = CodegenStatus::Done;
self.generation = Task::ready(()); self.generation = Task::ready(());
cx.emit(CodegenEvent::Finished); cx.emit(CodegenEvent::Finished);
cx.notify(); 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() { if let Some(transaction) = self.transaction.take() {
transaction.complete(cx); 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() { if let Some(transaction) = self.transaction.take() {
transaction.undo(cx); transaction.undo(cx);
} }

View file

@ -3,9 +3,9 @@ use std::sync::Arc;
use assistant_tool::ToolWorkingSet; use assistant_tool::ToolWorkingSet;
use collections::HashMap; use collections::HashMap;
use gpui::{ use gpui::{
list, AbsoluteLength, AnyElement, AppContext, DefiniteLength, EdgesRefinement, Empty, Length, list, AbsoluteLength, AnyElement, App, DefiniteLength, EdgesRefinement, Empty, Entity, Length,
ListAlignment, ListOffset, ListState, Model, StyleRefinement, Subscription, ListAlignment, ListOffset, ListState, StyleRefinement, Subscription, TextStyleRefinement,
TextStyleRefinement, UnderlineStyle, View, WeakView, UnderlineStyle, WeakEntity,
}; };
use language::LanguageRegistry; use language::LanguageRegistry;
use language_model::Role; use language_model::Role;
@ -20,30 +20,31 @@ use crate::thread_store::ThreadStore;
use crate::ui::ContextPill; use crate::ui::ContextPill;
pub struct ActiveThread { pub struct ActiveThread {
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
language_registry: Arc<LanguageRegistry>, language_registry: Arc<LanguageRegistry>,
tools: Arc<ToolWorkingSet>, tools: Arc<ToolWorkingSet>,
thread_store: Model<ThreadStore>, thread_store: Entity<ThreadStore>,
thread: Model<Thread>, thread: Entity<Thread>,
messages: Vec<MessageId>, messages: Vec<MessageId>,
list_state: ListState, list_state: ListState,
rendered_messages_by_id: HashMap<MessageId, View<Markdown>>, rendered_messages_by_id: HashMap<MessageId, Entity<Markdown>>,
last_error: Option<ThreadError>, last_error: Option<ThreadError>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
impl ActiveThread { impl ActiveThread {
pub fn new( pub fn new(
thread: Model<Thread>, thread: Entity<Thread>,
thread_store: Model<ThreadStore>, thread_store: Entity<ThreadStore>,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
language_registry: Arc<LanguageRegistry>, language_registry: Arc<LanguageRegistry>,
tools: Arc<ToolWorkingSet>, tools: Arc<ToolWorkingSet>,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) -> Self { ) -> Self {
let subscriptions = vec![ let subscriptions = vec![
cx.observe(&thread, |_, _, cx| cx.notify()), 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 { let mut this = Self {
@ -55,8 +56,8 @@ impl ActiveThread {
messages: Vec::new(), messages: Vec::new(),
rendered_messages_by_id: HashMap::default(), rendered_messages_by_id: HashMap::default(),
list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), { list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), {
let this = cx.view().downgrade(); let this = cx.model().downgrade();
move |ix, cx: &mut WindowContext| { move |ix, _: &mut Window, cx: &mut App| {
this.update(cx, |this, cx| this.render_message(ix, cx)) this.update(cx, |this, cx| this.render_message(ix, cx))
.unwrap() .unwrap()
} }
@ -66,13 +67,13 @@ impl ActiveThread {
}; };
for message in thread.read(cx).messages().cloned().collect::<Vec<_>>() { 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 this
} }
pub fn thread(&self) -> &Model<Thread> { pub fn thread(&self) -> &Entity<Thread> {
&self.thread &self.thread
} }
@ -80,15 +81,15 @@ impl ActiveThread {
self.messages.is_empty() 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() 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() 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.last_error.take();
self.thread self.thread
.update(cx, |thread, _cx| thread.cancel_last_completion()) .update(cx, |thread, _cx| thread.cancel_last_completion())
@ -102,7 +103,13 @@ impl ActiveThread {
self.last_error.take(); 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(); let old_len = self.messages.len();
self.messages.push(*id); self.messages.push(*id);
self.list_state.splice(old_len..old_len, 1); self.list_state.splice(old_len..old_len, 1);
@ -111,7 +118,7 @@ impl ActiveThread {
let colors = cx.theme().colors(); let colors = cx.theme().colors();
let ui_font_size = TextSize::Default.rems(cx); let ui_font_size = TextSize::Default.rems(cx);
let buffer_font_size = TextSize::Small.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 { text_style.refine(&TextStyleRefinement {
font_family: Some(theme_settings.ui_font.family.clone()), font_family: Some(theme_settings.ui_font.family.clone()),
@ -170,12 +177,13 @@ impl ActiveThread {
..Default::default() ..Default::default()
}; };
let markdown = cx.new_view(|cx| { let markdown = cx.new(|cx| {
Markdown::new( Markdown::new(
text, text,
markdown_style, markdown_style,
Some(self.language_registry.clone()), Some(self.language_registry.clone()),
None, None,
window,
cx, cx,
) )
}); });
@ -188,9 +196,10 @@ impl ActiveThread {
fn handle_thread_event( fn handle_thread_event(
&mut self, &mut self,
_: Model<Thread>, _: &Entity<Thread>,
event: &ThreadEvent, event: &ThreadEvent,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
match event { match event {
ThreadEvent::ShowError(error) => { ThreadEvent::ShowError(error) => {
@ -206,7 +215,7 @@ impl ActiveThread {
ThreadEvent::StreamedAssistantText(message_id, text) => { ThreadEvent::StreamedAssistantText(message_id, text) => {
if let Some(markdown) = self.rendered_messages_by_id.get_mut(&message_id) { if let Some(markdown) = self.rendered_messages_by_id.get_mut(&message_id) {
markdown.update(cx, |markdown, cx| { markdown.update(cx, |markdown, cx| {
markdown.append(text, cx); markdown.append(text, window, cx);
}); });
} }
} }
@ -217,7 +226,7 @@ impl ActiveThread {
.message(*message_id) .message(*message_id)
.map(|message| message.text.clone()) .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 self.thread_store
@ -240,7 +249,7 @@ impl ActiveThread {
for tool_use in pending_tool_uses { for tool_use in pending_tool_uses {
if let Some(tool) = self.tools.tool(&tool_use.name, cx) { 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| { self.thread.update(cx, |thread, cx| {
thread.insert_tool_output( 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 message_id = self.messages[ix];
let Some(message) = self.thread.read(cx).message(message_id) else { let Some(message) = self.thread.read(cx).message(message_id) else {
return Empty.into_any(); return Empty.into_any();
@ -338,7 +347,7 @@ impl ActiveThread {
} }
impl Render for 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() v_flex()
.size_full() .size_full()
.pt_1p5() .pt_1p5()

View file

@ -24,7 +24,7 @@ use client::Client;
use command_palette_hooks::CommandPaletteFilter; use command_palette_hooks::CommandPaletteFilter;
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt}; use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
use fs::Fs; use fs::Fs;
use gpui::{actions, AppContext}; use gpui::{actions, App};
use prompt_library::PromptBuilder; use prompt_library::PromptBuilder;
use settings::Settings as _; use settings::Settings as _;
@ -63,7 +63,7 @@ pub fn init(
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
client: Arc<Client>, client: Arc<Client>,
prompt_builder: Arc<PromptBuilder>, prompt_builder: Arc<PromptBuilder>,
cx: &mut AppContext, cx: &mut App,
) { ) {
AssistantSettings::register(cx); AssistantSettings::register(cx);
assistant_panel::init(cx); assistant_panel::init(cx);
@ -84,7 +84,7 @@ pub fn init(
feature_gate_assistant2_actions(cx); 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| { CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_namespace(NAMESPACE); filter.hide_namespace(NAMESPACE);
}); });

View file

@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use collections::HashMap; 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 language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
use ui::{prelude::*, ElevationIndex}; use ui::{prelude::*, ElevationIndex};
use zed_actions::assistant::DeployPromptLibrary; use zed_actions::assistant::DeployPromptLibrary;
@ -13,16 +13,17 @@ pub struct AssistantConfiguration {
} }
impl 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 focus_handle = cx.focus_handle();
let registry_subscription = cx.subscribe( let registry_subscription = cx.subscribe_in(
&LanguageModelRegistry::global(cx), &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) => { language_model::Event::AddedProvider(provider_id) => {
let provider = LanguageModelRegistry::read_global(cx).provider(provider_id); let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
if let Some(provider) = provider { 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) => { language_model::Event::RemovedProvider(provider_id) => {
@ -37,14 +38,14 @@ impl AssistantConfiguration {
configuration_views_by_provider: HashMap::default(), configuration_views_by_provider: HashMap::default(),
_registry_subscription: registry_subscription, _registry_subscription: registry_subscription,
}; };
this.build_provider_configuration_views(cx); this.build_provider_configuration_views(window, cx);
this 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(); let providers = LanguageModelRegistry::read_global(cx).providers();
for provider in 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( fn add_provider_configuration_view(
&mut self, &mut self,
provider: &Arc<dyn LanguageModelProvider>, 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 self.configuration_views_by_provider
.insert(provider.id(), configuration_view); .insert(provider.id(), configuration_view);
} }
} }
impl FocusableView for AssistantConfiguration { impl Focusable for AssistantConfiguration {
fn focus_handle(&self, _: &AppContext) -> FocusHandle { fn focus_handle(&self, _: &App) -> FocusHandle {
self.focus_handle.clone() self.focus_handle.clone()
} }
} }
@ -79,7 +81,7 @@ impl AssistantConfiguration {
fn render_provider_configuration( fn render_provider_configuration(
&mut self, &mut self,
provider: &Arc<dyn LanguageModelProvider>, provider: &Arc<dyn LanguageModelProvider>,
cx: &mut ViewContext<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement {
let provider_id = provider.id().0.clone(); let provider_id = provider.id().0.clone();
let provider_name = provider.name().0.clone(); let provider_name = provider.name().0.clone();
@ -107,7 +109,7 @@ impl AssistantConfiguration {
.layer(ElevationIndex::ModalSurface) .layer(ElevationIndex::ModalSurface)
.on_click(cx.listener({ .on_click(cx.listener({
let provider = provider.clone(); let provider = provider.clone();
move |_this, _event, cx| { move |_this, _event, _window, cx| {
cx.emit(AssistantConfigurationEvent::NewThread( cx.emit(AssistantConfigurationEvent::NewThread(
provider.clone(), provider.clone(),
)) ))
@ -135,7 +137,7 @@ impl AssistantConfiguration {
} }
impl Render for 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(); let providers = LanguageModelRegistry::read_global(cx).providers();
v_flex() v_flex()
@ -152,9 +154,7 @@ impl Render for AssistantConfiguration {
.icon(IconName::Book) .icon(IconName::Book)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
.on_click(|_event, cx| { .on_click(|_event, _window, cx| cx.dispatch_action(&DeployPromptLibrary)),
cx.dispatch_action(DeployPromptLibrary.boxed_clone())
}),
), ),
) )
.child( .child(

View file

@ -1,6 +1,6 @@
use assistant_settings::AssistantSettings; use assistant_settings::AssistantSettings;
use fs::Fs; use fs::Fs;
use gpui::{FocusHandle, View}; use gpui::{Entity, FocusHandle};
use language_model::LanguageModelRegistry; use language_model::LanguageModelRegistry;
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu}; use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use settings::update_settings_file; use settings::update_settings_file;
@ -10,7 +10,7 @@ use ui::{prelude::*, ButtonLike, PopoverMenuHandle, Tooltip};
use crate::ToggleModelSelector; use crate::ToggleModelSelector;
pub struct AssistantModelSelector { pub struct AssistantModelSelector {
selector: View<LanguageModelSelector>, selector: Entity<LanguageModelSelector>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>, menu_handle: PopoverMenuHandle<LanguageModelSelector>,
focus_handle: FocusHandle, focus_handle: FocusHandle,
} }
@ -20,10 +20,11 @@ impl AssistantModelSelector {
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>, menu_handle: PopoverMenuHandle<LanguageModelSelector>,
focus_handle: FocusHandle, focus_handle: FocusHandle,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Self { ) -> Self {
Self { Self {
selector: cx.new_view(|cx| { selector: cx.new(|cx| {
let fs = fs.clone(); let fs = fs.clone();
LanguageModelSelector::new( LanguageModelSelector::new(
move |model, cx| { move |model, cx| {
@ -33,6 +34,7 @@ impl AssistantModelSelector {
move |settings, _cx| settings.set_model(model.clone()), move |settings, _cx| settings.set_model(model.clone()),
); );
}, },
window,
cx, cx,
) )
}), }),
@ -43,7 +45,7 @@ impl AssistantModelSelector {
} }
impl Render for 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 active_model = LanguageModelRegistry::read_global(cx).active_model();
let focus_handle = self.focus_handle.clone(); let focus_handle = self.focus_handle.clone();
@ -79,8 +81,14 @@ impl Render for AssistantModelSelector {
.size(IconSize::XSmall), .size(IconSize::XSmall),
), ),
) )
.tooltip(move |cx| { .tooltip(move |window, cx| {
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx) Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
}), }),
) )
.with_handle(self.menu_handle.clone()) .with_handle(self.menu_handle.clone())

View file

@ -14,9 +14,8 @@ use client::zed_urls;
use editor::Editor; use editor::Editor;
use fs::Fs; use fs::Fs;
use gpui::{ use gpui::{
prelude::*, px, svg, Action, AnyElement, AppContext, AsyncWindowContext, Corner, EventEmitter, prelude::*, px, svg, Action, AnyElement, App, AsyncWindowContext, Corner, Entity, EventEmitter,
FocusHandle, FocusableView, FontWeight, Model, Pixels, Subscription, Task, UpdateGlobal, View, FocusHandle, Focusable, FontWeight, Pixels, Subscription, Task, UpdateGlobal, WeakEntity,
ViewContext, WeakView, WindowContext,
}; };
use language::LanguageRegistry; use language::LanguageRegistry;
use language_model::{LanguageModelProviderTosView, LanguageModelRegistry}; use language_model::{LanguageModelProviderTosView, LanguageModelRegistry};
@ -41,38 +40,38 @@ use crate::{
OpenPromptEditorHistory, OpenPromptEditorHistory,
}; };
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut App) {
cx.observe_new_views( cx.observe_new(
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| { |workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {
workspace workspace
.register_action(|workspace, _: &NewThread, cx| { .register_action(|workspace, _: &NewThread, window, cx| {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) { if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
panel.update(cx, |panel, cx| panel.new_thread(cx)); panel.update(cx, |panel, cx| panel.new_thread(window, cx));
workspace.focus_panel::<AssistantPanel>(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) { if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
workspace.focus_panel::<AssistantPanel>(cx); workspace.focus_panel::<AssistantPanel>(window, cx);
panel.update(cx, |panel, cx| panel.open_history(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) { if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
workspace.focus_panel::<AssistantPanel>(cx); workspace.focus_panel::<AssistantPanel>(window, cx);
panel.update(cx, |panel, cx| panel.new_prompt_editor(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) { if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
workspace.focus_panel::<AssistantPanel>(cx); workspace.focus_panel::<AssistantPanel>(window, cx);
panel.update(cx, |panel, cx| panel.open_prompt_editor_history(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) { if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
workspace.focus_panel::<AssistantPanel>(cx); workspace.focus_panel::<AssistantPanel>(window, cx);
panel.update(cx, |panel, cx| panel.open_configuration(cx)); panel.update(cx, |panel, cx| panel.open_configuration(window, cx));
} }
}); });
}, },
@ -89,22 +88,22 @@ enum ActiveView {
} }
pub struct AssistantPanel { pub struct AssistantPanel {
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
project: Model<Project>, project: Entity<Project>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
language_registry: Arc<LanguageRegistry>, language_registry: Arc<LanguageRegistry>,
thread_store: Model<ThreadStore>, thread_store: Entity<ThreadStore>,
thread: View<ActiveThread>, thread: Entity<ActiveThread>,
message_editor: View<MessageEditor>, message_editor: Entity<MessageEditor>,
context_store: Model<assistant_context_editor::ContextStore>, context_store: Entity<assistant_context_editor::ContextStore>,
context_editor: Option<View<ContextEditor>>, context_editor: Option<Entity<ContextEditor>>,
context_history: Option<View<ContextHistory>>, context_history: Option<Entity<ContextHistory>>,
configuration: Option<View<AssistantConfiguration>>, configuration: Option<Entity<AssistantConfiguration>>,
configuration_subscription: Option<Subscription>, configuration_subscription: Option<Subscription>,
tools: Arc<ToolWorkingSet>, tools: Arc<ToolWorkingSet>,
local_timezone: UtcOffset, local_timezone: UtcOffset,
active_view: ActiveView, active_view: ActiveView,
history: View<ThreadHistory>, history: Entity<ThreadHistory>,
new_item_context_menu_handle: PopoverMenuHandle<ContextMenu>, new_item_context_menu_handle: PopoverMenuHandle<ContextMenu>,
open_history_context_menu_handle: PopoverMenuHandle<ContextMenu>, open_history_context_menu_handle: PopoverMenuHandle<ContextMenu>,
width: Option<Pixels>, width: Option<Pixels>,
@ -113,10 +112,10 @@ pub struct AssistantPanel {
impl AssistantPanel { impl AssistantPanel {
pub fn load( pub fn load(
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
prompt_builder: Arc<PromptBuilder>, prompt_builder: Arc<PromptBuilder>,
cx: AsyncWindowContext, cx: AsyncWindowContext,
) -> Task<Result<View<Self>>> { ) -> Task<Result<Entity<Self>>> {
cx.spawn(|mut cx| async move { cx.spawn(|mut cx| async move {
let tools = Arc::new(ToolWorkingSet::default()); let tools = Arc::new(ToolWorkingSet::default());
let thread_store = workspace let thread_store = workspace
@ -140,32 +139,34 @@ impl AssistantPanel {
})? })?
.await?; .await?;
workspace.update(&mut cx, |workspace, cx| { workspace.update_in(&mut cx, |workspace, window, cx| {
cx.new_view(|cx| Self::new(workspace, thread_store, context_store, tools, cx)) cx.new(|cx| Self::new(workspace, thread_store, context_store, tools, window, cx))
}) })
}) })
} }
fn new( fn new(
workspace: &Workspace, workspace: &Workspace,
thread_store: Model<ThreadStore>, thread_store: Entity<ThreadStore>,
context_store: Model<assistant_context_editor::ContextStore>, context_store: Entity<assistant_context_editor::ContextStore>,
tools: Arc<ToolWorkingSet>, tools: Arc<ToolWorkingSet>,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) -> Self { ) -> Self {
let thread = thread_store.update(cx, |this, cx| this.create_thread(cx)); let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
let fs = workspace.app_state().fs.clone(); let fs = workspace.app_state().fs.clone();
let project = workspace.project().clone(); let project = workspace.project().clone();
let language_registry = project.read(cx).languages().clone(); let language_registry = project.read(cx).languages().clone();
let workspace = workspace.weak_handle(); 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( MessageEditor::new(
fs.clone(), fs.clone(),
workspace.clone(), workspace.clone(),
thread_store.downgrade(), thread_store.downgrade(),
thread.clone(), thread.clone(),
window,
cx, cx,
) )
}); });
@ -177,13 +178,14 @@ impl AssistantPanel {
fs: fs.clone(), fs: fs.clone(),
language_registry: language_registry.clone(), language_registry: language_registry.clone(),
thread_store: thread_store.clone(), thread_store: thread_store.clone(),
thread: cx.new_view(|cx| { thread: cx.new(|cx| {
ActiveThread::new( ActiveThread::new(
thread.clone(), thread.clone(),
thread_store.clone(), thread_store.clone(),
workspace, workspace,
language_registry, language_registry,
tools.clone(), tools.clone(),
window,
cx, cx,
) )
}), }),
@ -198,7 +200,7 @@ impl AssistantPanel {
chrono::Local::now().offset().local_minus_utc(), chrono::Local::now().offset().local_minus_utc(),
) )
.unwrap(), .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(), new_item_context_menu_handle: PopoverMenuHandle::default(),
open_history_context_menu_handle: PopoverMenuHandle::default(), open_history_context_menu_handle: PopoverMenuHandle::default(),
width: None, width: None,
@ -209,58 +211,66 @@ impl AssistantPanel {
pub fn toggle_focus( pub fn toggle_focus(
workspace: &mut Workspace, workspace: &mut Workspace,
_: &ToggleFocus, _: &ToggleFocus,
cx: &mut ViewContext<Workspace>, window: &mut Window,
cx: &mut Context<Workspace>,
) { ) {
let settings = AssistantSettings::get_global(cx); let settings = AssistantSettings::get_global(cx);
if !settings.enabled { if !settings.enabled {
return; return;
} }
workspace.toggle_panel_focus::<Self>(cx); workspace.toggle_panel_focus::<Self>(window, cx);
} }
pub(crate) fn local_timezone(&self) -> UtcOffset { pub(crate) fn local_timezone(&self) -> UtcOffset {
self.local_timezone self.local_timezone
} }
pub(crate) fn thread_store(&self) -> &Model<ThreadStore> { pub(crate) fn thread_store(&self) -> &Entity<ThreadStore> {
&self.thread_store &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 self.thread
.update(cx, |thread, cx| thread.cancel_last_completion(cx)); .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 let thread = self
.thread_store .thread_store
.update(cx, |this, cx| this.create_thread(cx)); .update(cx, |this, cx| this.create_thread(cx));
self.active_view = ActiveView::Thread; self.active_view = ActiveView::Thread;
self.thread = cx.new_view(|cx| { self.thread = cx.new(|cx| {
ActiveThread::new( ActiveThread::new(
thread.clone(), thread.clone(),
self.thread_store.clone(), self.thread_store.clone(),
self.workspace.clone(), self.workspace.clone(),
self.language_registry.clone(), self.language_registry.clone(),
self.tools.clone(), self.tools.clone(),
window,
cx, cx,
) )
}); });
self.message_editor = cx.new_view(|cx| { self.message_editor = cx.new(|cx| {
MessageEditor::new( MessageEditor::new(
self.fs.clone(), self.fs.clone(),
self.workspace.clone(), self.workspace.clone(),
self.thread_store.downgrade(), self.thread_store.downgrade(),
thread, thread,
window,
cx, 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; self.active_view = ActiveView::PromptEditor;
let context = self let context = self
@ -270,25 +280,31 @@ impl AssistantPanel {
.log_err() .log_err()
.flatten(); .flatten();
self.context_editor = Some(cx.new_view(|cx| { self.context_editor = Some(cx.new(|cx| {
let mut editor = ContextEditor::for_context( let mut editor = ContextEditor::for_context(
context, context,
self.fs.clone(), self.fs.clone(),
self.workspace.clone(), self.workspace.clone(),
self.project.clone(), self.project.clone(),
lsp_adapter_delegate, lsp_adapter_delegate,
window,
cx, cx,
); );
editor.insert_default_prompt(cx); editor.insert_default_prompt(window, cx);
editor editor
})); }));
if let Some(context_editor) = self.context_editor.as_ref() { 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( open_prompt_library(
self.language_registry.clone(), self.language_registry.clone(),
Box::new(PromptLibraryInlineAssist::new(self.workspace.clone())), Box::new(PromptLibraryInlineAssist::new(self.workspace.clone())),
@ -304,25 +320,26 @@ impl AssistantPanel {
.detach_and_log_err(cx); .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.active_view = ActiveView::History;
self.history.focus_handle(cx).focus(cx); self.history.focus_handle(cx).focus(window);
cx.notify(); 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.active_view = ActiveView::PromptEditorHistory;
self.context_history = Some(cx.new_view(|cx| { self.context_history = Some(cx.new(|cx| {
ContextHistory::new( ContextHistory::new(
self.project.clone(), self.project.clone(),
self.context_store.clone(), self.context_store.clone(),
self.workspace.clone(), self.workspace.clone(),
window,
cx, cx,
) )
})); }));
if let Some(context_history) = self.context_history.as_ref() { 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(); cx.notify();
@ -331,7 +348,8 @@ impl AssistantPanel {
fn open_saved_prompt_editor( fn open_saved_prompt_editor(
&mut self, &mut self,
path: PathBuf, path: PathBuf,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
let context = self let context = self
.context_store .context_store
@ -342,16 +360,17 @@ impl AssistantPanel {
let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err().flatten(); 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?; let context = context.await?;
this.update(&mut cx, |this, cx| { this.update_in(&mut cx, |this, window, cx| {
let editor = cx.new_view(|cx| { let editor = cx.new(|cx| {
ContextEditor::for_context( ContextEditor::for_context(
context, context,
fs, fs,
workspace, workspace,
project, project,
lsp_adapter_delegate, lsp_adapter_delegate,
window,
cx, cx,
) )
}); });
@ -367,57 +386,64 @@ impl AssistantPanel {
pub(crate) fn open_thread( pub(crate) fn open_thread(
&mut self, &mut self,
thread_id: &ThreadId, thread_id: &ThreadId,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
let open_thread_task = self let open_thread_task = self
.thread_store .thread_store
.update(cx, |this, cx| this.open_thread(thread_id, cx)); .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?; 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.active_view = ActiveView::Thread;
this.thread = cx.new_view(|cx| { this.thread = cx.new(|cx| {
ActiveThread::new( ActiveThread::new(
thread.clone(), thread.clone(),
this.thread_store.clone(), this.thread_store.clone(),
this.workspace.clone(), this.workspace.clone(),
this.language_registry.clone(), this.language_registry.clone(),
this.tools.clone(), this.tools.clone(),
window,
cx, cx,
) )
}); });
this.message_editor = cx.new_view(|cx| { this.message_editor = cx.new(|cx| {
MessageEditor::new( MessageEditor::new(
this.fs.clone(), this.fs.clone(),
this.workspace.clone(), this.workspace.clone(),
this.thread_store.downgrade(), this.thread_store.downgrade(),
thread, thread,
window,
cx, 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.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() { if let Some(configuration) = self.configuration.as_ref() {
self.configuration_subscription = self.configuration_subscription = Some(cx.subscribe_in(
Some(cx.subscribe(configuration, Self::handle_assistant_configuration_event)); configuration,
window,
Self::handle_assistant_configuration_event,
));
configuration.focus_handle(cx).focus(cx); configuration.focus_handle(cx).focus(window);
} }
} }
fn handle_assistant_configuration_event( fn handle_assistant_configuration_event(
&mut self, &mut self,
_view: View<AssistantConfiguration>, _model: &Entity<AssistantConfiguration>,
event: &AssistantConfigurationEvent, event: &AssistantConfigurationEvent,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
match event { match event {
AssistantConfigurationEvent::NewThread(provider) => { 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() 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 self.thread_store
.update(cx, |this, cx| this.delete_thread(thread_id, cx)) .update(cx, |this, cx| this.delete_thread(thread_id, cx))
.detach_and_log_err(cx); .detach_and_log_err(cx);
} }
} }
impl FocusableView for AssistantPanel { impl Focusable for AssistantPanel {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle { fn focus_handle(&self, cx: &App) -> FocusHandle {
match self.active_view { match self.active_view {
ActiveView::Thread => self.message_editor.focus_handle(cx), ActiveView::Thread => self.message_editor.focus_handle(cx),
ActiveView::History => self.history.focus_handle(cx), ActiveView::History => self.history.focus_handle(cx),
@ -489,7 +515,7 @@ impl Panel for AssistantPanel {
"AssistantPanel2" "AssistantPanel2"
} }
fn position(&self, cx: &WindowContext) -> DockPosition { fn position(&self, _window: &Window, cx: &App) -> DockPosition {
match AssistantSettings::get_global(cx).dock { match AssistantSettings::get_global(cx).dock {
AssistantDockPosition::Left => DockPosition::Left, AssistantDockPosition::Left => DockPosition::Left,
AssistantDockPosition::Bottom => DockPosition::Bottom, AssistantDockPosition::Bottom => DockPosition::Bottom,
@ -501,7 +527,7 @@ impl Panel for AssistantPanel {
true 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>( settings::update_settings_file::<AssistantSettings>(
self.fs.clone(), self.fs.clone(),
cx, 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); let settings = AssistantSettings::get_global(cx);
match self.position(cx) { match self.position(window, cx) {
DockPosition::Left | DockPosition::Right => { DockPosition::Left | DockPosition::Right => {
self.width.unwrap_or(settings.default_width) 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>) { fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
match self.position(cx) { match self.position(window, cx) {
DockPosition::Left | DockPosition::Right => self.width = size, DockPosition::Left | DockPosition::Right => self.width = size,
DockPosition::Bottom => self.height = size, DockPosition::Bottom => self.height = size,
} }
cx.notify(); 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> { fn remote_id() -> Option<proto::PanelId> {
Some(proto::PanelId::AssistantPanel) 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); let settings = AssistantSettings::get_global(cx);
if !settings.enabled || !settings.button { if !settings.enabled || !settings.button {
return None; return None;
@ -549,7 +575,7 @@ impl Panel for AssistantPanel {
Some(IconName::ZedAssistant) 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") Some("Assistant Panel")
} }
@ -563,7 +589,7 @@ impl Panel for AssistantPanel {
} }
impl 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 thread = self.thread.read(cx);
let title = match self.active_view { let title = match self.active_view {
@ -612,12 +638,12 @@ impl AssistantPanel {
IconButton::new("new", IconName::Plus) IconButton::new("new", IconName::Plus)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.tooltip(|cx| Tooltip::text("New…", cx)), .tooltip(Tooltip::text("New…")),
) )
.anchor(Corner::TopRight) .anchor(Corner::TopRight)
.with_handle(self.new_item_context_menu_handle.clone()) .with_handle(self.new_item_context_menu_handle.clone())
.menu(move |cx| { .menu(move |window, cx| {
Some(ContextMenu::build(cx, |menu, _| { Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
menu.action("New Thread", NewThread.boxed_clone()) menu.action("New Thread", NewThread.boxed_clone())
.action("New Prompt Editor", NewPromptEditor.boxed_clone()) .action("New Prompt Editor", NewPromptEditor.boxed_clone())
})) }))
@ -629,12 +655,12 @@ impl AssistantPanel {
IconButton::new("open-history", IconName::HistoryRerun) IconButton::new("open-history", IconName::HistoryRerun)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.tooltip(|cx| Tooltip::text("History…", cx)), .tooltip(Tooltip::text("History…")),
) )
.anchor(Corner::TopRight) .anchor(Corner::TopRight)
.with_handle(self.open_history_context_menu_handle.clone()) .with_handle(self.open_history_context_menu_handle.clone())
.menu(move |cx| { .menu(move |window, cx| {
Some(ContextMenu::build(cx, |menu, _| { Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
menu.action("Thread History", OpenHistory.boxed_clone()) menu.action("Thread History", OpenHistory.boxed_clone())
.action( .action(
"Prompt Editor History", "Prompt Editor History",
@ -647,23 +673,29 @@ impl AssistantPanel {
IconButton::new("configure-assistant", IconName::Settings) IconButton::new("configure-assistant", IconName::Settings)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.tooltip(move |cx| Tooltip::text("Configure Assistant", cx)) .tooltip(Tooltip::text("Configure Assistant"))
.on_click(move |_event, cx| { .on_click(move |_event, _window, cx| {
cx.dispatch_action(OpenConfiguration.boxed_clone()); 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() { 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 { let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
return Some(ConfigurationError::NoProvider); return Some(ConfigurationError::NoProvider);
}; };
@ -679,7 +711,11 @@ impl AssistantPanel {
None 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 let recent_threads = self
.thread_store .thread_store
.update(cx, |this, _cx| this.recent_threads(3)); .update(cx, |this, _cx| this.recent_threads(3));
@ -729,8 +765,8 @@ impl AssistantPanel {
.icon(Some(IconName::Sliders)) .icon(Some(IconName::Sliders))
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
.on_click(cx.listener(|this, _, cx| { .on_click(cx.listener(|this, _, window, cx| {
this.open_configuration(cx); this.open_configuration(window, cx);
})), })),
), ),
), ),
@ -775,7 +811,7 @@ impl AssistantPanel {
.child(v_flex().mx_auto().w_4_5().gap_2().children( .child(v_flex().mx_auto().w_4_5().gap_2().children(
recent_threads.into_iter().map(|thread| { recent_threads.into_iter().map(|thread| {
// TODO: keyboard navigation // TODO: keyboard navigation
PastThread::new(thread, cx.view().downgrade(), false) PastThread::new(thread, cx.model().downgrade(), false)
}), }),
)) ))
.child( .child(
@ -786,17 +822,17 @@ impl AssistantPanel {
.key_binding(KeyBinding::for_action_in( .key_binding(KeyBinding::for_action_in(
&OpenHistory, &OpenHistory,
&self.focus_handle(cx), &self.focus_handle(cx),
cx, window,
)) ))
.on_click(move |_event, cx| { .on_click(move |_event, window, cx| {
cx.dispatch_action(OpenHistory.boxed_clone()); 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()?; let last_error = self.thread.read(cx).last_error()?;
Some( 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."; 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() v_flex()
@ -846,7 +882,7 @@ impl AssistantPanel {
.justify_end() .justify_end()
.mt_1() .mt_1()
.child(Button::new("subscribe", "Subscribe").on_click(cx.listener( .child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
|this, _, cx| { |this, _, _, cx| {
this.thread.update(cx, |this, _cx| { this.thread.update(cx, |this, _cx| {
this.clear_last_error(); this.clear_last_error();
}); });
@ -856,7 +892,7 @@ impl AssistantPanel {
}, },
))) )))
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener( .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|this, _, cx| { |this, _, _, cx| {
this.thread.update(cx, |this, _cx| { this.thread.update(cx, |this, _cx| {
this.clear_last_error(); this.clear_last_error();
}); });
@ -868,7 +904,7 @@ impl AssistantPanel {
.into_any() .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."; const ERROR_MESSAGE: &str = "You have reached your maximum monthly spend. Increase your spend limit to continue using Zed LLMs.";
v_flex() v_flex()
@ -893,7 +929,7 @@ impl AssistantPanel {
.mt_1() .mt_1()
.child( .child(
Button::new("subscribe", "Update Monthly Spend Limit").on_click( Button::new("subscribe", "Update Monthly Spend Limit").on_click(
cx.listener(|this, _, cx| { cx.listener(|this, _, _, cx| {
this.thread.update(cx, |this, _cx| { this.thread.update(cx, |this, _cx| {
this.clear_last_error(); this.clear_last_error();
}); });
@ -904,7 +940,7 @@ impl AssistantPanel {
), ),
) )
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener( .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|this, _, cx| { |this, _, _, cx| {
this.thread.update(cx, |this, _cx| { this.thread.update(cx, |this, _cx| {
this.clear_last_error(); this.clear_last_error();
}); });
@ -919,7 +955,7 @@ impl AssistantPanel {
fn render_error_message( fn render_error_message(
&self, &self,
error_message: &SharedString, error_message: &SharedString,
cx: &mut ViewContext<Self>, cx: &mut Context<Self>,
) -> AnyElement { ) -> AnyElement {
v_flex() v_flex()
.gap_0p5() .gap_0p5()
@ -945,7 +981,7 @@ impl AssistantPanel {
.justify_end() .justify_end()
.mt_1() .mt_1()
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener( .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|this, _, cx| { |this, _, _, cx| {
this.thread.update(cx, |this, _cx| { this.thread.update(cx, |this, _cx| {
this.clear_last_error(); this.clear_last_error();
}); });
@ -959,23 +995,23 @@ impl AssistantPanel {
} }
impl Render for 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() v_flex()
.key_context("AssistantPanel2") .key_context("AssistantPanel2")
.justify_between() .justify_between()
.size_full() .size_full()
.on_action(cx.listener(Self::cancel)) .on_action(cx.listener(Self::cancel))
.on_action(cx.listener(|this, _: &NewThread, cx| { .on_action(cx.listener(|this, _: &NewThread, window, cx| {
this.new_thread(cx); this.new_thread(window, cx);
})) }))
.on_action(cx.listener(|this, _: &OpenHistory, cx| { .on_action(cx.listener(|this, _: &OpenHistory, window, cx| {
this.open_history(cx); this.open_history(window, cx);
})) }))
.on_action(cx.listener(Self::deploy_prompt_library)) .on_action(cx.listener(Self::deploy_prompt_library))
.child(self.render_toolbar(cx)) .child(self.render_toolbar(cx))
.map(|parent| match self.active_view { .map(|parent| match self.active_view {
ActiveView::Thread => parent ActiveView::Thread => parent
.child(self.render_active_thread_or_empty_state(cx)) .child(self.render_active_thread_or_empty_state(window, cx))
.child( .child(
h_flex() h_flex()
.border_t_1() .border_t_1()
@ -992,11 +1028,11 @@ impl Render for AssistantPanel {
} }
struct PromptLibraryInlineAssist { struct PromptLibraryInlineAssist {
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
} }
impl PromptLibraryInlineAssist { impl PromptLibraryInlineAssist {
pub fn new(workspace: WeakView<Workspace>) -> Self { pub fn new(workspace: WeakEntity<Workspace>) -> Self {
Self { workspace } Self { workspace }
} }
} }
@ -1004,21 +1040,25 @@ impl PromptLibraryInlineAssist {
impl prompt_library::InlineAssistDelegate for PromptLibraryInlineAssist { impl prompt_library::InlineAssistDelegate for PromptLibraryInlineAssist {
fn assist( fn assist(
&self, &self,
prompt_editor: &View<Editor>, prompt_editor: &Entity<Editor>,
_initial_prompt: Option<String>, _initial_prompt: Option<String>,
cx: &mut ViewContext<PromptLibrary>, window: &mut Window,
cx: &mut Context<PromptLibrary>,
) { ) {
InlineAssistant::update_global(cx, |assistant, cx| { 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( fn focus_assistant_panel(
&self, &self,
workspace: &mut Workspace, workspace: &mut Workspace,
cx: &mut ViewContext<Workspace>, window: &mut Window,
cx: &mut Context<Workspace>,
) -> bool { ) -> 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( fn active_context_editor(
&self, &self,
workspace: &mut Workspace, workspace: &mut Workspace,
cx: &mut ViewContext<Workspace>, _window: &mut Window,
) -> Option<View<ContextEditor>> { cx: &mut Context<Workspace>,
) -> Option<Entity<ContextEditor>> {
let panel = workspace.panel::<AssistantPanel>(cx)?; let panel = workspace.panel::<AssistantPanel>(cx)?;
panel.update(cx, |panel, _cx| panel.context_editor.clone()) panel.update(cx, |panel, _cx| panel.context_editor.clone())
} }
@ -1038,21 +1079,25 @@ impl AssistantPanelDelegate for ConcreteAssistantPanelDelegate {
&self, &self,
workspace: &mut Workspace, workspace: &mut Workspace,
path: std::path::PathBuf, path: std::path::PathBuf,
cx: &mut ViewContext<Workspace>, window: &mut Window,
cx: &mut Context<Workspace>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
let Some(panel) = workspace.panel::<AssistantPanel>(cx) else { let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
return Task::ready(Err(anyhow!("Assistant panel not found"))); 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( fn open_remote_context(
&self, &self,
_workspace: &mut Workspace, _workspace: &mut Workspace,
_context_id: assistant_context_editor::ContextId, _context_id: assistant_context_editor::ContextId,
_cx: &mut ViewContext<Workspace>, _window: &mut Window,
) -> Task<Result<View<ContextEditor>>> { _cx: &mut Context<Workspace>,
) -> Task<Result<Entity<ContextEditor>>> {
Task::ready(Err(anyhow!("opening remote context not implemented"))) Task::ready(Err(anyhow!("opening remote context not implemented")))
} }
@ -1060,7 +1105,8 @@ impl AssistantPanelDelegate for ConcreteAssistantPanelDelegate {
&self, &self,
_workspace: &mut Workspace, _workspace: &mut Workspace,
_creases: Vec<(String, String)>, _creases: Vec<(String, String)>,
_cx: &mut ViewContext<Workspace>, _window: &mut Window,
_cx: &mut Context<Workspace>,
) { ) {
} }
} }

View file

@ -6,7 +6,7 @@ use client::telemetry::Telemetry;
use collections::HashSet; use collections::HashSet;
use editor::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint}; use editor::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint};
use futures::{channel::mpsc, future::LocalBoxFuture, join, SinkExt, Stream, StreamExt}; 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::{Buffer, IndentKind, Point, TransactionId};
use language_model::{ use language_model::{
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
@ -32,14 +32,14 @@ use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase}; use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
pub struct BufferCodegen { pub struct BufferCodegen {
alternatives: Vec<Model<CodegenAlternative>>, alternatives: Vec<Entity<CodegenAlternative>>,
pub active_alternative: usize, pub active_alternative: usize,
seen_alternatives: HashSet<usize>, seen_alternatives: HashSet<usize>,
subscriptions: Vec<Subscription>, subscriptions: Vec<Subscription>,
buffer: Model<MultiBuffer>, buffer: Entity<MultiBuffer>,
range: Range<Anchor>, range: Range<Anchor>,
initial_transaction_id: Option<TransactionId>, initial_transaction_id: Option<TransactionId>,
context_store: Model<ContextStore>, context_store: Entity<ContextStore>,
telemetry: Arc<Telemetry>, telemetry: Arc<Telemetry>,
builder: Arc<PromptBuilder>, builder: Arc<PromptBuilder>,
pub is_insertion: bool, pub is_insertion: bool,
@ -47,15 +47,15 @@ pub struct BufferCodegen {
impl BufferCodegen { impl BufferCodegen {
pub fn new( pub fn new(
buffer: Model<MultiBuffer>, buffer: Entity<MultiBuffer>,
range: Range<Anchor>, range: Range<Anchor>,
initial_transaction_id: Option<TransactionId>, initial_transaction_id: Option<TransactionId>,
context_store: Model<ContextStore>, context_store: Entity<ContextStore>,
telemetry: Arc<Telemetry>, telemetry: Arc<Telemetry>,
builder: Arc<PromptBuilder>, builder: Arc<PromptBuilder>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Self { ) -> Self {
let codegen = cx.new_model(|cx| { let codegen = cx.new(|cx| {
CodegenAlternative::new( CodegenAlternative::new(
buffer.clone(), buffer.clone(),
range.clone(), range.clone(),
@ -83,7 +83,7 @@ impl BufferCodegen {
this 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(); let codegen = self.active_alternative().clone();
self.subscriptions.clear(); self.subscriptions.clear();
self.subscriptions self.subscriptions
@ -92,22 +92,22 @@ impl BufferCodegen {
.push(cx.subscribe(&codegen, |_, _, event, cx| cx.emit(*event))); .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] &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 &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) LanguageModelRegistry::read_global(cx)
.inline_alternative_models() .inline_alternative_models()
.len() .len()
+ 1 + 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 { let next_active_ix = if self.active_alternative == 0 {
self.alternatives.len() - 1 self.alternatives.len() - 1
} else { } else {
@ -116,12 +116,12 @@ impl BufferCodegen {
self.activate(next_active_ix, cx); 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(); let next_active_ix = (self.active_alternative + 1) % self.alternatives.len();
self.activate(next_active_ix, cx); 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() self.active_alternative()
.update(cx, |codegen, cx| codegen.set_active(false, cx)); .update(cx, |codegen, cx| codegen.set_active(false, cx));
self.seen_alternatives.insert(index); self.seen_alternatives.insert(index);
@ -132,7 +132,7 @@ impl BufferCodegen {
cx.notify(); 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) let alternative_models = LanguageModelRegistry::read_global(cx)
.inline_alternative_models() .inline_alternative_models()
.to_vec(); .to_vec();
@ -143,7 +143,7 @@ impl BufferCodegen {
self.alternatives.truncate(1); self.alternatives.truncate(1);
for _ in 0..alternative_models.len() { for _ in 0..alternative_models.len() {
self.alternatives.push(cx.new_model(|cx| { self.alternatives.push(cx.new(|cx| {
CodegenAlternative::new( CodegenAlternative::new(
self.buffer.clone(), self.buffer.clone(),
self.range.clone(), self.range.clone(),
@ -172,13 +172,13 @@ impl BufferCodegen {
Ok(()) Ok(())
} }
pub fn stop(&mut self, cx: &mut ModelContext<Self>) { pub fn stop(&mut self, cx: &mut Context<Self>) {
for codegen in &self.alternatives { for codegen in &self.alternatives {
codegen.update(cx, |codegen, cx| codegen.stop(cx)); 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() self.active_alternative()
.update(cx, |codegen, cx| codegen.undo(cx)); .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() 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() 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() 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 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 &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() self.active_alternative().read(cx).last_equal_ranges()
} }
} }
@ -218,8 +218,8 @@ impl BufferCodegen {
impl EventEmitter<CodegenEvent> for BufferCodegen {} impl EventEmitter<CodegenEvent> for BufferCodegen {}
pub struct CodegenAlternative { pub struct CodegenAlternative {
buffer: Model<MultiBuffer>, buffer: Entity<MultiBuffer>,
old_buffer: Model<Buffer>, old_buffer: Entity<Buffer>,
snapshot: MultiBufferSnapshot, snapshot: MultiBufferSnapshot,
edit_position: Option<Anchor>, edit_position: Option<Anchor>,
range: Range<Anchor>, range: Range<Anchor>,
@ -228,7 +228,7 @@ pub struct CodegenAlternative {
status: CodegenStatus, status: CodegenStatus,
generation: Task<()>, generation: Task<()>,
diff: Diff, diff: Diff,
context_store: Option<Model<ContextStore>>, context_store: Option<Entity<ContextStore>>,
telemetry: Option<Arc<Telemetry>>, telemetry: Option<Arc<Telemetry>>,
_subscription: gpui::Subscription, _subscription: gpui::Subscription,
builder: Arc<PromptBuilder>, builder: Arc<PromptBuilder>,
@ -245,13 +245,13 @@ impl EventEmitter<CodegenEvent> for CodegenAlternative {}
impl CodegenAlternative { impl CodegenAlternative {
pub fn new( pub fn new(
buffer: Model<MultiBuffer>, buffer: Entity<MultiBuffer>,
range: Range<Anchor>, range: Range<Anchor>,
active: bool, active: bool,
context_store: Option<Model<ContextStore>>, context_store: Option<Entity<ContextStore>>,
telemetry: Option<Arc<Telemetry>>, telemetry: Option<Arc<Telemetry>>,
builder: Arc<PromptBuilder>, builder: Arc<PromptBuilder>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Self { ) -> Self {
let snapshot = buffer.read(cx).snapshot(cx); let snapshot = buffer.read(cx).snapshot(cx);
@ -259,7 +259,7 @@ impl CodegenAlternative {
.range_to_buffer_ranges(range.clone()) .range_to_buffer_ranges(range.clone())
.pop() .pop()
.unwrap(); .unwrap();
let old_buffer = cx.new_model(|cx| { let old_buffer = cx.new(|cx| {
let text = old_buffer.as_rope().clone(); let text = old_buffer.as_rope().clone();
let line_ending = old_buffer.line_ending(); let line_ending = old_buffer.line_ending();
let language = old_buffer.language().cloned(); 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 { if active != self.active {
self.active = active; self.active = active;
@ -327,9 +327,9 @@ impl CodegenAlternative {
fn handle_buffer_event( fn handle_buffer_event(
&mut self, &mut self,
_buffer: Model<MultiBuffer>, _buffer: Entity<MultiBuffer>,
event: &multi_buffer::Event, event: &multi_buffer::Event,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
if let multi_buffer::Event::TransactionUndone { transaction_id } = event { if let multi_buffer::Event::TransactionUndone { transaction_id } = event {
if self.transformation_transaction_id == Some(*transaction_id) { if self.transformation_transaction_id == Some(*transaction_id) {
@ -348,7 +348,7 @@ impl CodegenAlternative {
&mut self, &mut self,
user_prompt: String, user_prompt: String,
model: Arc<dyn LanguageModel>, model: Arc<dyn LanguageModel>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Result<()> { ) -> Result<()> {
if let Some(transformation_transaction_id) = self.transformation_transaction_id.take() { if let Some(transformation_transaction_id) = self.transformation_transaction_id.take() {
self.buffer.update(cx, |buffer, cx| { self.buffer.update(cx, |buffer, cx| {
@ -375,11 +375,7 @@ impl CodegenAlternative {
Ok(()) Ok(())
} }
fn build_request( fn build_request(&self, user_prompt: String, cx: &mut App) -> Result<LanguageModelRequest> {
&self,
user_prompt: String,
cx: &mut AppContext,
) -> Result<LanguageModelRequest> {
let buffer = self.buffer.read(cx).snapshot(cx); let buffer = self.buffer.read(cx).snapshot(cx);
let language = buffer.language_at(self.range.start); let language = buffer.language_at(self.range.start);
let language_name = if let Some(language) = language.as_ref() { let language_name = if let Some(language) = language.as_ref() {
@ -438,7 +434,7 @@ impl CodegenAlternative {
model_provider_id: String, model_provider_id: String,
model_api_key: Option<String>, model_api_key: Option<String>,
stream: impl 'static + Future<Output = Result<LanguageModelTextStream>>, stream: impl 'static + Future<Output = Result<LanguageModelTextStream>>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
let start_time = Instant::now(); let start_time = Instant::now();
let snapshot = self.snapshot.clone(); let snapshot = self.snapshot.clone();
@ -696,7 +692,7 @@ impl CodegenAlternative {
cx.notify(); 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(); self.last_equal_ranges.clear();
if self.diff.is_empty() { if self.diff.is_empty() {
self.status = CodegenStatus::Idle; self.status = CodegenStatus::Idle;
@ -708,7 +704,7 @@ impl CodegenAlternative {
cx.notify(); 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| { self.buffer.update(cx, |buffer, cx| {
if let Some(transaction_id) = self.transformation_transaction_id.take() { if let Some(transaction_id) = self.transformation_transaction_id.take() {
buffer.undo_transaction(transaction_id, cx); buffer.undo_transaction(transaction_id, cx);
@ -720,7 +716,7 @@ impl CodegenAlternative {
fn apply_edits( fn apply_edits(
&mut self, &mut self,
edits: impl IntoIterator<Item = (Range<Anchor>, String)>, edits: impl IntoIterator<Item = (Range<Anchor>, String)>,
cx: &mut ModelContext<CodegenAlternative>, cx: &mut Context<CodegenAlternative>,
) { ) {
let transaction = self.buffer.update(cx, |buffer, cx| { let transaction = self.buffer.update(cx, |buffer, cx| {
// Avoid grouping assistant edits with user edits. // Avoid grouping assistant edits with user edits.
@ -747,7 +743,7 @@ impl CodegenAlternative {
fn reapply_line_based_diff( fn reapply_line_based_diff(
&mut self, &mut self,
line_operations: impl IntoIterator<Item = LineOperation>, line_operations: impl IntoIterator<Item = LineOperation>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
let old_snapshot = self.snapshot.clone(); let old_snapshot = self.snapshot.clone();
let old_range = self.range.to_point(&old_snapshot); 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_snapshot = self.snapshot.clone();
let old_range = self.range.to_point(&old_snapshot); let old_range = self.range.to_point(&old_snapshot);
let new_snapshot = self.buffer.read(cx).snapshot(cx); let new_snapshot = self.buffer.read(cx).snapshot(cx);
@ -1081,15 +1077,14 @@ mod tests {
} }
} }
"}; "};
let buffer = let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx)); let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let range = buffer.read_with(cx, |buffer, cx| { let range = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx); let snapshot = buffer.snapshot(cx);
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5)) snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5))
}); });
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let codegen = cx.new_model(|cx| { let codegen = cx.new(|cx| {
CodegenAlternative::new( CodegenAlternative::new(
buffer.clone(), buffer.clone(),
range.clone(), range.clone(),
@ -1146,15 +1141,14 @@ mod tests {
le le
} }
"}; "};
let buffer = let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx)); let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let range = buffer.read_with(cx, |buffer, cx| { let range = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx); let snapshot = buffer.snapshot(cx);
snapshot.anchor_before(Point::new(1, 6))..snapshot.anchor_after(Point::new(1, 6)) snapshot.anchor_before(Point::new(1, 6))..snapshot.anchor_after(Point::new(1, 6))
}); });
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let codegen = cx.new_model(|cx| { let codegen = cx.new(|cx| {
CodegenAlternative::new( CodegenAlternative::new(
buffer.clone(), buffer.clone(),
range.clone(), range.clone(),
@ -1214,15 +1208,14 @@ mod tests {
" \n", " \n",
"}\n" // "}\n" //
); );
let buffer = let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx)); let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let range = buffer.read_with(cx, |buffer, cx| { let range = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx); let snapshot = buffer.snapshot(cx);
snapshot.anchor_before(Point::new(1, 2))..snapshot.anchor_after(Point::new(1, 2)) snapshot.anchor_before(Point::new(1, 2))..snapshot.anchor_after(Point::new(1, 2))
}); });
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let codegen = cx.new_model(|cx| { let codegen = cx.new(|cx| {
CodegenAlternative::new( CodegenAlternative::new(
buffer.clone(), buffer.clone(),
range.clone(), range.clone(),
@ -1282,14 +1275,14 @@ mod tests {
\t} \t}
} }
"}; "};
let buffer = cx.new_model(|cx| Buffer::local(text, cx)); let buffer = cx.new(|cx| Buffer::local(text, cx));
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx)); let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let range = buffer.read_with(cx, |buffer, cx| { let range = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx); let snapshot = buffer.snapshot(cx);
snapshot.anchor_before(Point::new(0, 0))..snapshot.anchor_after(Point::new(4, 2)) snapshot.anchor_before(Point::new(0, 0))..snapshot.anchor_after(Point::new(4, 2))
}); });
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let codegen = cx.new_model(|cx| { let codegen = cx.new(|cx| {
CodegenAlternative::new( CodegenAlternative::new(
buffer.clone(), buffer.clone(),
range.clone(), range.clone(),
@ -1337,15 +1330,14 @@ mod tests {
let x = 0; let x = 0;
} }
"}; "};
let buffer = let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx)); let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let range = buffer.read_with(cx, |buffer, cx| { let range = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx); let snapshot = buffer.snapshot(cx);
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 14)) snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 14))
}); });
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let codegen = cx.new_model(|cx| { let codegen = cx.new(|cx| {
CodegenAlternative::new( CodegenAlternative::new(
buffer.clone(), buffer.clone(),
range.clone(), range.clone(),
@ -1432,7 +1424,7 @@ mod tests {
} }
fn simulate_response_stream( fn simulate_response_stream(
codegen: Model<CodegenAlternative>, codegen: Entity<CodegenAlternative>,
cx: &mut TestAppContext, cx: &mut TestAppContext,
) -> mpsc::UnboundedSender<String> { ) -> mpsc::UnboundedSender<String> {
let (chunks_tx, chunks_rx) = mpsc::unbounded(); let (chunks_tx, chunks_rx) = mpsc::unbounded();

View file

@ -2,7 +2,7 @@ use std::path::Path;
use std::rc::Rc; use std::rc::Rc;
use file_icons::FileIcons; use file_icons::FileIcons;
use gpui::{AppContext, Model, SharedString}; use gpui::{App, Entity, SharedString};
use language::Buffer; use language::Buffer;
use language_model::{LanguageModelRequestMessage, MessageContent}; use language_model::{LanguageModelRequestMessage, MessageContent};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -63,14 +63,14 @@ impl ContextKind {
} }
#[derive(Debug)] #[derive(Debug)]
pub enum Context { pub enum AssistantContext {
File(FileContext), File(FileContext),
Directory(DirectoryContext), Directory(DirectoryContext),
FetchedUrl(FetchedUrlContext), FetchedUrl(FetchedUrlContext),
Thread(ThreadContext), Thread(ThreadContext),
} }
impl Context { impl AssistantContext {
pub fn id(&self) -> ContextId { pub fn id(&self) -> ContextId {
match self { match self {
Self::File(file) => file.id, Self::File(file) => file.id,
@ -107,7 +107,7 @@ pub struct FetchedUrlContext {
#[derive(Debug)] #[derive(Debug)]
pub struct ThreadContext { pub struct ThreadContext {
pub id: ContextId, pub id: ContextId,
pub thread: Model<Thread>, pub thread: Entity<Thread>,
pub text: SharedString, pub text: SharedString,
} }
@ -117,13 +117,13 @@ pub struct ThreadContext {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ContextBuffer { pub struct ContextBuffer {
pub id: BufferId, pub id: BufferId,
pub buffer: Model<Buffer>, pub buffer: Entity<Buffer>,
pub version: clock::Global, pub version: clock::Global,
pub text: SharedString, pub text: SharedString,
} }
impl Context { impl AssistantContext {
pub fn snapshot(&self, cx: &AppContext) -> Option<ContextSnapshot> { pub fn snapshot(&self, cx: &App) -> Option<ContextSnapshot> {
match &self { match &self {
Self::File(file_context) => file_context.snapshot(cx), Self::File(file_context) => file_context.snapshot(cx),
Self::Directory(directory_context) => Some(directory_context.snapshot()), Self::Directory(directory_context) => Some(directory_context.snapshot()),
@ -134,7 +134,7 @@ impl Context {
} }
impl FileContext { 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 buffer = self.context_buffer.buffer.read(cx);
let path = buffer_path_log_err(buffer)?; let path = buffer_path_log_err(buffer)?;
let full_path: SharedString = path.to_string_lossy().into_owned().into(); let full_path: SharedString = path.to_string_lossy().into_owned().into();
@ -221,7 +221,7 @@ impl FetchedUrlContext {
} }
impl ThreadContext { impl ThreadContext {
pub fn snapshot(&self, cx: &AppContext) -> ContextSnapshot { pub fn snapshot(&self, cx: &App) -> ContextSnapshot {
let thread = self.thread.read(cx); let thread = self.thread.read(cx);
ContextSnapshot { ContextSnapshot {
id: self.id, id: self.id,

View file

@ -9,10 +9,7 @@ use std::sync::Arc;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use editor::Editor; use editor::Editor;
use file_context_picker::render_file_context_entry; use file_context_picker::render_file_context_entry;
use gpui::{ use gpui::{App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Task, WeakEntity};
AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Task, View, WeakModel,
WeakView,
};
use project::ProjectPath; use project::ProjectPath;
use thread_context_picker::{render_thread_context_entry, ThreadContextEntry}; use thread_context_picker::{render_thread_context_entry, ThreadContextEntry};
use ui::{prelude::*, ContextMenu, ContextMenuEntry, ContextMenuItem}; use ui::{prelude::*, ContextMenu, ContextMenuEntry, ContextMenuItem};
@ -35,33 +32,38 @@ pub enum ConfirmBehavior {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum ContextPickerMode { enum ContextPickerMode {
Default(View<ContextMenu>), Default(Entity<ContextMenu>),
File(View<FileContextPicker>), File(Entity<FileContextPicker>),
Directory(View<DirectoryContextPicker>), Directory(Entity<DirectoryContextPicker>),
Fetch(View<FetchContextPicker>), Fetch(Entity<FetchContextPicker>),
Thread(View<ThreadContextPicker>), Thread(Entity<ThreadContextPicker>),
} }
pub(super) struct ContextPicker { pub(super) struct ContextPicker {
mode: ContextPickerMode, mode: ContextPickerMode,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
editor: WeakView<Editor>, editor: WeakEntity<Editor>,
context_store: WeakModel<ContextStore>, context_store: WeakEntity<ContextStore>,
thread_store: Option<WeakModel<ThreadStore>>, thread_store: Option<WeakEntity<ThreadStore>>,
confirm_behavior: ConfirmBehavior, confirm_behavior: ConfirmBehavior,
} }
impl ContextPicker { impl ContextPicker {
pub fn new( pub fn new(
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>, thread_store: Option<WeakEntity<ThreadStore>>,
context_store: WeakModel<ContextStore>, context_store: WeakEntity<ContextStore>,
editor: WeakView<Editor>, editor: WeakEntity<Editor>,
confirm_behavior: ConfirmBehavior, confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) -> Self { ) -> Self {
ContextPicker { ContextPicker {
mode: ContextPickerMode::Default(ContextMenu::build(cx, |menu, _cx| menu)), mode: ContextPickerMode::Default(ContextMenu::build(
window,
cx,
|menu, _window, _cx| menu,
)),
workspace, workspace,
context_store, context_store,
thread_store, thread_store,
@ -70,15 +72,15 @@ impl ContextPicker {
} }
} }
pub fn init(&mut self, cx: &mut ViewContext<Self>) { pub fn init(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.mode = ContextPickerMode::Default(self.build_menu(cx)); self.mode = ContextPickerMode::Default(self.build_menu(window, cx));
cx.notify(); cx.notify();
} }
fn build_menu(&mut self, cx: &mut ViewContext<Self>) -> View<ContextMenu> { fn build_menu(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Entity<ContextMenu> {
let context_picker = cx.view().clone(); 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 recent = self.recent_entries(cx);
let has_recent = !recent.is_empty(); let has_recent = !recent.is_empty();
let recent_entries = recent let recent_entries = recent
@ -97,7 +99,7 @@ impl ContextPicker {
let menu = menu let menu = menu
.when(has_recent, |menu| { .when(has_recent, |menu| {
menu.custom_row(|_| { menu.custom_row(|_, _| {
div() div()
.mb_1() .mb_1()
.child( .child(
@ -117,8 +119,8 @@ impl ContextPicker {
.icon(kind.icon()) .icon(kind.icon())
.icon_size(IconSize::XSmall) .icon_size(IconSize::XSmall)
.icon_color(Color::Muted) .icon_color(Color::Muted)
.handler(move |cx| { .handler(move |window, cx| {
context_picker.update(cx, |this, cx| this.select_kind(kind, cx)) context_picker.update(cx, |this, cx| this.select_kind(kind, window, cx))
}) })
})); }));
@ -141,52 +143,56 @@ impl ContextPicker {
self.thread_store.is_some() self.thread_store.is_some()
} }
fn select_kind(&mut self, kind: ContextKind, cx: &mut ViewContext<Self>) { fn select_kind(&mut self, kind: ContextKind, window: &mut Window, cx: &mut Context<Self>) {
let context_picker = cx.view().downgrade(); let context_picker = cx.model().downgrade();
match kind { match kind {
ContextKind::File => { ContextKind::File => {
self.mode = ContextPickerMode::File(cx.new_view(|cx| { self.mode = ContextPickerMode::File(cx.new(|cx| {
FileContextPicker::new( FileContextPicker::new(
context_picker.clone(), context_picker.clone(),
self.workspace.clone(), self.workspace.clone(),
self.editor.clone(), self.editor.clone(),
self.context_store.clone(), self.context_store.clone(),
self.confirm_behavior, self.confirm_behavior,
window,
cx, cx,
) )
})); }));
} }
ContextKind::Directory => { ContextKind::Directory => {
self.mode = ContextPickerMode::Directory(cx.new_view(|cx| { self.mode = ContextPickerMode::Directory(cx.new(|cx| {
DirectoryContextPicker::new( DirectoryContextPicker::new(
context_picker.clone(), context_picker.clone(),
self.workspace.clone(), self.workspace.clone(),
self.context_store.clone(), self.context_store.clone(),
self.confirm_behavior, self.confirm_behavior,
window,
cx, cx,
) )
})); }));
} }
ContextKind::FetchedUrl => { ContextKind::FetchedUrl => {
self.mode = ContextPickerMode::Fetch(cx.new_view(|cx| { self.mode = ContextPickerMode::Fetch(cx.new(|cx| {
FetchContextPicker::new( FetchContextPicker::new(
context_picker.clone(), context_picker.clone(),
self.workspace.clone(), self.workspace.clone(),
self.context_store.clone(), self.context_store.clone(),
self.confirm_behavior, self.confirm_behavior,
window,
cx, cx,
) )
})); }));
} }
ContextKind::Thread => { ContextKind::Thread => {
if let Some(thread_store) = self.thread_store.as_ref() { 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( ThreadContextPicker::new(
thread_store.clone(), thread_store.clone(),
context_picker.clone(), context_picker.clone(),
self.context_store.clone(), self.context_store.clone(),
self.confirm_behavior, self.confirm_behavior,
window,
cx, cx,
) )
})); }));
@ -195,12 +201,12 @@ impl ContextPicker {
} }
cx.notify(); cx.notify();
cx.focus_self(); cx.focus_self(window);
} }
fn recent_menu_item( fn recent_menu_item(
&self, &self,
context_picker: View<ContextPicker>, context_picker: Entity<ContextPicker>,
ix: usize, ix: usize,
entry: RecentEntry, entry: RecentEntry,
) -> ContextMenuItem { ) -> ContextMenuItem {
@ -213,7 +219,7 @@ impl ContextPicker {
let path = project_path.path.clone(); let path = project_path.path.clone();
ContextMenuItem::custom_entry( ContextMenuItem::custom_entry(
move |cx| { move |_window, cx| {
render_file_context_entry( render_file_context_entry(
ElementId::NamedInteger("ctx-recent".into(), ix), ElementId::NamedInteger("ctx-recent".into(), ix),
&path, &path,
@ -223,9 +229,9 @@ impl ContextPicker {
) )
.into_any() .into_any()
}, },
move |cx| { move |window, cx| {
context_picker.update(cx, |this, 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(); let view_thread = thread.clone();
ContextMenuItem::custom_entry( ContextMenuItem::custom_entry(
move |cx| { move |_window, cx| {
render_thread_context_entry(&view_thread, context_store.clone(), cx) render_thread_context_entry(&view_thread, context_store.clone(), cx)
.into_any() .into_any()
}, },
move |cx| { move |_window, cx| {
context_picker.update(cx, |this, cx| { context_picker.update(cx, |this, cx| {
this.add_recent_thread(thread.clone(), cx) this.add_recent_thread(thread.clone(), cx)
.detach_and_log_err(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 { let Some(context_store) = self.context_store.upgrade() else {
return; return;
}; };
@ -259,8 +270,10 @@ impl ContextPicker {
context_store.add_file_from_path(project_path.clone(), cx) context_store.add_file_from_path(project_path.clone(), cx)
}); });
cx.spawn(|_, mut cx| async move { task.await.notify_async_err(&mut cx) }) cx.spawn_in(window, |_, mut cx| async move {
.detach(); task.await.notify_async_err(&mut cx)
})
.detach();
cx.notify(); cx.notify();
} }
@ -268,7 +281,7 @@ impl ContextPicker {
fn add_recent_thread( fn add_recent_thread(
&self, &self,
thread: ThreadContextEntry, thread: ThreadContextEntry,
cx: &mut ViewContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
let Some(context_store) = self.context_store.upgrade() else { let Some(context_store) = self.context_store.upgrade() else {
return Task::ready(Err(anyhow!("context store not available"))); 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 { let Some(workspace) = self.workspace.upgrade().map(|w| w.read(cx)) else {
return vec![]; return vec![];
}; };
@ -363,7 +376,7 @@ impl ContextPicker {
recent 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 active_item = workspace.active_item(cx)?;
let editor = active_item.to_any().downcast::<Editor>().ok()?.read(cx); let editor = active_item.to_any().downcast::<Editor>().ok()?.read(cx);
@ -376,8 +389,8 @@ impl ContextPicker {
impl EventEmitter<DismissEvent> for ContextPicker {} impl EventEmitter<DismissEvent> for ContextPicker {}
impl FocusableView for ContextPicker { impl Focusable for ContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle { fn focus_handle(&self, cx: &App) -> FocusHandle {
match &self.mode { match &self.mode {
ContextPickerMode::Default(menu) => menu.focus_handle(cx), ContextPickerMode::Default(menu) => menu.focus_handle(cx),
ContextPickerMode::File(file_picker) => file_picker.focus_handle(cx), ContextPickerMode::File(file_picker) => file_picker.focus_handle(cx),
@ -389,7 +402,7 @@ impl FocusableView for ContextPicker {
} }
impl Render 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() v_flex()
.w(px(400.)) .w(px(400.))
.min_w(px(400.)) .min_w(px(400.))

View file

@ -3,7 +3,7 @@ use std::sync::atomic::AtomicBool;
use std::sync::Arc; use std::sync::Arc;
use fuzzy::PathMatch; 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 picker::{Picker, PickerDelegate};
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId}; use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
use ui::{prelude::*, ListItem}; use ui::{prelude::*, ListItem};
@ -14,16 +14,17 @@ use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore; use crate::context_store::ContextStore;
pub struct DirectoryContextPicker { pub struct DirectoryContextPicker {
picker: View<Picker<DirectoryContextPickerDelegate>>, picker: Entity<Picker<DirectoryContextPickerDelegate>>,
} }
impl DirectoryContextPicker { impl DirectoryContextPicker {
pub fn new( pub fn new(
context_picker: WeakView<ContextPicker>, context_picker: WeakEntity<ContextPicker>,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
context_store: WeakModel<ContextStore>, context_store: WeakEntity<ContextStore>,
confirm_behavior: ConfirmBehavior, confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) -> Self { ) -> Self {
let delegate = DirectoryContextPickerDelegate::new( let delegate = DirectoryContextPickerDelegate::new(
context_picker, context_picker,
@ -31,28 +32,28 @@ impl DirectoryContextPicker {
context_store, context_store,
confirm_behavior, 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 } Self { picker }
} }
} }
impl FocusableView for DirectoryContextPicker { impl Focusable for DirectoryContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle { fn focus_handle(&self, cx: &App) -> FocusHandle {
self.picker.focus_handle(cx) self.picker.focus_handle(cx)
} }
} }
impl Render for DirectoryContextPicker { 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() self.picker.clone()
} }
} }
pub struct DirectoryContextPickerDelegate { pub struct DirectoryContextPickerDelegate {
context_picker: WeakView<ContextPicker>, context_picker: WeakEntity<ContextPicker>,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
context_store: WeakModel<ContextStore>, context_store: WeakEntity<ContextStore>,
confirm_behavior: ConfirmBehavior, confirm_behavior: ConfirmBehavior,
matches: Vec<PathMatch>, matches: Vec<PathMatch>,
selected_index: usize, selected_index: usize,
@ -60,9 +61,9 @@ pub struct DirectoryContextPickerDelegate {
impl DirectoryContextPickerDelegate { impl DirectoryContextPickerDelegate {
pub fn new( pub fn new(
context_picker: WeakView<ContextPicker>, context_picker: WeakEntity<ContextPicker>,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
context_store: WeakModel<ContextStore>, context_store: WeakEntity<ContextStore>,
confirm_behavior: ConfirmBehavior, confirm_behavior: ConfirmBehavior,
) -> Self { ) -> Self {
Self { Self {
@ -79,8 +80,8 @@ impl DirectoryContextPickerDelegate {
&mut self, &mut self,
query: String, query: String,
cancellation_flag: Arc<AtomicBool>, cancellation_flag: Arc<AtomicBool>,
workspace: &View<Workspace>, workspace: &Entity<Workspace>,
cx: &mut ViewContext<Picker<Self>>, cx: &mut Context<Picker<Self>>,
) -> Task<Vec<PathMatch>> { ) -> Task<Vec<PathMatch>> {
if query.is_empty() { if query.is_empty() {
let workspace = workspace.read(cx); let workspace = workspace.read(cx);
@ -146,15 +147,25 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
self.selected_index 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; 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() "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 { let Some(workspace) = self.workspace.upgrade() else {
return Task::ready(()); 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 { let Some(mat) = self.matches.get(self.selected_index) else {
return; return;
}; };
@ -194,19 +205,19 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
}; };
let confirm_behavior = self.confirm_behavior; 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) { match task.await.notify_async_err(&mut cx) {
None => anyhow::Ok(()), 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::KeepOpen => {}
ConfirmBehavior::Close => this.delegate.dismissed(cx), ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
}), }),
} }
}) })
.detach_and_log_err(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 self.context_picker
.update(cx, |_, cx| { .update(cx, |_, cx| {
cx.emit(DismissEvent); cx.emit(DismissEvent);
@ -218,7 +229,8 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
&self, &self,
ix: usize, ix: usize,
selected: bool, selected: bool,
cx: &mut ViewContext<Picker<Self>>, _window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> { ) -> Option<Self::ListItem> {
let path_match = &self.matches[ix]; let path_match = &self.matches[ix];
let directory_name = path_match.path.to_string_lossy().to_string(); let directory_name = path_match.path.to_string_lossy().to_string();

View file

@ -4,27 +4,28 @@ use std::sync::Arc;
use anyhow::{bail, Context as _, Result}; use anyhow::{bail, Context as _, Result};
use futures::AsyncReadExt as _; 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 html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
use http_client::{AsyncBody, HttpClientWithUrl}; use http_client::{AsyncBody, HttpClientWithUrl};
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use ui::{prelude::*, ListItem, ViewContext}; use ui::{prelude::*, Context, ListItem, Window};
use workspace::Workspace; use workspace::Workspace;
use crate::context_picker::{ConfirmBehavior, ContextPicker}; use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore; use crate::context_store::ContextStore;
pub struct FetchContextPicker { pub struct FetchContextPicker {
picker: View<Picker<FetchContextPickerDelegate>>, picker: Entity<Picker<FetchContextPickerDelegate>>,
} }
impl FetchContextPicker { impl FetchContextPicker {
pub fn new( pub fn new(
context_picker: WeakView<ContextPicker>, context_picker: WeakEntity<ContextPicker>,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
context_store: WeakModel<ContextStore>, context_store: WeakEntity<ContextStore>,
confirm_behavior: ConfirmBehavior, confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) -> Self { ) -> Self {
let delegate = FetchContextPickerDelegate::new( let delegate = FetchContextPickerDelegate::new(
context_picker, context_picker,
@ -32,20 +33,20 @@ impl FetchContextPicker {
context_store, context_store,
confirm_behavior, 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 } Self { picker }
} }
} }
impl FocusableView for FetchContextPicker { impl Focusable for FetchContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle { fn focus_handle(&self, cx: &App) -> FocusHandle {
self.picker.focus_handle(cx) self.picker.focus_handle(cx)
} }
} }
impl Render for FetchContextPicker { 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() self.picker.clone()
} }
} }
@ -58,18 +59,18 @@ enum ContentType {
} }
pub struct FetchContextPickerDelegate { pub struct FetchContextPickerDelegate {
context_picker: WeakView<ContextPicker>, context_picker: WeakEntity<ContextPicker>,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
context_store: WeakModel<ContextStore>, context_store: WeakEntity<ContextStore>,
confirm_behavior: ConfirmBehavior, confirm_behavior: ConfirmBehavior,
url: String, url: String,
} }
impl FetchContextPickerDelegate { impl FetchContextPickerDelegate {
pub fn new( pub fn new(
context_picker: WeakView<ContextPicker>, context_picker: WeakEntity<ContextPicker>,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
context_store: WeakModel<ContextStore>, context_store: WeakEntity<ContextStore>,
confirm_behavior: ConfirmBehavior, confirm_behavior: ConfirmBehavior,
) -> Self { ) -> Self {
FetchContextPickerDelegate { 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() "Enter the URL that you would like to fetch".into()
} }
@ -174,19 +175,30 @@ impl PickerDelegate for FetchContextPickerDelegate {
0 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() "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; self.url = query;
Task::ready(()) 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 { let Some(workspace) = self.workspace.upgrade() else {
return; return;
}; };
@ -194,13 +206,13 @@ impl PickerDelegate for FetchContextPickerDelegate {
let http_client = workspace.read(cx).client().http_client().clone(); let http_client = workspace.read(cx).client().http_client().clone();
let url = self.url.clone(); let url = self.url.clone();
let confirm_behavior = self.confirm_behavior; 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 let text = cx
.background_executor() .background_executor()
.spawn(Self::build_message(http_client, url.clone())) .spawn(Self::build_message(http_client, url.clone()))
.await?; .await?;
this.update(&mut cx, |this, cx| { this.update_in(&mut cx, |this, window, cx| {
this.delegate this.delegate
.context_store .context_store
.update(cx, |context_store, _cx| { .update(cx, |context_store, _cx| {
@ -209,7 +221,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
match confirm_behavior { match confirm_behavior {
ConfirmBehavior::KeepOpen => {} ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => this.delegate.dismissed(cx), ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
} }
anyhow::Ok(()) anyhow::Ok(())
@ -220,7 +232,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
.detach_and_log_err(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 self.context_picker
.update(cx, |_, cx| { .update(cx, |_, cx| {
cx.emit(DismissEvent); cx.emit(DismissEvent);
@ -232,7 +244,8 @@ impl PickerDelegate for FetchContextPickerDelegate {
&self, &self,
ix: usize, ix: usize,
selected: bool, selected: bool,
cx: &mut ViewContext<Picker<Self>>, _window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> { ) -> Option<Self::ListItem> {
let added = self.context_store.upgrade().map_or(false, |context_store| { let added = self.context_store.upgrade().map_or(false, |context_store| {
context_store.read(cx).includes_url(&self.url).is_some() context_store.read(cx).includes_url(&self.url).is_some()

View file

@ -11,8 +11,8 @@ use editor::{Anchor, Editor, FoldPlaceholder, ToPoint};
use file_icons::FileIcons; use file_icons::FileIcons;
use fuzzy::PathMatch; use fuzzy::PathMatch;
use gpui::{ use gpui::{
AnyElement, AppContext, DismissEvent, Empty, FocusHandle, FocusableView, Stateful, Task, View, AnyElement, App, DismissEvent, Empty, Entity, FocusHandle, Focusable, Stateful, Task,
WeakModel, WeakView, WeakEntity,
}; };
use multi_buffer::{MultiBufferPoint, MultiBufferRow}; use multi_buffer::{MultiBufferPoint, MultiBufferRow};
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
@ -27,17 +27,18 @@ use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::{ContextStore, FileInclusion}; use crate::context_store::{ContextStore, FileInclusion};
pub struct FileContextPicker { pub struct FileContextPicker {
picker: View<Picker<FileContextPickerDelegate>>, picker: Entity<Picker<FileContextPickerDelegate>>,
} }
impl FileContextPicker { impl FileContextPicker {
pub fn new( pub fn new(
context_picker: WeakView<ContextPicker>, context_picker: WeakEntity<ContextPicker>,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
editor: WeakView<Editor>, editor: WeakEntity<Editor>,
context_store: WeakModel<ContextStore>, context_store: WeakEntity<ContextStore>,
confirm_behavior: ConfirmBehavior, confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) -> Self { ) -> Self {
let delegate = FileContextPickerDelegate::new( let delegate = FileContextPickerDelegate::new(
context_picker, context_picker,
@ -46,29 +47,29 @@ impl FileContextPicker {
context_store, context_store,
confirm_behavior, 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 } Self { picker }
} }
} }
impl FocusableView for FileContextPicker { impl Focusable for FileContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle { fn focus_handle(&self, cx: &App) -> FocusHandle {
self.picker.focus_handle(cx) self.picker.focus_handle(cx)
} }
} }
impl Render for FileContextPicker { 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() self.picker.clone()
} }
} }
pub struct FileContextPickerDelegate { pub struct FileContextPickerDelegate {
context_picker: WeakView<ContextPicker>, context_picker: WeakEntity<ContextPicker>,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
editor: WeakView<Editor>, editor: WeakEntity<Editor>,
context_store: WeakModel<ContextStore>, context_store: WeakEntity<ContextStore>,
confirm_behavior: ConfirmBehavior, confirm_behavior: ConfirmBehavior,
matches: Vec<PathMatch>, matches: Vec<PathMatch>,
selected_index: usize, selected_index: usize,
@ -76,10 +77,10 @@ pub struct FileContextPickerDelegate {
impl FileContextPickerDelegate { impl FileContextPickerDelegate {
pub fn new( pub fn new(
context_picker: WeakView<ContextPicker>, context_picker: WeakEntity<ContextPicker>,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
editor: WeakView<Editor>, editor: WeakEntity<Editor>,
context_store: WeakModel<ContextStore>, context_store: WeakEntity<ContextStore>,
confirm_behavior: ConfirmBehavior, confirm_behavior: ConfirmBehavior,
) -> Self { ) -> Self {
Self { Self {
@ -97,8 +98,9 @@ impl FileContextPickerDelegate {
&mut self, &mut self,
query: String, query: String,
cancellation_flag: Arc<AtomicBool>, cancellation_flag: Arc<AtomicBool>,
workspace: &View<Workspace>, workspace: &Entity<Workspace>,
cx: &mut ViewContext<Picker<Self>>,
cx: &mut Context<Picker<Self>>,
) -> Task<Vec<PathMatch>> { ) -> Task<Vec<PathMatch>> {
if query.is_empty() { if query.is_empty() {
let workspace = workspace.read(cx); let workspace = workspace.read(cx);
@ -180,22 +182,32 @@ impl PickerDelegate for FileContextPickerDelegate {
self.selected_index 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; 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() "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 { let Some(workspace) = self.workspace.upgrade() else {
return Task::ready(()); return Task::ready(());
}; };
let search_task = self.search(query, Arc::<AtomicBool>::default(), &workspace, cx); 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. // TODO: This should be probably be run in the background.
let paths = search_task.await; 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 { let Some(mat) = self.matches.get(self.selected_index) else {
return; return;
}; };
@ -231,7 +243,7 @@ impl PickerDelegate for FileContextPickerDelegate {
}; };
editor.update(cx, |editor, cx| { 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. // 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); 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 = { let start_anchors = {
@ -260,7 +274,7 @@ impl PickerDelegate for FileContextPickerDelegate {
.collect::<Vec<_>>() .collect::<Vec<_>>()
}; };
editor.insert(&full_path, cx); editor.insert(&full_path, window, cx);
let end_anchors = { let end_anchors = {
let snapshot = editor.buffer().read(cx).snapshot(cx); let snapshot = editor.buffer().read(cx).snapshot(cx);
@ -272,14 +286,15 @@ impl PickerDelegate for FileContextPickerDelegate {
.collect::<Vec<_>>() .collect::<Vec<_>>()
}; };
editor.insert("\n", cx); // Needed to end the fold editor.insert("\n", window, cx); // Needed to end the fold
let placeholder = FoldPlaceholder { let placeholder = FoldPlaceholder {
render: render_fold_icon_button(IconName::File, file_name.into()), render: render_fold_icon_button(IconName::File, file_name.into()),
..Default::default() ..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 buffer = editor.buffer().read(cx).snapshot(cx);
let mut rows_to_fold = BTreeSet::new(); let mut rows_to_fold = BTreeSet::new();
@ -300,7 +315,7 @@ impl PickerDelegate for FileContextPickerDelegate {
editor.insert_creases(crease_iter, cx); editor.insert_creases(crease_iter, cx);
for buffer_row in rows_to_fold { 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; 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) { match task.await.notify_async_err(&mut cx) {
None => anyhow::Ok(()), 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::KeepOpen => {}
ConfirmBehavior::Close => this.delegate.dismissed(cx), ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
}), }),
} }
}) })
.detach_and_log_err(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 self.context_picker
.update(cx, |_, cx| { .update(cx, |_, cx| {
cx.emit(DismissEvent); cx.emit(DismissEvent);
@ -340,7 +355,8 @@ impl PickerDelegate for FileContextPickerDelegate {
&self, &self,
ix: usize, ix: usize,
selected: bool, selected: bool,
cx: &mut ViewContext<Picker<Self>>, _window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> { ) -> Option<Self::ListItem> {
let path_match = &self.matches[ix]; let path_match = &self.matches[ix];
@ -363,8 +379,8 @@ pub fn render_file_context_entry(
id: ElementId, id: ElementId,
path: &Path, path: &Path,
path_prefix: &Arc<str>, path_prefix: &Arc<str>,
context_store: WeakModel<ContextStore>, context_store: WeakEntity<ContextStore>,
cx: &WindowContext, cx: &App,
) -> Stateful<Div> { ) -> Stateful<Div> {
let (file_name, directory) = if path == Path::new("") { let (file_name, directory) = if path == Path::new("") {
(SharedString::from(path_prefix.clone()), None) (SharedString::from(path_prefix.clone()), None)
@ -437,7 +453,7 @@ pub fn render_file_context_entry(
) )
.child(Label::new("Included").size(LabelSize::Small)), .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( fn render_fold_icon_button(
icon: IconName, icon: IconName,
label: SharedString, label: SharedString,
) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut WindowContext) -> AnyElement> { ) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut Window, &mut App) -> AnyElement> {
Arc::new(move |fold_id, _fold_range, _cx| { Arc::new(move |fold_id, _fold_range, _window, _cx| {
ButtonLike::new(fold_id) ButtonLike::new(fold_id)
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
.layer(ElevationIndex::ElevatedSurface) .layer(ElevationIndex::ElevatedSurface)
@ -461,13 +477,14 @@ fn fold_toggle(
) -> impl Fn( ) -> impl Fn(
MultiBufferRow, MultiBufferRow,
bool, bool,
Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>, Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
&mut WindowContext, &mut Window,
&mut App,
) -> AnyElement { ) -> AnyElement {
move |row, is_folded, fold, _cx| { move |row, is_folded, fold, _window, _cx| {
Disclosure::new((name, row.0 as u64), !is_folded) Disclosure::new((name, row.0 as u64), !is_folded)
.toggle_state(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() .into_any_element()
} }
} }

View file

@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use fuzzy::StringMatchCandidate; 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 picker::{Picker, PickerDelegate};
use ui::{prelude::*, ListItem}; use ui::{prelude::*, ListItem};
@ -11,16 +11,17 @@ use crate::thread::ThreadId;
use crate::thread_store::ThreadStore; use crate::thread_store::ThreadStore;
pub struct ThreadContextPicker { pub struct ThreadContextPicker {
picker: View<Picker<ThreadContextPickerDelegate>>, picker: Entity<Picker<ThreadContextPickerDelegate>>,
} }
impl ThreadContextPicker { impl ThreadContextPicker {
pub fn new( pub fn new(
thread_store: WeakModel<ThreadStore>, thread_store: WeakEntity<ThreadStore>,
context_picker: WeakView<ContextPicker>, context_picker: WeakEntity<ContextPicker>,
context_store: WeakModel<context_store::ContextStore>, context_store: WeakEntity<context_store::ContextStore>,
confirm_behavior: ConfirmBehavior, confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) -> Self { ) -> Self {
let delegate = ThreadContextPickerDelegate::new( let delegate = ThreadContextPickerDelegate::new(
thread_store, thread_store,
@ -28,20 +29,20 @@ impl ThreadContextPicker {
context_store, context_store,
confirm_behavior, 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 } ThreadContextPicker { picker }
} }
} }
impl FocusableView for ThreadContextPicker { impl Focusable for ThreadContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle { fn focus_handle(&self, cx: &App) -> FocusHandle {
self.picker.focus_handle(cx) self.picker.focus_handle(cx)
} }
} }
impl Render for ThreadContextPicker { 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() self.picker.clone()
} }
} }
@ -53,9 +54,9 @@ pub struct ThreadContextEntry {
} }
pub struct ThreadContextPickerDelegate { pub struct ThreadContextPickerDelegate {
thread_store: WeakModel<ThreadStore>, thread_store: WeakEntity<ThreadStore>,
context_picker: WeakView<ContextPicker>, context_picker: WeakEntity<ContextPicker>,
context_store: WeakModel<context_store::ContextStore>, context_store: WeakEntity<context_store::ContextStore>,
confirm_behavior: ConfirmBehavior, confirm_behavior: ConfirmBehavior,
matches: Vec<ThreadContextEntry>, matches: Vec<ThreadContextEntry>,
selected_index: usize, selected_index: usize,
@ -63,9 +64,9 @@ pub struct ThreadContextPickerDelegate {
impl ThreadContextPickerDelegate { impl ThreadContextPickerDelegate {
pub fn new( pub fn new(
thread_store: WeakModel<ThreadStore>, thread_store: WeakEntity<ThreadStore>,
context_picker: WeakView<ContextPicker>, context_picker: WeakEntity<ContextPicker>,
context_store: WeakModel<context_store::ContextStore>, context_store: WeakEntity<context_store::ContextStore>,
confirm_behavior: ConfirmBehavior, confirm_behavior: ConfirmBehavior,
) -> Self { ) -> Self {
ThreadContextPickerDelegate { ThreadContextPickerDelegate {
@ -90,15 +91,25 @@ impl PickerDelegate for ThreadContextPickerDelegate {
self.selected_index 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; 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() "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| { let Ok(threads) = self.thread_store.update(cx, |this, _cx| {
this.threads() this.threads()
.into_iter() .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; let matches = search_task.await;
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
this.delegate.matches = matches; 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 { let Some(entry) = self.matches.get(self.selected_index) else {
return; 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)); 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?; let thread = open_thread_task.await?;
this.update(&mut cx, |this, cx| { this.update_in(&mut cx, |this, window, cx| {
this.delegate this.delegate
.context_store .context_store
.update(cx, |context_store, cx| context_store.add_thread(thread, cx)) .update(cx, |context_store, cx| context_store.add_thread(thread, cx))
@ -170,14 +181,14 @@ impl PickerDelegate for ThreadContextPickerDelegate {
match this.delegate.confirm_behavior { match this.delegate.confirm_behavior {
ConfirmBehavior::KeepOpen => {} ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => this.delegate.dismissed(cx), ConfirmBehavior::Close => this.delegate.dismissed(window, cx),
} }
}) })
}) })
.detach_and_log_err(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 self.context_picker
.update(cx, |_, cx| { .update(cx, |_, cx| {
cx.emit(DismissEvent); cx.emit(DismissEvent);
@ -189,7 +200,8 @@ impl PickerDelegate for ThreadContextPickerDelegate {
&self, &self,
ix: usize, ix: usize,
selected: bool, selected: bool,
cx: &mut ViewContext<Picker<Self>>, _window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> { ) -> Option<Self::ListItem> {
let thread = &self.matches[ix]; let thread = &self.matches[ix];
@ -201,8 +213,8 @@ impl PickerDelegate for ThreadContextPickerDelegate {
pub fn render_thread_context_entry( pub fn render_thread_context_entry(
thread: &ThreadContextEntry, thread: &ThreadContextEntry,
context_store: WeakModel<ContextStore>, context_store: WeakEntity<ContextStore>,
cx: &mut WindowContext, cx: &mut App,
) -> Div { ) -> Div {
let added = context_store.upgrade().map_or(false, |ctx_store| { let added = context_store.upgrade().map_or(false, |ctx_store| {
ctx_store.read(cx).includes_thread(&thread.id).is_some() ctx_store.read(cx).includes_thread(&thread.id).is_some()

View file

@ -4,7 +4,7 @@ use std::sync::Arc;
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail, Result};
use collections::{BTreeMap, HashMap, HashSet}; use collections::{BTreeMap, HashMap, HashSet};
use futures::{self, future, Future, FutureExt}; 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 language::Buffer;
use project::{ProjectPath, Worktree}; use project::{ProjectPath, Worktree};
use rope::Rope; use rope::Rope;
@ -12,15 +12,15 @@ use text::BufferId;
use workspace::Workspace; use workspace::Workspace;
use crate::context::{ use crate::context::{
Context, ContextBuffer, ContextId, ContextSnapshot, DirectoryContext, FetchedUrlContext, AssistantContext, ContextBuffer, ContextId, ContextSnapshot, DirectoryContext,
FileContext, ThreadContext, FetchedUrlContext, FileContext, ThreadContext,
}; };
use crate::context_strip::SuggestedContext; use crate::context_strip::SuggestedContext;
use crate::thread::{Thread, ThreadId}; use crate::thread::{Thread, ThreadId};
pub struct ContextStore { pub struct ContextStore {
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
context: Vec<Context>, context: Vec<AssistantContext>,
// TODO: If an EntityId is used for all context types (like BufferId), can remove ContextId. // TODO: If an EntityId is used for all context types (like BufferId), can remove ContextId.
next_context_id: ContextId, next_context_id: ContextId,
files: BTreeMap<BufferId, ContextId>, files: BTreeMap<BufferId, ContextId>,
@ -30,7 +30,7 @@ pub struct ContextStore {
} }
impl ContextStore { impl ContextStore {
pub fn new(workspace: WeakView<Workspace>) -> Self { pub fn new(workspace: WeakEntity<Workspace>) -> Self {
Self { Self {
workspace, workspace,
context: Vec::new(), context: Vec::new(),
@ -42,16 +42,13 @@ impl ContextStore {
} }
} }
pub fn snapshot<'a>( pub fn snapshot<'a>(&'a self, cx: &'a App) -> impl Iterator<Item = ContextSnapshot> + 'a {
&'a self,
cx: &'a AppContext,
) -> impl Iterator<Item = ContextSnapshot> + 'a {
self.context() self.context()
.iter() .iter()
.flat_map(|context| context.snapshot(cx)) .flat_map(|context| context.snapshot(cx))
} }
pub fn context(&self) -> &Vec<Context> { pub fn context(&self) -> &Vec<AssistantContext> {
&self.context &self.context
} }
@ -66,7 +63,7 @@ impl ContextStore {
pub fn add_file_from_path( pub fn add_file_from_path(
&mut self, &mut self,
project_path: ProjectPath, project_path: ProjectPath,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
let workspace = self.workspace.clone(); let workspace = self.workspace.clone();
@ -122,8 +119,8 @@ impl ContextStore {
pub fn add_file_from_buffer( pub fn add_file_from_buffer(
&mut self, &mut self,
buffer_model: Model<Buffer>, buffer_model: Entity<Buffer>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
let (buffer_info, text_task) = this.update(&mut cx, |_, cx| { let (buffer_info, text_task) = this.update(&mut cx, |_, cx| {
@ -153,13 +150,13 @@ impl ContextStore {
let id = self.next_context_id.post_inc(); let id = self.next_context_id.post_inc();
self.files.insert(context_buffer.id, id); self.files.insert(context_buffer.id, id);
self.context self.context
.push(Context::File(FileContext { id, context_buffer })); .push(AssistantContext::File(FileContext { id, context_buffer }));
} }
pub fn add_directory( pub fn add_directory(
&mut self, &mut self,
project_path: ProjectPath, project_path: ProjectPath,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
let workspace = self.workspace.clone(); let workspace = self.workspace.clone();
let Some(project) = workspace let Some(project) = workspace
@ -244,14 +241,15 @@ impl ContextStore {
let id = self.next_context_id.post_inc(); let id = self.next_context_id.post_inc();
self.directories.insert(path.to_path_buf(), id); self.directories.insert(path.to_path_buf(), id);
self.context.push(Context::Directory(DirectoryContext::new( self.context
id, .push(AssistantContext::Directory(DirectoryContext::new(
path, id,
context_buffers, 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()) { if let Some(context_id) = self.includes_thread(&thread.read(cx).id()) {
self.remove_context(context_id); self.remove_context(context_id);
} else { } 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 id = self.next_context_id.post_inc();
let text = thread.read(cx).text().into(); let text = thread.read(cx).text().into();
self.threads.insert(thread.read(cx).id().clone(), id); self.threads.insert(thread.read(cx).id().clone(), id);
self.context 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>) { 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(); let id = self.next_context_id.post_inc();
self.fetched_urls.insert(url.clone(), id); self.fetched_urls.insert(url.clone(), id);
self.context.push(Context::FetchedUrl(FetchedUrlContext { self.context
id, .push(AssistantContext::FetchedUrl(FetchedUrlContext {
url: url.into(), id,
text: text.into(), url: url.into(),
})); text: text.into(),
}));
} }
pub fn accept_suggested_context( pub fn accept_suggested_context(
&mut self, &mut self,
suggested: &SuggestedContext, suggested: &SuggestedContext,
cx: &mut ModelContext<ContextStore>, cx: &mut Context<ContextStore>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
match suggested { match suggested {
SuggestedContext::File { SuggestedContext::File {
@ -315,16 +314,16 @@ impl ContextStore {
}; };
match self.context.remove(ix) { match self.context.remove(ix) {
Context::File(_) => { AssistantContext::File(_) => {
self.files.retain(|_, context_id| *context_id != id); self.files.retain(|_, context_id| *context_id != id);
} }
Context::Directory(_) => { AssistantContext::Directory(_) => {
self.directories.retain(|_, context_id| *context_id != id); self.directories.retain(|_, context_id| *context_id != id);
} }
Context::FetchedUrl(_) => { AssistantContext::FetchedUrl(_) => {
self.fetched_urls.retain(|_, context_id| *context_id != id); self.fetched_urls.retain(|_, context_id| *context_id != id);
} }
Context::Thread(_) => { AssistantContext::Thread(_) => {
self.threads.retain(|_, context_id| *context_id != id); 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 /// Returns whether this file path is already included directly in the context, or if it will be
/// included in the context via a directory. /// 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() { if !self.files.is_empty() {
let found_file_context = self.context.iter().find(|context| match &context { 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); let buffer = file_context.context_buffer.buffer.read(cx);
if let Some(file_path) = buffer_path_log_err(buffer) { if let Some(file_path) = buffer_path_log_err(buffer) {
*file_path == *path *file_path == *path
@ -393,7 +392,7 @@ impl ContextStore {
} }
/// Replaces the context that matches the ID of the new context, if any match. /// 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(); let id = new_context.id();
for context in self.context.iter_mut() { for context in self.context.iter_mut() {
if context.id() == id { 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 self.context
.iter() .iter()
.filter_map(|context| match context { .filter_map(|context| match context {
Context::File(file) => { AssistantContext::File(file) => {
let buffer = file.context_buffer.buffer.read(cx); let buffer = file.context_buffer.buffer.read(cx);
buffer_path_log_err(buffer).map(|p| p.to_path_buf()) 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() .collect()
} }
@ -428,7 +429,7 @@ pub enum FileInclusion {
// ContextBuffer without text. // ContextBuffer without text.
struct BufferInfo { struct BufferInfo {
buffer_model: Model<Buffer>, buffer_model: Entity<Buffer>,
id: BufferId, id: BufferId,
version: clock::Global, version: clock::Global,
} }
@ -444,7 +445,7 @@ fn make_context_buffer(info: BufferInfo, text: SharedString) -> ContextBuffer {
fn collect_buffer_info_and_text( fn collect_buffer_info_and_text(
path: Arc<Path>, path: Arc<Path>,
buffer_model: Model<Buffer>, buffer_model: Entity<Buffer>,
buffer: &Buffer, buffer: &Buffer,
cx: AsyncAppContext, cx: AsyncAppContext,
) -> (BufferInfo, Task<SharedString>) { ) -> (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( pub fn refresh_context_store_text(
context_store: Model<ContextStore>, context_store: Entity<ContextStore>,
cx: &AppContext, cx: &App,
) -> impl Future<Output = ()> { ) -> impl Future<Output = ()> {
let mut tasks = Vec::new(); let mut tasks = Vec::new();
for context in &context_store.read(cx).context { for context in &context_store.read(cx).context {
match context { match context {
Context::File(file_context) => { AssistantContext::File(file_context) => {
let context_store = context_store.clone(); let context_store = context_store.clone();
if let Some(task) = refresh_file_text(context_store, file_context, cx) { if let Some(task) = refresh_file_text(context_store, file_context, cx) {
tasks.push(task); tasks.push(task);
} }
} }
Context::Directory(directory_context) => { AssistantContext::Directory(directory_context) => {
let context_store = context_store.clone(); let context_store = context_store.clone();
if let Some(task) = refresh_directory_text(context_store, directory_context, cx) { if let Some(task) = refresh_directory_text(context_store, directory_context, cx) {
tasks.push(task); tasks.push(task);
} }
} }
Context::Thread(thread_context) => { AssistantContext::Thread(thread_context) => {
let context_store = context_store.clone(); let context_store = context_store.clone();
tasks.push(refresh_thread_text(context_store, thread_context, cx)); tasks.push(refresh_thread_text(context_store, thread_context, cx));
} }
// Intentionally omit refreshing fetched URLs as it doesn't seem all that useful, // 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 // and doing the caching properly could be tricky (unless it's already handled by
// the HttpClient?). // the HttpClient?).
Context::FetchedUrl(_) => {} AssistantContext::FetchedUrl(_) => {}
} }
} }
@ -558,9 +559,9 @@ pub fn refresh_context_store_text(
} }
fn refresh_file_text( fn refresh_file_text(
context_store: Model<ContextStore>, context_store: Entity<ContextStore>,
file_context: &FileContext, file_context: &FileContext,
cx: &AppContext, cx: &App,
) -> Option<Task<()>> { ) -> Option<Task<()>> {
let id = file_context.id; let id = file_context.id;
let task = refresh_context_buffer(&file_context.context_buffer, cx); let task = refresh_context_buffer(&file_context.context_buffer, cx);
@ -570,7 +571,7 @@ fn refresh_file_text(
context_store context_store
.update(&mut cx, |context_store, _| { .update(&mut cx, |context_store, _| {
let new_file_context = FileContext { id, context_buffer }; 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(); .ok();
})) }))
@ -580,9 +581,9 @@ fn refresh_file_text(
} }
fn refresh_directory_text( fn refresh_directory_text(
context_store: Model<ContextStore>, context_store: Entity<ContextStore>,
directory_context: &DirectoryContext, directory_context: &DirectoryContext,
cx: &AppContext, cx: &App,
) -> Option<Task<()>> { ) -> Option<Task<()>> {
let mut stale = false; let mut stale = false;
let futures = directory_context let futures = directory_context
@ -611,16 +612,16 @@ fn refresh_directory_text(
context_store context_store
.update(&mut cx, |context_store, _| { .update(&mut cx, |context_store, _| {
let new_directory_context = DirectoryContext::new(id, &path, context_buffers); 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(); .ok();
})) }))
} }
fn refresh_thread_text( fn refresh_thread_text(
context_store: Model<ContextStore>, context_store: Entity<ContextStore>,
thread_context: &ThreadContext, thread_context: &ThreadContext,
cx: &AppContext, cx: &App,
) -> Task<()> { ) -> Task<()> {
let id = thread_context.id; let id = thread_context.id;
let thread = thread_context.thread.clone(); let thread = thread_context.thread.clone();
@ -628,7 +629,11 @@ fn refresh_thread_text(
context_store context_store
.update(&mut cx, |context_store, cx| { .update(&mut cx, |context_store, cx| {
let text = thread.read(cx).text().into(); 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(); .ok();
}) })
@ -636,7 +641,7 @@ fn refresh_thread_text(
fn refresh_context_buffer( fn refresh_context_buffer(
context_buffer: &ContextBuffer, context_buffer: &ContextBuffer,
cx: &AppContext, cx: &App,
) -> Option<impl Future<Output = ContextBuffer>> { ) -> Option<impl Future<Output = ContextBuffer>> {
let buffer = context_buffer.buffer.read(cx); let buffer = context_buffer.buffer.read(cx);
let path = buffer_path_log_err(buffer)?; let path = buffer_path_log_err(buffer)?;

View file

@ -4,8 +4,8 @@ use collections::HashSet;
use editor::Editor; use editor::Editor;
use file_icons::FileIcons; use file_icons::FileIcons;
use gpui::{ use gpui::{
AppContext, Bounds, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model, App, Bounds, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription,
Subscription, View, WeakModel, WeakView, WeakEntity,
}; };
use itertools::Itertools; use itertools::Itertools;
use language::Buffer; use language::Buffer;
@ -24,34 +24,37 @@ use crate::{
}; };
pub struct ContextStrip { pub struct ContextStrip {
context_store: Model<ContextStore>, context_store: Entity<ContextStore>,
pub context_picker: View<ContextPicker>, pub context_picker: Entity<ContextPicker>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>, context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
focus_handle: FocusHandle, focus_handle: FocusHandle,
suggest_context_kind: SuggestContextKind, suggest_context_kind: SuggestContextKind,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
focused_index: Option<usize>, focused_index: Option<usize>,
children_bounds: Option<Vec<Bounds<Pixels>>>, children_bounds: Option<Vec<Bounds<Pixels>>>,
} }
impl ContextStrip { impl ContextStrip {
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
context_store: Model<ContextStore>, context_store: Entity<ContextStore>,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
editor: WeakView<Editor>, editor: WeakEntity<Editor>,
thread_store: Option<WeakModel<ThreadStore>>, thread_store: Option<WeakEntity<ThreadStore>>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>, context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
suggest_context_kind: SuggestContextKind, suggest_context_kind: SuggestContextKind,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) -> Self { ) -> Self {
let context_picker = cx.new_view(|cx| { let context_picker = cx.new(|cx| {
ContextPicker::new( ContextPicker::new(
workspace.clone(), workspace.clone(),
thread_store.clone(), thread_store.clone(),
context_store.downgrade(), context_store.downgrade(),
editor.clone(), editor.clone(),
ConfirmBehavior::KeepOpen, ConfirmBehavior::KeepOpen,
window,
cx, cx,
) )
}); });
@ -59,9 +62,9 @@ impl ContextStrip {
let focus_handle = cx.focus_handle(); let focus_handle = cx.focus_handle();
let subscriptions = vec![ let subscriptions = vec![
cx.subscribe(&context_picker, Self::handle_context_picker_event), cx.subscribe_in(&context_picker, window, Self::handle_context_picker_event),
cx.on_focus(&focus_handle, Self::handle_focus), cx.on_focus(&focus_handle, window, Self::handle_focus),
cx.on_blur(&focus_handle, Self::handle_blur), cx.on_blur(&focus_handle, window, Self::handle_blur),
]; ];
Self { 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 { match self.suggest_context_kind {
SuggestContextKind::File => self.suggested_file(cx), SuggestContextKind::File => self.suggested_file(cx),
SuggestContextKind::Thread => self.suggested_thread(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 workspace = self.workspace.upgrade()?;
let active_item = workspace.read(cx).active_item(cx)?; 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() { if !self.context_picker.read(cx).allow_threads() {
return None; return None;
} }
@ -149,24 +152,25 @@ impl ContextStrip {
fn handle_context_picker_event( fn handle_context_picker_event(
&mut self, &mut self,
_picker: View<ContextPicker>, _picker: &Entity<ContextPicker>,
_event: &DismissEvent, _event: &DismissEvent,
cx: &mut ViewContext<Self>, _window: &mut Window,
cx: &mut Context<Self>,
) { ) {
cx.emit(ContextStripEvent::PickerDismissed); 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(); self.focused_index = self.last_pill_index();
cx.notify(); 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; self.focused_index = None;
cx.notify(); 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 { self.focused_index = match self.focused_index {
Some(index) if index > 0 => Some(index - 1), Some(index) if index > 0 => Some(index - 1),
_ => self.last_pill_index(), _ => self.last_pill_index(),
@ -175,7 +179,7 @@ impl ContextStrip {
cx.notify(); 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 { let Some(last_index) = self.last_pill_index() else {
return; return;
}; };
@ -188,7 +192,7 @@ impl ContextStrip {
cx.notify(); 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 { let Some(focused_index) = self.focused_index else {
return; return;
}; };
@ -206,7 +210,7 @@ impl ContextStrip {
cx.notify(); 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 { let Some(focused_index) = self.focused_index else {
return; return;
}; };
@ -276,7 +280,12 @@ impl ContextStrip {
best.map(|(index, _, _)| index) 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 { if let Some(index) = self.focused_index {
let mut is_empty = false; let mut is_empty = false;
@ -302,22 +311,32 @@ impl ContextStrip {
self.focused_index == Some(context.len()) 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) { if let Some(suggested) = self.suggested_context(cx) {
let context_store = self.context_store.read(cx); let context_store = self.context_store.read(cx);
if self.is_suggested_focused(context_store.context()) { 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| { let task = self.context_store.update(cx, |context_store, cx| {
context_store.accept_suggested_context(&suggested, 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) { match task.await.notify_async_err(&mut cx) {
None => {} None => {}
Some(()) => { Some(()) => {
@ -334,14 +353,14 @@ impl ContextStrip {
} }
} }
impl FocusableView for ContextStrip { impl Focusable for ContextStrip {
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle { fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone() self.focus_handle.clone()
} }
} }
impl Render for ContextStrip { 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_store = self.context_store.read(cx);
let context = context_store let context = context_store
.context() .context()
@ -374,19 +393,20 @@ impl Render for ContextStrip {
.on_action(cx.listener(Self::remove_focused_context)) .on_action(cx.listener(Self::remove_focused_context))
.on_action(cx.listener(Self::accept_suggested_context)) .on_action(cx.listener(Self::accept_suggested_context))
.on_children_prepainted({ .on_children_prepainted({
let view = cx.view().downgrade(); let model = cx.model().downgrade();
move |children_bounds, cx| { move |children_bounds, _window, cx| {
view.update(cx, |this, _| { model
this.children_bounds = Some(children_bounds); .update(cx, |this, _| {
}) this.children_bounds = Some(children_bounds);
.ok(); })
.ok();
} }
}) })
.child( .child(
PopoverMenu::new("context-picker") PopoverMenu::new("context-picker")
.menu(move |cx| { .menu(move |window, cx| {
context_picker.update(cx, |this, cx| { context_picker.update(cx, |this, cx| {
this.init(cx); this.init(window, cx);
}); });
Some(context_picker.clone()) Some(context_picker.clone())
@ -397,12 +417,12 @@ impl Render for ContextStrip {
.style(ui::ButtonStyle::Filled) .style(ui::ButtonStyle::Filled)
.tooltip({ .tooltip({
let focus_handle = focus_handle.clone(); let focus_handle = focus_handle.clone();
move |window, cx| {
move |cx| {
Tooltip::for_action_in( Tooltip::for_action_in(
"Add Context", "Add Context",
&ToggleContextPicker, &ToggleContextPicker,
&focus_handle, &focus_handle,
window,
cx, cx,
) )
} }
@ -429,8 +449,12 @@ impl Render for ContextStrip {
) )
.opacity(0.5) .opacity(0.5)
.children( .children(
KeyBinding::for_action_in(&ToggleContextPicker, &focus_handle, cx) KeyBinding::for_action_in(
.map(|binding| binding.into_any_element()), &ToggleContextPicker,
&focus_handle,
window,
)
.map(|binding| binding.into_any_element()),
), ),
) )
} }
@ -443,7 +467,7 @@ impl Render for ContextStrip {
Some({ Some({
let id = context.id; let id = context.id;
let context_store = self.context_store.clone(); 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| { context_store.update(cx, |this, _cx| {
this.remove_context(id); 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); this.focused_index = Some(i);
cx.notify(); cx.notify();
}))) })))
@ -464,9 +488,11 @@ impl Render for ContextStrip {
suggested.kind(), suggested.kind(),
self.is_suggested_focused(&context), self.is_suggested_focused(&context),
) )
.on_click(Rc::new(cx.listener(move |this, _event, cx| { .on_click(Rc::new(cx.listener(
this.add_suggested_context(&suggested, cx); move |this, _event, window, cx| {
}))), this.add_suggested_context(&suggested, window, cx);
},
))),
) )
}) })
.when(!context.is_empty(), { .when(!context.is_empty(), {
@ -476,19 +502,20 @@ impl Render for ContextStrip {
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.tooltip({ .tooltip({
let focus_handle = focus_handle.clone(); let focus_handle = focus_handle.clone();
move |cx| { move |window, cx| {
Tooltip::for_action_in( Tooltip::for_action_in(
"Remove All Context", "Remove All Context",
&RemoveAllContext, &RemoveAllContext,
&focus_handle, &focus_handle,
window,
cx, cx,
) )
} }
}) })
.on_click(cx.listener({ .on_click(cx.listener({
let focus_handle = focus_handle.clone(); let focus_handle = focus_handle.clone();
move |_this, _event, cx| { move |_this, _event, window, cx| {
focus_handle.dispatch_action(&RemoveAllContext, cx); focus_handle.dispatch_action(&RemoveAllContext, window, cx);
} }
})), })),
) )
@ -516,11 +543,11 @@ pub enum SuggestedContext {
File { File {
name: SharedString, name: SharedString,
icon_path: Option<SharedString>, icon_path: Option<SharedString>,
buffer: WeakModel<Buffer>, buffer: WeakEntity<Buffer>,
}, },
Thread { Thread {
name: SharedString, name: SharedString,
thread: WeakModel<Thread>, thread: WeakEntity<Thread>,
}, },
} }

File diff suppressed because it is too large Load diff

View file

@ -16,9 +16,8 @@ use editor::{
use feature_flags::{FeatureFlagAppExt as _, ZedPro}; use feature_flags::{FeatureFlagAppExt as _, ZedPro};
use fs::Fs; use fs::Fs;
use gpui::{ use gpui::{
anchored, deferred, point, AnyElement, AppContext, ClickEvent, CursorStyle, EventEmitter, anchored, deferred, point, AnyElement, App, ClickEvent, Context, CursorStyle, Entity,
FocusHandle, FocusableView, FontWeight, Model, Subscription, TextStyle, View, ViewContext, EventEmitter, FocusHandle, Focusable, FontWeight, Subscription, TextStyle, WeakEntity, Window,
WeakModel, WeakView, WindowContext,
}; };
use language_model::{LanguageModel, LanguageModelRegistry}; use language_model::{LanguageModel, LanguageModelRegistry};
use language_model_selector::LanguageModelSelector; use language_model_selector::LanguageModelSelector;
@ -35,12 +34,12 @@ use util::ResultExt;
use workspace::Workspace; use workspace::Workspace;
pub struct PromptEditor<T> { pub struct PromptEditor<T> {
pub editor: View<Editor>, pub editor: Entity<Editor>,
mode: PromptEditorMode, mode: PromptEditorMode,
context_store: Model<ContextStore>, context_store: Entity<ContextStore>,
context_strip: View<ContextStrip>, context_strip: Entity<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>, context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
model_selector: View<AssistantModelSelector>, model_selector: Entity<AssistantModelSelector>,
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>, model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
edited_since_done: bool, edited_since_done: bool,
prompt_history: VecDeque<String>, prompt_history: VecDeque<String>,
@ -56,7 +55,7 @@ pub struct PromptEditor<T> {
impl<T: 'static> EventEmitter<PromptEditorEvent> for PromptEditor<T> {} impl<T: 'static> EventEmitter<PromptEditorEvent> for PromptEditor<T> {}
impl<T: 'static> Render 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 ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
let mut buttons = Vec::new(); let mut buttons = Vec::new();
@ -87,7 +86,7 @@ impl<T: 'static> Render for PromptEditor<T> {
PromptEditorMode::Terminal { .. } => Pixels::from(8.0), PromptEditorMode::Terminal { .. } => Pixels::from(8.0),
}; };
buttons.extend(self.render_buttons(cx)); buttons.extend(self.render_buttons(window, cx));
v_flex() v_flex()
.key_context("PromptEditor") .key_context("PromptEditor")
@ -163,9 +162,7 @@ impl<T: 'static> Render for PromptEditor<T> {
el.child( el.child(
div() div()
.id("error") .id("error")
.tooltip(move |cx| { .tooltip(Tooltip::text(error_message))
Tooltip::text(error_message.clone(), cx)
})
.child( .child(
Icon::new(IconName::XCircle) Icon::new(IconName::XCircle)
.size(IconSize::Small) .size(IconSize::Small)
@ -179,7 +176,7 @@ impl<T: 'static> Render for PromptEditor<T> {
h_flex() h_flex()
.w_full() .w_full()
.justify_between() .justify_between()
.child(div().flex_1().child(self.render_editor(cx))) .child(div().flex_1().child(self.render_editor(window, cx)))
.child( .child(
WithRemSize::new(ui_font_size) WithRemSize::new(ui_font_size)
.flex() .flex()
@ -209,8 +206,8 @@ impl<T: 'static> Render for PromptEditor<T> {
} }
} }
impl<T: 'static> FocusableView for PromptEditor<T> { impl<T: 'static> Focusable for PromptEditor<T> {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle { fn focus_handle(&self, cx: &App) -> FocusHandle {
self.editor.focus_handle(cx) self.editor.focus_handle(cx)
} }
} }
@ -218,47 +215,50 @@ impl<T: 'static> FocusableView for PromptEditor<T> {
impl<T: 'static> PromptEditor<T> { impl<T: 'static> PromptEditor<T> {
const MAX_LINES: u8 = 8; 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 { match &self.mode {
PromptEditorMode::Buffer { codegen, .. } => codegen.read(cx).status(cx), PromptEditorMode::Buffer { codegen, .. } => codegen.read(cx).status(cx),
PromptEditorMode::Terminal { codegen, .. } => &codegen.read(cx).status, 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.clear();
self.editor_subscriptions self.editor_subscriptions.push(cx.subscribe_in(
.push(cx.subscribe(&self.editor, Self::handle_prompt_editor_events)); &self.editor,
window,
Self::handle_prompt_editor_events,
));
} }
pub fn set_show_cursor_when_unfocused( pub fn set_show_cursor_when_unfocused(
&mut self, &mut self,
show_cursor_when_unfocused: bool, show_cursor_when_unfocused: bool,
cx: &mut ViewContext<Self>, cx: &mut Context<Self>,
) { ) {
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
editor.set_show_cursor_when_unfocused(show_cursor_when_unfocused, 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 prompt = self.prompt(cx);
let focus = self.editor.focus_handle(cx).contains_focused(cx); let focus = self.editor.focus_handle(cx).contains_focused(window, cx);
self.editor = cx.new_view(|cx| { self.editor = cx.new(|cx| {
let mut editor = Editor::auto_height(Self::MAX_LINES as usize, 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_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_placeholder_text("Add a prompt…", cx);
editor.set_text(prompt, cx); editor.set_text(prompt, window, cx);
if focus { if focus {
editor.focus(cx); window.focus(&editor.focus_handle(cx));
} }
editor 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 { let action = match mode {
PromptEditorMode::Buffer { codegen, .. } => { PromptEditorMode::Buffer { codegen, .. } => {
if codegen.read(cx).is_insertion { if codegen.read(cx).is_insertion {
@ -271,36 +271,42 @@ impl<T: 'static> PromptEditor<T> {
}; };
let assistant_panel_keybinding = 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 ― ")) .map(|keybinding| format!("{keybinding} to chat ― "))
.unwrap_or_default(); .unwrap_or_default();
format!("{action}… ({assistant_panel_keybinding}↓↑ for history)") 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) 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; self.show_rate_limit_notice = !self.show_rate_limit_notice;
if 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(); cx.notify();
} }
fn handle_prompt_editor_events( fn handle_prompt_editor_events(
&mut self, &mut self,
_: View<Editor>, _: &Entity<Editor>,
event: &EditorEvent, event: &EditorEvent,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
match event { match event {
EditorEvent::Edited { .. } => { EditorEvent::Edited { .. } => {
if let Some(workspace) = cx.window_handle().downcast::<Workspace>() { if let Some(workspace) = window.window_handle().downcast::<Workspace>() {
workspace workspace
.update(cx, |workspace, cx| { .update(cx, |workspace, _, cx| {
let is_via_ssh = workspace let is_via_ssh = workspace
.project() .project()
.update(cx, |project, _| project.is_via_ssh()); .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>) { fn toggle_context_picker(
self.context_picker_menu_handle.toggle(cx); &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>) { fn toggle_model_selector(
self.model_selector_menu_handle.toggle(cx); &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()); self.context_store.update(cx, |store, _cx| store.clear());
cx.notify(); 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) { match self.codegen_status(cx) {
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => { CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
cx.emit(PromptEditorEvent::CancelRequested); 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) { match self.codegen_status(cx) {
CodegenStatus::Idle => { CodegenStatus::Idle => {
cx.emit(PromptEditorEvent::StartRequested); 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 let Some(ix) = self.prompt_history_ix {
if ix > 0 { if ix > 0 {
self.prompt_history_ix = Some(ix - 1); self.prompt_history_ix = Some(ix - 1);
let prompt = self.prompt_history[ix - 1].as_str(); let prompt = self.prompt_history[ix - 1].as_str();
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx); editor.set_text(prompt, window, cx);
editor.move_to_beginning(&Default::default(), cx); editor.move_to_beginning(&Default::default(), window, cx);
}); });
} }
} else if !self.prompt_history.is_empty() { } else if !self.prompt_history.is_empty() {
self.prompt_history_ix = Some(self.prompt_history.len() - 1); self.prompt_history_ix = Some(self.prompt_history.len() - 1);
let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str(); let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str();
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx); editor.set_text(prompt, window, cx);
editor.move_to_beginning(&Default::default(), 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 let Some(ix) = self.prompt_history_ix {
if ix < self.prompt_history.len() - 1 { if ix < self.prompt_history.len() - 1 {
self.prompt_history_ix = Some(ix + 1); self.prompt_history_ix = Some(ix + 1);
let prompt = self.prompt_history[ix + 1].as_str(); let prompt = self.prompt_history[ix + 1].as_str();
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx); editor.set_text(prompt, window, cx);
editor.move_to_end(&Default::default(), cx) editor.move_to_end(&Default::default(), window, cx)
}); });
} else { } else {
self.prompt_history_ix = None; self.prompt_history_ix = None;
let prompt = self.pending_prompt.as_str(); let prompt = self.pending_prompt.as_str();
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx); editor.set_text(prompt, window, cx);
editor.move_to_end(&Default::default(), cx) editor.move_to_end(&Default::default(), window, cx)
}); });
} }
} else { } 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 { let mode = match &self.mode {
PromptEditorMode::Buffer { codegen, .. } => { PromptEditorMode::Buffer { codegen, .. } => {
let codegen = codegen.read(cx); let codegen = codegen.read(cx);
@ -443,21 +469,22 @@ impl<T: 'static> PromptEditor<T> {
.icon(IconName::Return) .icon(IconName::Return)
.icon_size(IconSize::XSmall) .icon_size(IconSize::XSmall)
.icon_color(Color::Muted) .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()] .into_any_element()]
} }
CodegenStatus::Pending => vec![IconButton::new("stop", IconName::Stop) CodegenStatus::Pending => vec![IconButton::new("stop", IconName::Stop)
.icon_color(Color::Error) .icon_color(Color::Error)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.tooltip(move |cx| { .tooltip(move |window, cx| {
Tooltip::with_meta( Tooltip::with_meta(
mode.tooltip_interrupt(), mode.tooltip_interrupt(),
Some(&menu::Cancel), Some(&menu::Cancel),
"Changes won't be discarded", "Changes won't be discarded",
window,
cx, cx,
) )
}) })
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StopRequested))) .on_click(cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
.into_any_element()], .into_any_element()],
CodegenStatus::Done | CodegenStatus::Error(_) => { CodegenStatus::Done | CodegenStatus::Error(_) => {
let has_error = matches!(codegen_status, 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) vec![IconButton::new("restart", IconName::RotateCw)
.icon_color(Color::Info) .icon_color(Color::Info)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.tooltip(move |cx| { .tooltip(move |window, cx| {
Tooltip::with_meta( Tooltip::with_meta(
mode.tooltip_restart(), mode.tooltip_restart(),
Some(&menu::Confirm), Some(&menu::Confirm),
"Changes will be discarded", "Changes will be discarded",
window,
cx, cx,
) )
}) })
.on_click(cx.listener(|_, _, cx| { .on_click(cx.listener(|_, _, _, cx| {
cx.emit(PromptEditorEvent::StartRequested); cx.emit(PromptEditorEvent::StartRequested);
})) }))
.into_any_element()] .into_any_element()]
@ -481,10 +509,10 @@ impl<T: 'static> PromptEditor<T> {
let accept = IconButton::new("accept", IconName::Check) let accept = IconButton::new("accept", IconName::Check)
.icon_color(Color::Info) .icon_color(Color::Info)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.tooltip(move |cx| { .tooltip(move |window, cx| {
Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, 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 }); cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
})) }))
.into_any_element(); .into_any_element();
@ -495,14 +523,15 @@ impl<T: 'static> PromptEditor<T> {
IconButton::new("confirm", IconName::Play) IconButton::new("confirm", IconName::Play)
.icon_color(Color::Info) .icon_color(Color::Info)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.tooltip(|cx| { .tooltip(|window, cx| {
Tooltip::for_action( Tooltip::for_action(
"Execute Generated Command", "Execute Generated Command",
&menu::SecondaryConfirm, &menu::SecondaryConfirm,
window,
cx, cx,
) )
}) })
.on_click(cx.listener(|_, _, cx| { .on_click(cx.listener(|_, _, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true }); cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
})) }))
.into_any_element(), .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 { match &self.mode {
PromptEditorMode::Buffer { codegen, .. } => { PromptEditorMode::Buffer { codegen, .. } => {
codegen.update(cx, |codegen, cx| codegen.cycle_prev(cx)); 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 { match &self.mode {
PromptEditorMode::Buffer { codegen, .. } => { PromptEditorMode::Buffer { codegen, .. } => {
codegen.update(cx, |codegen, cx| codegen.cycle_next(cx)); 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) IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted) .icon_color(Color::Muted)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::text("Close Assistant", cx)) .tooltip(Tooltip::text("Close Assistant"))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested))) .on_click(cx.listener(|_, _, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
.into_any_element() .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 disabled = matches!(codegen.status(cx), CodegenStatus::Idle);
let model_registry = LanguageModelRegistry::read_global(cx); let model_registry = LanguageModelRegistry::read_global(cx);
@ -585,13 +619,13 @@ impl<T: 'static> PromptEditor<T> {
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.tooltip({ .tooltip({
let focus_handle = self.editor.focus_handle(cx); let focus_handle = self.editor.focus_handle(cx);
move |cx| { move |window, cx| {
cx.new_view(|cx| { cx.new(|_| {
let mut tooltip = Tooltip::new("Previous Alternative").key_binding( let mut tooltip = Tooltip::new("Previous Alternative").key_binding(
KeyBinding::for_action_in( KeyBinding::for_action_in(
&CyclePreviousInlineAssist, &CyclePreviousInlineAssist,
&focus_handle, &focus_handle,
cx, window,
), ),
); );
if !disabled && current_index != 0 { if !disabled && current_index != 0 {
@ -602,8 +636,8 @@ impl<T: 'static> PromptEditor<T> {
.into() .into()
} }
}) })
.on_click(cx.listener(|this, _, cx| { .on_click(cx.listener(|this, _, window, cx| {
this.cycle_prev(&CyclePreviousInlineAssist, cx); this.cycle_prev(&CyclePreviousInlineAssist, window, cx);
})), })),
) )
.child( .child(
@ -626,13 +660,13 @@ impl<T: 'static> PromptEditor<T> {
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.tooltip({ .tooltip({
let focus_handle = self.editor.focus_handle(cx); let focus_handle = self.editor.focus_handle(cx);
move |cx| { move |window, cx| {
cx.new_view(|cx| { cx.new(|_| {
let mut tooltip = Tooltip::new("Next Alternative").key_binding( let mut tooltip = Tooltip::new("Next Alternative").key_binding(
KeyBinding::for_action_in( KeyBinding::for_action_in(
&CycleNextInlineAssist, &CycleNextInlineAssist,
&focus_handle, &focus_handle,
cx, window,
), ),
); );
if !disabled && current_index != total_models - 1 { if !disabled && current_index != total_models - 1 {
@ -643,14 +677,14 @@ impl<T: 'static> PromptEditor<T> {
.into() .into()
} }
}) })
.on_click( .on_click(cx.listener(|this, _, window, cx| {
cx.listener(|this, _, cx| this.cycle_next(&CycleNextInlineAssist, cx)), this.cycle_next(&CycleNextInlineAssist, window, cx)
), })),
) )
.into_any_element() .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( Popover::new().child(
v_flex() v_flex()
.occlude() .occlude()
@ -674,7 +708,7 @@ impl<T: 'static> PromptEditor<T> {
} else { } else {
ui::ToggleState::Unselected ui::ToggleState::Unselected
}, },
|selection, cx| { |selection, _, cx| {
let is_dismissed = match selection { let is_dismissed = match selection {
ui::ToggleState::Unselected => false, ui::ToggleState::Unselected => false,
ui::ToggleState::Indeterminate => return, ui::ToggleState::Indeterminate => return,
@ -693,10 +727,11 @@ impl<T: 'static> PromptEditor<T> {
.on_click(cx.listener(Self::toggle_rate_limit_notice)), .on_click(cx.listener(Self::toggle_rate_limit_notice)),
) )
.child(Button::new("more-info", "More Info").on_click( .child(Button::new("more-info", "More Info").on_click(
|_event, cx| { |_event, window, cx| {
cx.dispatch_action(Box::new( window.dispatch_action(
zed_actions::OpenAccountSettings, 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 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() div()
.key_context("InlineAssistEditor") .key_context("InlineAssistEditor")
@ -740,17 +775,15 @@ impl<T: 'static> PromptEditor<T> {
fn handle_context_strip_event( fn handle_context_strip_event(
&mut self, &mut self,
_context_strip: View<ContextStrip>, _context_strip: &Entity<ContextStrip>,
event: &ContextStripEvent, event: &ContextStripEvent,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
match event { match event {
ContextStripEvent::PickerDismissed ContextStripEvent::PickerDismissed
| ContextStripEvent::BlurredEmpty | ContextStripEvent::BlurredEmpty
| ContextStripEvent::BlurredUp => { | ContextStripEvent::BlurredUp => self.editor.focus_handle(cx).focus(window),
let editor_focus_handle = self.editor.focus_handle(cx);
cx.focus(&editor_focus_handle);
}
ContextStripEvent::BlurredDown => {} ContextStripEvent::BlurredDown => {}
} }
} }
@ -759,12 +792,12 @@ impl<T: 'static> PromptEditor<T> {
pub enum PromptEditorMode { pub enum PromptEditorMode {
Buffer { Buffer {
id: InlineAssistId, id: InlineAssistId,
codegen: Model<BufferCodegen>, codegen: Entity<BufferCodegen>,
gutter_dimensions: Arc<Mutex<GutterDimensions>>, gutter_dimensions: Arc<Mutex<GutterDimensions>>,
}, },
Terminal { Terminal {
id: TerminalInlineAssistId, id: TerminalInlineAssistId,
codegen: Model<TerminalCodegen>, codegen: Entity<TerminalCodegen>,
height_in_lines: u8, height_in_lines: u8,
}, },
} }
@ -795,13 +828,14 @@ impl PromptEditor<BufferCodegen> {
id: InlineAssistId, id: InlineAssistId,
gutter_dimensions: Arc<Mutex<GutterDimensions>>, gutter_dimensions: Arc<Mutex<GutterDimensions>>,
prompt_history: VecDeque<String>, prompt_history: VecDeque<String>,
prompt_buffer: Model<MultiBuffer>, prompt_buffer: Entity<MultiBuffer>,
codegen: Model<BufferCodegen>, codegen: Entity<BufferCodegen>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
context_store: Model<ContextStore>, context_store: Entity<ContextStore>,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>, thread_store: Option<WeakEntity<ThreadStore>>,
cx: &mut ViewContext<PromptEditor<BufferCodegen>>, window: &mut Window,
cx: &mut Context<PromptEditor<BufferCodegen>>,
) -> PromptEditor<BufferCodegen> { ) -> PromptEditor<BufferCodegen> {
let codegen_subscription = cx.observe(&codegen, Self::handle_codegen_changed); let codegen_subscription = cx.observe(&codegen, Self::handle_codegen_changed);
let mode = PromptEditorMode::Buffer { let mode = PromptEditorMode::Buffer {
@ -810,7 +844,7 @@ impl PromptEditor<BufferCodegen> {
gutter_dimensions, gutter_dimensions,
}; };
let prompt_editor = cx.new_view(|cx| { let prompt_editor = cx.new(|cx| {
let mut editor = Editor::new( let mut editor = Editor::new(
EditorMode::AutoHeight { EditorMode::AutoHeight {
max_lines: Self::MAX_LINES as usize, max_lines: Self::MAX_LINES as usize,
@ -818,6 +852,7 @@ impl PromptEditor<BufferCodegen> {
prompt_buffer, prompt_buffer,
None, None,
false, false,
window,
cx, cx,
); );
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, 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 // always show the cursor (even when it isn't focused) because
// typing in one will make what you typed appear in all of them. // typing in one will make what you typed appear in all of them.
editor.set_show_cursor_when_unfocused(true, cx); 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 editor
}); });
let context_picker_menu_handle = PopoverMenuHandle::default(); let context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_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( ContextStrip::new(
context_store.clone(), context_store.clone(),
workspace.clone(), workspace.clone(),
@ -839,23 +874,25 @@ impl PromptEditor<BufferCodegen> {
thread_store.clone(), thread_store.clone(),
context_picker_menu_handle.clone(), context_picker_menu_handle.clone(),
SuggestContextKind::Thread, SuggestContextKind::Thread,
window,
cx, cx,
) )
}); });
let context_strip_subscription = 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 { let mut this: PromptEditor<BufferCodegen> = PromptEditor {
editor: prompt_editor.clone(), editor: prompt_editor.clone(),
context_store, context_store,
context_strip, context_strip,
context_picker_menu_handle, context_picker_menu_handle,
model_selector: cx.new_view(|cx| { model_selector: cx.new(|cx| {
AssistantModelSelector::new( AssistantModelSelector::new(
fs, fs,
model_selector_menu_handle.clone(), model_selector_menu_handle.clone(),
prompt_editor.focus_handle(cx), prompt_editor.focus_handle(cx),
window,
cx, cx,
) )
}), }),
@ -872,14 +909,14 @@ impl PromptEditor<BufferCodegen> {
_phantom: Default::default(), _phantom: Default::default(),
}; };
this.subscribe_to_editor(cx); this.subscribe_to_editor(window, cx);
this this
} }
fn handle_codegen_changed( fn handle_codegen_changed(
&mut self, &mut self,
_: Model<BufferCodegen>, _: Entity<BufferCodegen>,
cx: &mut ViewContext<PromptEditor<BufferCodegen>>, cx: &mut Context<PromptEditor<BufferCodegen>>,
) { ) {
match self.codegen_status(cx) { match self.codegen_status(cx) {
CodegenStatus::Idle => { CodegenStatus::Idle => {
@ -918,7 +955,7 @@ impl PromptEditor<BufferCodegen> {
} }
} }
pub fn codegen(&self) -> &Model<BufferCodegen> { pub fn codegen(&self) -> &Entity<BufferCodegen> {
match &self.mode { match &self.mode {
PromptEditorMode::Buffer { codegen, .. } => codegen, PromptEditorMode::Buffer { codegen, .. } => codegen,
PromptEditorMode::Terminal { .. } => unreachable!(), PromptEditorMode::Terminal { .. } => unreachable!(),
@ -951,13 +988,14 @@ impl PromptEditor<TerminalCodegen> {
pub fn new_terminal( pub fn new_terminal(
id: TerminalInlineAssistId, id: TerminalInlineAssistId,
prompt_history: VecDeque<String>, prompt_history: VecDeque<String>,
prompt_buffer: Model<MultiBuffer>, prompt_buffer: Entity<MultiBuffer>,
codegen: Model<TerminalCodegen>, codegen: Entity<TerminalCodegen>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
context_store: Model<ContextStore>, context_store: Entity<ContextStore>,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>, thread_store: Option<WeakEntity<ThreadStore>>,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) -> Self { ) -> Self {
let codegen_subscription = cx.observe(&codegen, Self::handle_codegen_changed); let codegen_subscription = cx.observe(&codegen, Self::handle_codegen_changed);
let mode = PromptEditorMode::Terminal { let mode = PromptEditorMode::Terminal {
@ -966,7 +1004,7 @@ impl PromptEditor<TerminalCodegen> {
height_in_lines: 1, height_in_lines: 1,
}; };
let prompt_editor = cx.new_view(|cx| { let prompt_editor = cx.new(|cx| {
let mut editor = Editor::new( let mut editor = Editor::new(
EditorMode::AutoHeight { EditorMode::AutoHeight {
max_lines: Self::MAX_LINES as usize, max_lines: Self::MAX_LINES as usize,
@ -974,16 +1012,17 @@ impl PromptEditor<TerminalCodegen> {
prompt_buffer, prompt_buffer,
None, None,
false, false,
window,
cx, cx,
); );
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, 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 editor
}); });
let context_picker_menu_handle = PopoverMenuHandle::default(); let context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_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( ContextStrip::new(
context_store.clone(), context_store.clone(),
workspace.clone(), workspace.clone(),
@ -991,23 +1030,25 @@ impl PromptEditor<TerminalCodegen> {
thread_store.clone(), thread_store.clone(),
context_picker_menu_handle.clone(), context_picker_menu_handle.clone(),
SuggestContextKind::Thread, SuggestContextKind::Thread,
window,
cx, cx,
) )
}); });
let context_strip_subscription = 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 { let mut this = Self {
editor: prompt_editor.clone(), editor: prompt_editor.clone(),
context_store, context_store,
context_strip, context_strip,
context_picker_menu_handle, context_picker_menu_handle,
model_selector: cx.new_view(|cx| { model_selector: cx.new(|cx| {
AssistantModelSelector::new( AssistantModelSelector::new(
fs, fs,
model_selector_menu_handle.clone(), model_selector_menu_handle.clone(),
prompt_editor.focus_handle(cx), prompt_editor.focus_handle(cx),
window,
cx, cx,
) )
}), }),
@ -1024,11 +1065,11 @@ impl PromptEditor<TerminalCodegen> {
_phantom: Default::default(), _phantom: Default::default(),
}; };
this.count_lines(cx); this.count_lines(cx);
this.subscribe_to_editor(cx); this.subscribe_to_editor(window, cx);
this 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( let height_in_lines = cmp::max(
2, // Make the editor at least two lines tall, to account for padding and buttons. 2, // Make the editor at least two lines tall, to account for padding and buttons.
cmp::min( 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 { match &self.codegen().read(cx).status {
CodegenStatus::Idle => { CodegenStatus::Idle => {
self.editor self.editor
@ -1070,7 +1111,7 @@ impl PromptEditor<TerminalCodegen> {
} }
} }
pub fn codegen(&self) -> &Model<TerminalCodegen> { pub fn codegen(&self) -> &Entity<TerminalCodegen> {
match &self.mode { match &self.mode {
PromptEditorMode::Buffer { .. } => unreachable!(), PromptEditorMode::Buffer { .. } => unreachable!(),
PromptEditorMode::Terminal { codegen, .. } => codegen, PromptEditorMode::Terminal { codegen, .. } => codegen,
@ -1094,7 +1135,7 @@ fn dismissed_rate_limit_notice() -> bool {
.map_or(false, |s| s.is_some()) .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 { db::write_and_log(cx, move || async move {
if is_dismissed { if is_dismissed {
db::kvp::KEY_VALUE_STORE db::kvp::KEY_VALUE_STORE

View file

@ -4,8 +4,8 @@ use editor::actions::MoveUp;
use editor::{Editor, EditorElement, EditorEvent, EditorStyle}; use editor::{Editor, EditorElement, EditorEvent, EditorStyle};
use fs::Fs; use fs::Fs;
use gpui::{ use gpui::{
pulsating_between, Animation, AnimationExt, AppContext, DismissEvent, FocusableView, Model, pulsating_between, Animation, AnimationExt, App, DismissEvent, Entity, Focusable, Subscription,
Subscription, TextStyle, View, WeakModel, WeakView, TextStyle, WeakEntity,
}; };
use language_model::{LanguageModelRegistry, LanguageModelRequestTool}; use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
use language_model_selector::LanguageModelSelector; use language_model_selector::LanguageModelSelector;
@ -27,14 +27,14 @@ use crate::thread_store::ThreadStore;
use crate::{Chat, ChatMode, RemoveAllContext, ToggleContextPicker, ToggleModelSelector}; use crate::{Chat, ChatMode, RemoveAllContext, ToggleContextPicker, ToggleModelSelector};
pub struct MessageEditor { pub struct MessageEditor {
thread: Model<Thread>, thread: Entity<Thread>,
editor: View<Editor>, editor: Entity<Editor>,
context_store: Model<ContextStore>, context_store: Entity<ContextStore>,
context_strip: View<ContextStrip>, context_strip: Entity<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>, context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
inline_context_picker: View<ContextPicker>, inline_context_picker: Entity<ContextPicker>,
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>, inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
model_selector: View<AssistantModelSelector>, model_selector: Entity<AssistantModelSelector>,
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>, model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
use_tools: bool, use_tools: bool,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
@ -43,36 +43,38 @@ pub struct MessageEditor {
impl MessageEditor { impl MessageEditor {
pub fn new( pub fn new(
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
thread_store: WeakModel<ThreadStore>, thread_store: WeakEntity<ThreadStore>,
thread: Model<Thread>, thread: Entity<Thread>,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) -> 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 context_picker_menu_handle = PopoverMenuHandle::default();
let inline_context_picker_menu_handle = PopoverMenuHandle::default(); let inline_context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_menu_handle = PopoverMenuHandle::default(); let model_selector_menu_handle = PopoverMenuHandle::default();
let editor = cx.new_view(|cx| { let editor = cx.new(|cx| {
let mut editor = Editor::auto_height(10, cx); let mut editor = Editor::auto_height(10, window, cx);
editor.set_placeholder_text("Ask anything, @ to mention, ↑ to select", cx); editor.set_placeholder_text("Ask anything, @ to mention, ↑ to select", cx);
editor.set_show_indent_guides(false, cx); editor.set_show_indent_guides(false, cx);
editor editor
}); });
let inline_context_picker = cx.new_view(|cx| { let inline_context_picker = cx.new(|cx| {
ContextPicker::new( ContextPicker::new(
workspace.clone(), workspace.clone(),
Some(thread_store.clone()), Some(thread_store.clone()),
context_store.downgrade(), context_store.downgrade(),
editor.downgrade(), editor.downgrade(),
ConfirmBehavior::Close, ConfirmBehavior::Close,
window,
cx, cx,
) )
}); });
let context_strip = cx.new_view(|cx| { let context_strip = cx.new(|cx| {
ContextStrip::new( ContextStrip::new(
context_store.clone(), context_store.clone(),
workspace.clone(), workspace.clone(),
@ -80,17 +82,19 @@ impl MessageEditor {
Some(thread_store.clone()), Some(thread_store.clone()),
context_picker_menu_handle.clone(), context_picker_menu_handle.clone(),
SuggestContextKind::File, SuggestContextKind::File,
window,
cx, cx,
) )
}); });
let subscriptions = vec![ let subscriptions = vec![
cx.subscribe(&editor, Self::handle_editor_event), cx.subscribe_in(&editor, window, Self::handle_editor_event),
cx.subscribe( cx.subscribe_in(
&inline_context_picker, &inline_context_picker,
window,
Self::handle_inline_context_picker_event, 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 { Self {
@ -101,11 +105,12 @@ impl MessageEditor {
context_picker_menu_handle, context_picker_menu_handle,
inline_context_picker, inline_context_picker,
inline_context_picker_menu_handle, inline_context_picker_menu_handle,
model_selector: cx.new_view(|cx| { model_selector: cx.new(|cx| {
AssistantModelSelector::new( AssistantModelSelector::new(
fs, fs,
model_selector_menu_handle.clone(), model_selector_menu_handle.clone(),
editor.focus_handle(cx), editor.focus_handle(cx),
window,
cx, cx,
) )
}), }),
@ -115,39 +120,59 @@ impl MessageEditor {
} }
} }
fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) { fn toggle_model_selector(
self.model_selector_menu_handle.toggle(cx) &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; self.use_tools = !self.use_tools;
cx.notify(); cx.notify();
} }
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) { fn toggle_context_picker(
self.context_picker_menu_handle.toggle(cx); &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()); self.context_store.update(cx, |store, _cx| store.clear());
cx.notify(); cx.notify();
} }
fn chat(&mut self, _: &Chat, cx: &mut ViewContext<Self>) { fn chat(&mut self, _: &Chat, window: &mut Window, cx: &mut Context<Self>) {
self.send_to_model(RequestKind::Chat, cx); 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() 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) LanguageModelRegistry::read_global(cx)
.active_model() .active_model()
.is_some() .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(); let provider = LanguageModelRegistry::read_global(cx).active_provider();
if provider if provider
.as_ref() .as_ref()
@ -164,7 +189,7 @@ impl MessageEditor {
let user_message = self.editor.update(cx, |editor, cx| { let user_message = self.editor.update(cx, |editor, cx| {
let text = editor.text(cx); let text = editor.text(cx);
editor.clear(cx); editor.clear(window, cx);
text text
}); });
@ -203,9 +228,10 @@ impl MessageEditor {
fn handle_editor_event( fn handle_editor_event(
&mut self, &mut self,
editor: View<Editor>, editor: &Entity<Editor>,
event: &EditorEvent, event: &EditorEvent,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
match event { match event {
EditorEvent::SelectionsChanged { .. } => { EditorEvent::SelectionsChanged { .. } => {
@ -216,7 +242,7 @@ impl MessageEditor {
let behind_cursor = Point::new(newest_cursor.row, newest_cursor.column - 1); let behind_cursor = Point::new(newest_cursor.row, newest_cursor.column - 1);
let char_behind_cursor = snapshot.chars_at(behind_cursor).next(); let char_behind_cursor = snapshot.chars_at(behind_cursor).next();
if char_behind_cursor == Some('@') { 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( fn handle_inline_context_picker_event(
&mut self, &mut self,
_inline_context_picker: View<ContextPicker>, _inline_context_picker: &Entity<ContextPicker>,
_event: &DismissEvent, _event: &DismissEvent,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
let editor_focus_handle = self.editor.focus_handle(cx); let editor_focus_handle = self.editor.focus_handle(cx);
cx.focus(&editor_focus_handle); window.focus(&editor_focus_handle);
} }
fn handle_context_strip_event( fn handle_context_strip_event(
&mut self, &mut self,
_context_strip: View<ContextStrip>, _context_strip: &Entity<ContextStrip>,
event: &ContextStripEvent, event: &ContextStripEvent,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
match event { match event {
ContextStripEvent::PickerDismissed ContextStripEvent::PickerDismissed
| ContextStripEvent::BlurredEmpty | ContextStripEvent::BlurredEmpty
| ContextStripEvent::BlurredDown => { | ContextStripEvent::BlurredDown => {
let editor_focus_handle = self.editor.focus_handle(cx); let editor_focus_handle = self.editor.focus_handle(cx);
cx.focus(&editor_focus_handle); window.focus(&editor_focus_handle);
} }
ContextStripEvent::BlurredUp => {} 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() if self.context_picker_menu_handle.is_deployed()
|| self.inline_context_picker_menu_handle.is_deployed() || self.inline_context_picker_menu_handle.is_deployed()
{ {
cx.propagate(); cx.propagate();
} else { } else {
cx.focus_view(&self.context_strip); self.context_strip.focus_handle(cx).focus(window);
} }
} }
} }
impl FocusableView for MessageEditor { impl Focusable for MessageEditor {
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle { fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
self.editor.focus_handle(cx) self.editor.focus_handle(cx)
} }
} }
impl Render for MessageEditor { 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 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 focus_handle = self.editor.focus_handle(cx);
let inline_context_picker = self.inline_context_picker.clone(); let inline_context_picker = self.inline_context_picker.clone();
let bg_color = cx.theme().colors().editor_background; let bg_color = cx.theme().colors().editor_background;
@ -326,9 +354,9 @@ impl Render for MessageEditor {
}) })
.child( .child(
PopoverMenu::new("inline-context-picker") PopoverMenu::new("inline-context-picker")
.menu(move |cx| { .menu(move |window, cx| {
inline_context_picker.update(cx, |this, cx| { inline_context_picker.update(cx, |this, cx| {
this.init(cx); this.init(window, cx);
}); });
Some(inline_context_picker.clone()) Some(inline_context_picker.clone())
@ -351,7 +379,7 @@ impl Render for MessageEditor {
.child( .child(
Switch::new("use-tools", self.use_tools.into()) Switch::new("use-tools", self.use_tools.into())
.label("Tools") .label("Tools")
.on_click(cx.listener(|this, selection, _cx| { .on_click(cx.listener(|this, selection, _window, _cx| {
this.use_tools = match selection { this.use_tools = match selection {
ToggleState::Selected => true, ToggleState::Selected => true,
ToggleState::Unselected ToggleState::Unselected
@ -361,7 +389,7 @@ impl Render for MessageEditor {
.key_binding(KeyBinding::for_action_in( .key_binding(KeyBinding::for_action_in(
&ChatMode, &ChatMode,
&focus_handle, &focus_handle,
cx, window,
)), )),
) )
.child(h_flex().gap_1().child(self.model_selector.clone()).child( .child(h_flex().gap_1().child(self.model_selector.clone()).child(
@ -390,14 +418,17 @@ impl Render for MessageEditor {
KeyBinding::for_action_in( KeyBinding::for_action_in(
&editor::actions::Cancel, &editor::actions::Cancel,
&focus_handle, &focus_handle,
cx, window,
) )
.map(|binding| binding.into_any_element()), .map(|binding| binding.into_any_element()),
), ),
) )
.on_click(move |_event, cx| { .on_click(move |_event, window, cx| {
focus_handle focus_handle.dispatch_action(
.dispatch_action(&editor::actions::Cancel, cx); &editor::actions::Cancel,
window,
cx,
);
}) })
} else { } else {
ButtonLike::new("submit-message") ButtonLike::new("submit-message")
@ -417,23 +448,22 @@ impl Render for MessageEditor {
KeyBinding::for_action_in( KeyBinding::for_action_in(
&Chat, &Chat,
&focus_handle, &focus_handle,
cx, window,
) )
.map(|binding| binding.into_any_element()), .map(|binding| binding.into_any_element()),
), ),
) )
.on_click(move |_event, cx| { .on_click(move |_event, window, cx| {
focus_handle.dispatch_action(&Chat, cx); focus_handle.dispatch_action(&Chat, window, cx);
}) })
.when(is_editor_empty, |button| { .when(is_editor_empty, |button| {
button.tooltip(|cx| { button
Tooltip::text("Type a message to submit", cx) .tooltip(Tooltip::text("Type a message to submit"))
})
}) })
.when(!is_model_selected, |button| { .when(!is_model_selected, |button| {
button.tooltip(|cx| { button.tooltip(Tooltip::text(
Tooltip::text("Select a model to continue", cx) "Select a model to continue",
}) ))
}) })
}, },
)), )),

View file

@ -1,7 +1,7 @@
use crate::inline_prompt_editor::CodegenStatus; use crate::inline_prompt_editor::CodegenStatus;
use client::telemetry::Telemetry; use client::telemetry::Telemetry;
use futures::{channel::mpsc, SinkExt, StreamExt}; 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_model::{LanguageModelRegistry, LanguageModelRequest};
use language_models::report_assistant_event; use language_models::report_assistant_event;
use std::{sync::Arc, time::Instant}; use std::{sync::Arc, time::Instant};
@ -11,7 +11,7 @@ use terminal::Terminal;
pub struct TerminalCodegen { pub struct TerminalCodegen {
pub status: CodegenStatus, pub status: CodegenStatus,
pub telemetry: Option<Arc<Telemetry>>, pub telemetry: Option<Arc<Telemetry>>,
terminal: Model<Terminal>, terminal: Entity<Terminal>,
generation: Task<()>, generation: Task<()>,
pub message_id: Option<String>, pub message_id: Option<String>,
transaction: Option<TerminalTransaction>, transaction: Option<TerminalTransaction>,
@ -20,7 +20,7 @@ pub struct TerminalCodegen {
impl EventEmitter<CodegenEvent> for TerminalCodegen {} impl EventEmitter<CodegenEvent> for TerminalCodegen {}
impl 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 { Self {
terminal, terminal,
telemetry, 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 { let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
return; return;
}; };
@ -131,20 +131,20 @@ impl TerminalCodegen {
cx.notify(); 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.status = CodegenStatus::Done;
self.generation = Task::ready(()); self.generation = Task::ready(());
cx.emit(CodegenEvent::Finished); cx.emit(CodegenEvent::Finished);
cx.notify(); 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() { if let Some(transaction) = self.transaction.take() {
transaction.complete(cx); 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() { if let Some(transaction) = self.transaction.take() {
transaction.undo(cx); transaction.undo(cx);
} }
@ -160,27 +160,27 @@ pub const CLEAR_INPUT: &str = "\x15";
const CARRIAGE_RETURN: &str = "\x0d"; const CARRIAGE_RETURN: &str = "\x0d";
struct TerminalTransaction { struct TerminalTransaction {
terminal: Model<Terminal>, terminal: Entity<Terminal>,
} }
impl TerminalTransaction { impl TerminalTransaction {
pub fn start(terminal: Model<Terminal>) -> Self { pub fn start(terminal: Entity<Terminal>) -> Self {
Self { terminal } 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 // Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
let input = Self::sanitize_input(hunk); let input = Self::sanitize_input(hunk);
self.terminal self.terminal
.update(cx, |terminal, _| terminal.input(input)); .update(cx, |terminal, _| terminal.input(input));
} }
pub fn undo(&self, cx: &mut AppContext) { pub fn undo(&self, cx: &mut App) {
self.terminal self.terminal
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.to_string())); .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, _| { self.terminal.update(cx, |terminal, _| {
terminal.input(CARRIAGE_RETURN.to_string()) terminal.input(CARRIAGE_RETURN.to_string())
}); });

View file

@ -10,10 +10,7 @@ use client::telemetry::Telemetry;
use collections::{HashMap, VecDeque}; use collections::{HashMap, VecDeque};
use editor::{actions::SelectAll, MultiBuffer}; use editor::{actions::SelectAll, MultiBuffer};
use fs::Fs; use fs::Fs;
use gpui::{ use gpui::{App, Entity, Focusable, Global, Subscription, UpdateGlobal, WeakEntity};
AppContext, Context, FocusableView, Global, Model, Subscription, UpdateGlobal, View, WeakModel,
WeakView,
};
use language::Buffer; use language::Buffer;
use language_model::{ use language_model::{
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
@ -31,7 +28,7 @@ pub fn init(
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
prompt_builder: Arc<PromptBuilder>, prompt_builder: Arc<PromptBuilder>,
telemetry: Arc<Telemetry>, telemetry: Arc<Telemetry>,
cx: &mut AppContext, cx: &mut App,
) { ) {
cx.set_global(TerminalInlineAssistant::new(fs, prompt_builder, telemetry)); cx.set_global(TerminalInlineAssistant::new(fs, prompt_builder, telemetry));
} }
@ -68,20 +65,20 @@ impl TerminalInlineAssistant {
pub fn assist( pub fn assist(
&mut self, &mut self,
terminal_view: &View<TerminalView>, terminal_view: &Entity<TerminalView>,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>, thread_store: Option<WeakEntity<ThreadStore>>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) { ) {
let terminal = terminal_view.read(cx).terminal().clone(); let terminal = terminal_view.read(cx).terminal().clone();
let assist_id = self.next_assist_id.post_inc(); let assist_id = self.next_assist_id.post_inc();
let prompt_buffer = cx.new_model(|cx| { let prompt_buffer =
MultiBuffer::singleton(cx.new_model(|cx| Buffer::local(String::new(), cx)), cx) 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 context_store = cx.new_model(|_cx| ContextStore::new(workspace.clone())); let codegen = cx.new(|_| TerminalCodegen::new(terminal, self.telemetry.clone()));
let codegen = cx.new_model(|_| TerminalCodegen::new(terminal, self.telemetry.clone()));
let prompt_editor = cx.new_view(|cx| { let prompt_editor = cx.new(|cx| {
PromptEditor::new_terminal( PromptEditor::new_terminal(
assist_id, assist_id,
self.prompt_history.clone(), self.prompt_history.clone(),
@ -91,6 +88,7 @@ impl TerminalInlineAssistant {
context_store.clone(), context_store.clone(),
workspace.clone(), workspace.clone(),
thread_store.clone(), thread_store.clone(),
window,
cx, cx,
) )
}); });
@ -100,7 +98,7 @@ impl TerminalInlineAssistant {
render: Box::new(move |_| prompt_editor_render.clone().into_any_element()), render: Box::new(move |_| prompt_editor_render.clone().into_any_element()),
}; };
terminal_view.update(cx, |terminal_view, cx| { 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( let terminal_assistant = TerminalInlineAssist::new(
@ -109,21 +107,27 @@ impl TerminalInlineAssistant {
prompt_editor, prompt_editor,
workspace.clone(), workspace.clone(),
context_store, context_store,
window,
cx, cx,
); );
self.assists.insert(assist_id, terminal_assistant); 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]; let assist = &self.assists[&assist_id];
if let Some(prompt_editor) = assist.prompt_editor.as_ref() { if let Some(prompt_editor) = assist.prompt_editor.as_ref() {
prompt_editor.update(cx, |this, cx| { prompt_editor.update(cx, |this, cx| {
this.editor.update(cx, |editor, cx| { this.editor.update(cx, |editor, cx| {
editor.focus(cx); window.focus(&editor.focus_handle(cx));
editor.select_all(&SelectAll, cx); editor.select_all(&SelectAll, window, cx);
}); });
}); });
} }
@ -131,9 +135,10 @@ impl TerminalInlineAssistant {
fn handle_prompt_editor_event( fn handle_prompt_editor_event(
&mut self, &mut self,
prompt_editor: View<PromptEditor<TerminalCodegen>>, prompt_editor: Entity<PromptEditor<TerminalCodegen>>,
event: &PromptEditorEvent, event: &PromptEditorEvent,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) { ) {
let assist_id = prompt_editor.read(cx).id(); let assist_id = prompt_editor.read(cx).id();
match event { match event {
@ -144,21 +149,21 @@ impl TerminalInlineAssistant {
self.stop_assist(assist_id, cx); self.stop_assist(assist_id, cx);
} }
PromptEditorEvent::ConfirmRequested { execute } => { PromptEditorEvent::ConfirmRequested { execute } => {
self.finish_assist(assist_id, false, *execute, cx); self.finish_assist(assist_id, false, *execute, window, cx);
} }
PromptEditorEvent::CancelRequested => { PromptEditorEvent::CancelRequested => {
self.finish_assist(assist_id, true, false, cx); self.finish_assist(assist_id, true, false, window, cx);
} }
PromptEditorEvent::DismissRequested => { PromptEditorEvent::DismissRequested => {
self.dismiss_assist(assist_id, cx); self.dismiss_assist(assist_id, window, cx);
} }
PromptEditorEvent::Resized { height_in_lines } => { 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) { let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
assist assist
} else { } else {
@ -196,7 +201,7 @@ impl TerminalInlineAssistant {
codegen.update(cx, |codegen, cx| codegen.start(request, cx)); 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) { let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
assist assist
} else { } else {
@ -209,7 +214,7 @@ impl TerminalInlineAssistant {
fn request_for_inline_assist( fn request_for_inline_assist(
&self, &self,
assist_id: TerminalInlineAssistId, assist_id: TerminalInlineAssistId,
cx: &mut WindowContext, cx: &mut App,
) -> Result<LanguageModelRequest> { ) -> Result<LanguageModelRequest> {
let assist = self.assists.get(&assist_id).context("invalid assist")?; let assist = self.assists.get(&assist_id).context("invalid assist")?;
@ -265,16 +270,17 @@ impl TerminalInlineAssistant {
assist_id: TerminalInlineAssistId, assist_id: TerminalInlineAssistId,
undo: bool, undo: bool,
execute: 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) { if let Some(assist) = self.assists.remove(&assist_id) {
assist assist
.terminal .terminal
.update(cx, |this, cx| { .update(cx, |this, cx| {
this.clear_block_below_cursor(cx); this.clear_block_below_cursor(cx);
this.focus_handle(cx).focus(cx); this.focus_handle(cx).focus(window);
}) })
.log_err(); .log_err();
@ -317,7 +323,8 @@ impl TerminalInlineAssistant {
fn dismiss_assist( fn dismiss_assist(
&mut self, &mut self,
assist_id: TerminalInlineAssistId, assist_id: TerminalInlineAssistId,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> bool { ) -> bool {
let Some(assist) = self.assists.get_mut(&assist_id) else { let Some(assist) = self.assists.get_mut(&assist_id) else {
return false; return false;
@ -330,7 +337,7 @@ impl TerminalInlineAssistant {
.terminal .terminal
.update(cx, |this, cx| { .update(cx, |this, cx| {
this.clear_block_below_cursor(cx); this.clear_block_below_cursor(cx);
this.focus_handle(cx).focus(cx); this.focus_handle(cx).focus(window);
}) })
.is_ok() .is_ok()
} }
@ -339,7 +346,8 @@ impl TerminalInlineAssistant {
&mut self, &mut self,
assist_id: TerminalInlineAssistId, assist_id: TerminalInlineAssistId,
height: u8, height: u8,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) { ) {
if let Some(assist) = self.assists.get_mut(&assist_id) { if let Some(assist) = self.assists.get_mut(&assist_id) {
if let Some(prompt_editor) = assist.prompt_editor.as_ref().cloned() { if let Some(prompt_editor) = assist.prompt_editor.as_ref().cloned() {
@ -351,7 +359,7 @@ impl TerminalInlineAssistant {
height, height,
render: Box::new(move |_| prompt_editor.clone().into_any_element()), 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(); .log_err();
} }
@ -360,22 +368,23 @@ impl TerminalInlineAssistant {
} }
struct TerminalInlineAssist { struct TerminalInlineAssist {
terminal: WeakView<TerminalView>, terminal: WeakEntity<TerminalView>,
prompt_editor: Option<View<PromptEditor<TerminalCodegen>>>, prompt_editor: Option<Entity<PromptEditor<TerminalCodegen>>>,
codegen: Model<TerminalCodegen>, codegen: Entity<TerminalCodegen>,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
context_store: Model<ContextStore>, context_store: Entity<ContextStore>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
impl TerminalInlineAssist { impl TerminalInlineAssist {
pub fn new( pub fn new(
assist_id: TerminalInlineAssistId, assist_id: TerminalInlineAssistId,
terminal: &View<TerminalView>, terminal: &Entity<TerminalView>,
prompt_editor: View<PromptEditor<TerminalCodegen>>, prompt_editor: Entity<PromptEditor<TerminalCodegen>>,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
context_store: Model<ContextStore>, context_store: Entity<ContextStore>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Self { ) -> Self {
let codegen = prompt_editor.read(cx).codegen().clone(); let codegen = prompt_editor.read(cx).codegen().clone();
Self { Self {
@ -385,12 +394,12 @@ impl TerminalInlineAssist {
workspace: workspace.clone(), workspace: workspace.clone(),
context_store, context_store,
_subscriptions: vec![ _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| { 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 { TerminalInlineAssistant::update_global(cx, |this, cx| match event {
CodegenEvent::Finished => { CodegenEvent::Finished => {
let assist = if let Some(assist) = this.assists.get(&assist_id) { let assist = if let Some(assist) = this.assists.get(&assist_id) {
@ -419,7 +428,7 @@ impl TerminalInlineAssist {
} }
if assist.prompt_editor.is_none() { if assist.prompt_editor.is_none() {
this.finish_assist(assist_id, false, false, cx); this.finish_assist(assist_id, false, false, window, cx);
} }
} }
}) })

View file

@ -6,7 +6,7 @@ use chrono::{DateTime, Utc};
use collections::{BTreeMap, HashMap, HashSet}; use collections::{BTreeMap, HashMap, HashSet};
use futures::future::Shared; use futures::future::Shared;
use futures::{FutureExt as _, StreamExt as _}; use futures::{FutureExt as _, StreamExt as _};
use gpui::{AppContext, EventEmitter, ModelContext, SharedString, Task}; use gpui::{App, Context, EventEmitter, SharedString, Task};
use language_model::{ use language_model::{
LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest, LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse, LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse,
@ -76,7 +76,7 @@ pub struct Thread {
} }
impl 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 { Self {
id: ThreadId::new(), id: ThreadId::new(),
updated_at: Utc::now(), updated_at: Utc::now(),
@ -99,7 +99,7 @@ impl Thread {
id: ThreadId, id: ThreadId,
saved: SavedThread, saved: SavedThread,
tools: Arc<ToolWorkingSet>, tools: Arc<ToolWorkingSet>,
_cx: &mut ModelContext<Self>, _cx: &mut Context<Self>,
) -> Self { ) -> Self {
let next_message_id = MessageId(saved.messages.len()); let next_message_id = MessageId(saved.messages.len());
@ -154,7 +154,7 @@ impl Thread {
self.summary.clone().unwrap_or(DEFAULT) 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()); self.summary = Some(summary.into());
cx.emit(ThreadEvent::SummaryChanged); cx.emit(ThreadEvent::SummaryChanged);
} }
@ -194,7 +194,7 @@ impl Thread {
&mut self, &mut self,
text: impl Into<String>, text: impl Into<String>,
context: Vec<ContextSnapshot>, context: Vec<ContextSnapshot>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
let message_id = self.insert_message(Role::User, text, cx); let message_id = self.insert_message(Role::User, text, cx);
let context_ids = context.iter().map(|context| context.id).collect::<Vec<_>>(); let context_ids = context.iter().map(|context| context.id).collect::<Vec<_>>();
@ -207,7 +207,7 @@ impl Thread {
&mut self, &mut self,
role: Role, role: Role,
text: impl Into<String>, text: impl Into<String>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> MessageId { ) -> MessageId {
let id = self.next_message_id.post_inc(); let id = self.next_message_id.post_inc();
self.messages.push(Message { self.messages.push(Message {
@ -244,7 +244,7 @@ impl Thread {
pub fn to_completion_request( pub fn to_completion_request(
&self, &self,
_request_kind: RequestKind, _request_kind: RequestKind,
_cx: &AppContext, _cx: &App,
) -> LanguageModelRequest { ) -> LanguageModelRequest {
let mut request = LanguageModelRequest { let mut request = LanguageModelRequest {
messages: vec![], messages: vec![],
@ -314,7 +314,7 @@ impl Thread {
&mut self, &mut self,
request: LanguageModelRequest, request: LanguageModelRequest,
model: Arc<dyn LanguageModel>, model: Arc<dyn LanguageModel>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
let pending_completion_id = post_inc(&mut self.completion_count); 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 { let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
return; return;
}; };
@ -497,7 +497,7 @@ impl Thread {
assistant_message_id: MessageId, assistant_message_id: MessageId,
tool_use_id: LanguageModelToolUseId, tool_use_id: LanguageModelToolUseId,
output: Task<Result<String>>, output: Task<Result<String>>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
let insert_output_task = cx.spawn(|thread, mut cx| { let insert_output_task = cx.spawn(|thread, mut cx| {
let tool_use_id = tool_use_id.clone(); let tool_use_id = tool_use_id.clone();

View file

@ -1,6 +1,6 @@
use gpui::{ use gpui::{
uniform_list, AppContext, FocusHandle, FocusableView, Model, ScrollStrategy, uniform_list, App, Entity, FocusHandle, Focusable, ScrollStrategy, UniformListScrollHandle,
UniformListScrollHandle, WeakView, WeakEntity,
}; };
use time::{OffsetDateTime, UtcOffset}; use time::{OffsetDateTime, UtcOffset};
use ui::{prelude::*, IconButtonShape, ListItem, ListItemSpacing, Tooltip}; use ui::{prelude::*, IconButtonShape, ListItem, ListItemSpacing, Tooltip};
@ -10,17 +10,18 @@ use crate::{AssistantPanel, RemoveSelectedThread};
pub struct ThreadHistory { pub struct ThreadHistory {
focus_handle: FocusHandle, focus_handle: FocusHandle,
assistant_panel: WeakView<AssistantPanel>, assistant_panel: WeakEntity<AssistantPanel>,
thread_store: Model<ThreadStore>, thread_store: Entity<ThreadStore>,
scroll_handle: UniformListScrollHandle, scroll_handle: UniformListScrollHandle,
selected_index: usize, selected_index: usize,
} }
impl ThreadHistory { impl ThreadHistory {
pub(crate) fn new( pub(crate) fn new(
assistant_panel: WeakView<AssistantPanel>, assistant_panel: WeakEntity<AssistantPanel>,
thread_store: Model<ThreadStore>, thread_store: Entity<ThreadStore>,
cx: &mut ViewContext<Self>,
cx: &mut Context<Self>,
) -> Self { ) -> Self {
Self { Self {
focus_handle: cx.focus_handle(), 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(); let count = self.thread_store.read(cx).thread_count();
if count > 0 { if count > 0 {
if self.selected_index == 0 { if self.selected_index == 0 {
self.set_selected_index(count - 1, cx); self.set_selected_index(count - 1, window, cx);
} else { } 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(); let count = self.thread_store.read(cx).thread_count();
if count > 0 { if count > 0 {
if self.selected_index == count - 1 { if self.selected_index == count - 1 {
self.set_selected_index(0, cx); self.set_selected_index(0, window, cx);
} else { } 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(); let count = self.thread_store.read(cx).thread_count();
if count > 0 { 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(); let count = self.thread_store.read(cx).thread_count();
if count > 0 { 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.selected_index = index;
self.scroll_handle self.scroll_handle
.scroll_to_item(index, ScrollStrategy::Top); .scroll_to_item(index, ScrollStrategy::Top);
cx.notify(); 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()); let threads = self.thread_store.update(cx, |this, _cx| this.threads());
if let Some(thread) = threads.get(self.selected_index) { if let Some(thread) = threads.get(self.selected_index) {
self.assistant_panel 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(); .ok();
cx.notify(); 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()); let threads = self.thread_store.update(cx, |this, _cx| this.threads());
if let Some(thread) = threads.get(self.selected_index) { if let Some(thread) = threads.get(self.selected_index) {
@ -101,14 +117,14 @@ impl ThreadHistory {
} }
} }
impl FocusableView for ThreadHistory { impl Focusable for ThreadHistory {
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle { fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone() self.focus_handle.clone()
} }
} }
impl Render for ThreadHistory { 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 threads = self.thread_store.update(cx, |this, _cx| this.threads());
let selected_index = self.selected_index; let selected_index = self.selected_index;
@ -138,10 +154,10 @@ impl Render for ThreadHistory {
} else { } else {
history.child( history.child(
uniform_list( uniform_list(
cx.view().clone(), cx.model().clone(),
"thread-history", "thread-history",
threads.len(), threads.len(),
move |history, range, _cx| { move |history, range, _window, _cx| {
threads[range] threads[range]
.iter() .iter()
.enumerate() .enumerate()
@ -166,14 +182,14 @@ impl Render for ThreadHistory {
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct PastThread { pub struct PastThread {
thread: SavedThreadMetadata, thread: SavedThreadMetadata,
assistant_panel: WeakView<AssistantPanel>, assistant_panel: WeakEntity<AssistantPanel>,
selected: bool, selected: bool,
} }
impl PastThread { impl PastThread {
pub fn new( pub fn new(
thread: SavedThreadMetadata, thread: SavedThreadMetadata,
assistant_panel: WeakView<AssistantPanel>, assistant_panel: WeakEntity<AssistantPanel>,
selected: bool, selected: bool,
) -> Self { ) -> Self {
Self { Self {
@ -185,7 +201,7 @@ impl PastThread {
} }
impl RenderOnce for 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 summary = self.thread.summary;
let thread_timestamp = time_format::format_localized_timestamp( let thread_timestamp = time_format::format_localized_timestamp(
@ -219,11 +235,11 @@ impl RenderOnce for PastThread {
IconButton::new("delete", IconName::TrashAlt) IconButton::new("delete", IconName::TrashAlt)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.tooltip(|cx| Tooltip::text("Delete Thread", cx)) .tooltip(Tooltip::text("Delete Thread"))
.on_click({ .on_click({
let assistant_panel = self.assistant_panel.clone(); let assistant_panel = self.assistant_panel.clone();
let id = self.thread.id.clone(); let id = self.thread.id.clone();
move |_event, cx| { move |_event, _window, cx| {
assistant_panel assistant_panel
.update(cx, |this, cx| { .update(cx, |this, cx| {
this.delete_thread(&id, cx); this.delete_thread(&id, cx);
@ -236,10 +252,10 @@ impl RenderOnce for PastThread {
.on_click({ .on_click({
let assistant_panel = self.assistant_panel.clone(); let assistant_panel = self.assistant_panel.clone();
let id = self.thread.id.clone(); let id = self.thread.id.clone();
move |_event, cx| { move |_event, window, cx| {
assistant_panel assistant_panel
.update(cx, |this, cx| { .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(); .ok();
} }

View file

@ -9,7 +9,7 @@ use context_server::manager::ContextServerManager;
use context_server::{ContextServerFactoryRegistry, ContextServerTool}; use context_server::{ContextServerFactoryRegistry, ContextServerTool};
use futures::future::{self, BoxFuture, Shared}; use futures::future::{self, BoxFuture, Shared};
use futures::FutureExt as _; 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::types::SerdeBincode;
use heed::Database; use heed::Database;
use language_model::Role; use language_model::Role;
@ -21,9 +21,9 @@ use crate::thread::{MessageId, Thread, ThreadId};
pub struct ThreadStore { pub struct ThreadStore {
#[allow(unused)] #[allow(unused)]
project: Model<Project>, project: Entity<Project>,
tools: Arc<ToolWorkingSet>, tools: Arc<ToolWorkingSet>,
context_server_manager: Model<ContextServerManager>, context_server_manager: Entity<ContextServerManager>,
context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>, context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>,
threads: Vec<SavedThreadMetadata>, threads: Vec<SavedThreadMetadata>,
database_future: Shared<BoxFuture<'static, Result<Arc<ThreadsDatabase>, Arc<anyhow::Error>>>>, database_future: Shared<BoxFuture<'static, Result<Arc<ThreadsDatabase>, Arc<anyhow::Error>>>>,
@ -31,15 +31,15 @@ pub struct ThreadStore {
impl ThreadStore { impl ThreadStore {
pub fn new( pub fn new(
project: Model<Project>, project: Entity<Project>,
tools: Arc<ToolWorkingSet>, tools: Arc<ToolWorkingSet>,
cx: &mut AppContext, cx: &mut App,
) -> Task<Result<Model<Self>>> { ) -> Task<Result<Entity<Self>>> {
cx.spawn(|mut cx| async move { 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 = let context_server_factory_registry =
ContextServerFactoryRegistry::default_global(cx); 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) ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
}); });
@ -88,15 +88,15 @@ impl ThreadStore {
self.threads().into_iter().take(limit).collect() self.threads().into_iter().take(limit).collect()
} }
pub fn create_thread(&mut self, cx: &mut ModelContext<Self>) -> Model<Thread> { pub fn create_thread(&mut self, cx: &mut Context<Self>) -> Entity<Thread> {
cx.new_model(|cx| Thread::new(self.tools.clone(), cx)) cx.new(|cx| Thread::new(self.tools.clone(), cx))
} }
pub fn open_thread( pub fn open_thread(
&self, &self,
id: &ThreadId, id: &ThreadId,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<Model<Thread>>> { ) -> Task<Result<Entity<Thread>>> {
let id = id.clone(); let id = id.clone();
let database_future = self.database_future.clone(); let database_future = self.database_future.clone();
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
@ -107,16 +107,12 @@ impl ThreadStore {
.ok_or_else(|| anyhow!("no thread found with ID: {id:?}"))?; .ok_or_else(|| anyhow!("no thread found with ID: {id:?}"))?;
this.update(&mut cx, |this, cx| { 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( pub fn save_thread(&self, thread: &Entity<Thread>, cx: &mut Context<Self>) -> Task<Result<()>> {
&self,
thread: &Model<Thread>,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
let (metadata, thread) = thread.update(cx, |thread, _cx| { let (metadata, thread) = thread.update(cx, |thread, _cx| {
let id = thread.id().clone(); let id = thread.id().clone();
let thread = SavedThread { let thread = SavedThread {
@ -144,11 +140,7 @@ impl ThreadStore {
}) })
} }
pub fn delete_thread( pub fn delete_thread(&mut self, id: &ThreadId, cx: &mut Context<Self>) -> Task<Result<()>> {
&mut self,
id: &ThreadId,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
let id = id.clone(); let id = id.clone();
let database_future = self.database_future.clone(); let database_future = self.database_future.clone();
cx.spawn(|this, mut cx| async move { 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(); let database_future = self.database_future.clone();
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
let threads = database_future 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( cx.subscribe(
&self.context_server_manager.clone(), &self.context_server_manager.clone(),
Self::handle_context_server_event, Self::handle_context_server_event,
@ -187,9 +179,9 @@ impl ThreadStore {
fn handle_context_server_event( fn handle_context_server_event(
&mut self, &mut self,
context_server_manager: Model<ContextServerManager>, context_server_manager: Entity<ContextServerManager>,
event: &context_server::manager::Event, event: &context_server::manager::Event,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
let tool_working_set = self.tools.clone(); let tool_working_set = self.tools.clone();
match event { match event {

View file

@ -11,15 +11,15 @@ pub enum ContextPill {
context: ContextSnapshot, context: ContextSnapshot,
dupe_name: bool, dupe_name: bool,
focused: bool, focused: bool,
on_click: 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 WindowContext)>>, on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
}, },
Suggested { Suggested {
name: SharedString, name: SharedString,
icon_path: Option<SharedString>, icon_path: Option<SharedString>,
kind: ContextKind, kind: ContextKind,
focused: bool, 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, context: ContextSnapshot,
dupe_name: bool, dupe_name: bool,
focused: 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 {
Self::Added { Self::Added {
context, 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 { match &mut self {
ContextPill::Added { on_click, .. } => { ContextPill::Added { on_click, .. } => {
*on_click = Some(listener); *on_click = Some(listener);
@ -95,7 +95,7 @@ impl ContextPill {
} }
impl RenderOnce for 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 color = cx.theme().colors();
let base_pill = h_flex() let base_pill = h_flex()
@ -139,7 +139,7 @@ impl RenderOnce for ContextPill {
} }
}) })
.when_some(context.tooltip.clone(), |element, tooltip| { .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| { .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) IconButton::new(("remove", context.id.0), IconName::Close)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.icon_size(IconSize::XSmall) .icon_size(IconSize::XSmall)
.tooltip(|cx| Tooltip::text("Remove Context", cx)) .tooltip(Tooltip::text("Remove Context"))
.on_click({ .on_click({
let on_remove = on_remove.clone(); 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| { .when_some(on_click.as_ref(), |element, on_click| {
let on_click = on_click.clone(); 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 { ContextPill::Suggested {
name, name,
@ -195,10 +195,12 @@ impl RenderOnce for ContextPill {
.size(IconSize::XSmall) .size(IconSize::XSmall)
.into_any_element(), .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| { .when_some(on_click.as_ref(), |element, on_click| {
let on_click = on_click.clone(); 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))
}), }),
} }
} }

View file

@ -9,7 +9,7 @@ mod slash_command_picker;
use std::sync::Arc; use std::sync::Arc;
use client::Client; use client::Client;
use gpui::AppContext; use gpui::App;
pub use crate::context::*; pub use crate::context::*;
pub use crate::context_editor::*; pub use crate::context_editor::*;
@ -18,6 +18,6 @@ pub use crate::context_store::*;
pub use crate::patch::*; pub use crate::patch::*;
pub use crate::slash_command::*; pub use crate::slash_command::*;
pub fn init(client: Arc<Client>, _cx: &mut AppContext) { pub fn init(client: Arc<Client>, _cx: &mut App) {
context_store::init(&client.into()); context_store::init(&client.into());
} }

View file

@ -16,8 +16,8 @@ use feature_flags::{FeatureFlagAppExt, ToolUseFeatureFlag};
use fs::{Fs, RemoveOptions}; use fs::{Fs, RemoveOptions};
use futures::{future::Shared, FutureExt, StreamExt}; use futures::{future::Shared, FutureExt, StreamExt};
use gpui::{ use gpui::{
AppContext, Context as _, EventEmitter, Model, ModelContext, RenderImage, SharedString, App, AppContext as _, Context, Entity, EventEmitter, RenderImage, SharedString, Subscription,
Subscription, Task, Task,
}; };
use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset}; use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset};
use language_model::{ use language_model::{
@ -588,13 +588,13 @@ pub enum XmlTagKind {
Operation, Operation,
} }
pub struct Context { pub struct AssistantContext {
id: ContextId, id: ContextId,
timestamp: clock::Lamport, timestamp: clock::Lamport,
version: clock::Global, version: clock::Global,
pending_ops: Vec<ContextOperation>, pending_ops: Vec<ContextOperation>,
operations: Vec<ContextOperation>, operations: Vec<ContextOperation>,
buffer: Model<Buffer>, buffer: Entity<Buffer>,
parsed_slash_commands: Vec<ParsedSlashCommand>, parsed_slash_commands: Vec<ParsedSlashCommand>,
invoked_slash_commands: HashMap<InvokedSlashCommandId, InvokedSlashCommand>, invoked_slash_commands: HashMap<InvokedSlashCommandId, InvokedSlashCommand>,
edits_since_last_parse: language::Subscription, edits_since_last_parse: language::Subscription,
@ -619,7 +619,7 @@ pub struct Context {
language_registry: Arc<LanguageRegistry>, language_registry: Arc<LanguageRegistry>,
patches: Vec<AssistantPatch>, patches: Vec<AssistantPatch>,
xml_tags: Vec<XmlTag>, xml_tags: Vec<XmlTag>,
project: Option<Model<Project>>, project: Option<Entity<Project>>,
prompt_builder: Arc<PromptBuilder>, prompt_builder: Arc<PromptBuilder>,
} }
@ -645,17 +645,17 @@ impl ContextAnnotation for XmlTag {
} }
} }
impl EventEmitter<ContextEvent> for Context {} impl EventEmitter<ContextEvent> for AssistantContext {}
impl Context { impl AssistantContext {
pub fn local( pub fn local(
language_registry: Arc<LanguageRegistry>, language_registry: Arc<LanguageRegistry>,
project: Option<Model<Project>>, project: Option<Entity<Project>>,
telemetry: Option<Arc<Telemetry>>, telemetry: Option<Arc<Telemetry>>,
prompt_builder: Arc<PromptBuilder>, prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>, slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>, tools: Arc<ToolWorkingSet>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Self { ) -> Self {
Self::new( Self::new(
ContextId::new(), ContextId::new(),
@ -680,11 +680,11 @@ impl Context {
prompt_builder: Arc<PromptBuilder>, prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>, slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>, tools: Arc<ToolWorkingSet>,
project: Option<Model<Project>>, project: Option<Entity<Project>>,
telemetry: Option<Arc<Telemetry>>, telemetry: Option<Arc<Telemetry>>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Self { ) -> Self {
let buffer = cx.new_model(|_cx| { let buffer = cx.new(|_cx| {
let buffer = Buffer::remote( let buffer = Buffer::remote(
language::BufferId::new(1).unwrap(), language::BufferId::new(1).unwrap(),
replica_id, replica_id,
@ -755,7 +755,7 @@ impl Context {
this this
} }
pub(crate) fn serialize(&self, cx: &AppContext) -> SavedContext { pub(crate) fn serialize(&self, cx: &App) -> SavedContext {
let buffer = self.buffer.read(cx); let buffer = self.buffer.read(cx);
SavedContext { SavedContext {
id: Some(self.id.clone()), id: Some(self.id.clone()),
@ -803,9 +803,9 @@ impl Context {
prompt_builder: Arc<PromptBuilder>, prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>, slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>, tools: Arc<ToolWorkingSet>,
project: Option<Model<Project>>, project: Option<Entity<Project>>,
telemetry: Option<Arc<Telemetry>>, telemetry: Option<Arc<Telemetry>>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Self { ) -> Self {
let id = saved_context.id.clone().unwrap_or_else(ContextId::new); let id = saved_context.id.clone().unwrap_or_else(ContextId::new);
let mut this = Self::new( let mut this = Self::new(
@ -837,7 +837,7 @@ impl Context {
self.timestamp.replica_id self.timestamp.replica_id
} }
pub fn version(&self, cx: &AppContext) -> ContextVersion { pub fn version(&self, cx: &App) -> ContextVersion {
ContextVersion { ContextVersion {
context: self.version.clone(), context: self.version.clone(),
buffer: self.buffer.read(cx).version(), buffer: self.buffer.read(cx).version(),
@ -852,11 +852,7 @@ impl Context {
&self.tools &self.tools
} }
pub fn set_capability( pub fn set_capability(&mut self, capability: language::Capability, cx: &mut Context<Self>) {
&mut self,
capability: language::Capability,
cx: &mut ModelContext<Self>,
) {
self.buffer self.buffer
.update(cx, |buffer, cx| buffer.set_capability(capability, cx)); .update(cx, |buffer, cx| buffer.set_capability(capability, cx));
} }
@ -870,7 +866,7 @@ impl Context {
pub fn serialize_ops( pub fn serialize_ops(
&self, &self,
since: &ContextVersion, since: &ContextVersion,
cx: &AppContext, cx: &App,
) -> Task<Vec<proto::ContextOperation>> { ) -> Task<Vec<proto::ContextOperation>> {
let buffer_ops = self let buffer_ops = self
.buffer .buffer
@ -905,7 +901,7 @@ impl Context {
pub fn apply_ops( pub fn apply_ops(
&mut self, &mut self,
ops: impl IntoIterator<Item = ContextOperation>, ops: impl IntoIterator<Item = ContextOperation>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
let mut buffer_ops = Vec::new(); let mut buffer_ops = Vec::new();
for op in ops { for op in ops {
@ -919,7 +915,7 @@ impl Context {
self.flush_ops(cx); self.flush_ops(cx);
} }
fn flush_ops(&mut self, cx: &mut ModelContext<Context>) { fn flush_ops(&mut self, cx: &mut Context<AssistantContext>) {
let mut changed_messages = HashSet::default(); let mut changed_messages = HashSet::default();
let mut summary_changed = false; let mut summary_changed = false;
@ -1038,7 +1034,7 @@ impl Context {
} }
} }
fn can_apply_op(&self, op: &ContextOperation, cx: &AppContext) -> bool { fn can_apply_op(&self, op: &ContextOperation, cx: &App) -> bool {
if !self.version.observed_all(op.version()) { if !self.version.observed_all(op.version()) {
return false; return false;
} }
@ -1069,7 +1065,7 @@ impl Context {
fn has_received_operations_for_anchor_range( fn has_received_operations_for_anchor_range(
&self, &self,
range: Range<text::Anchor>, range: Range<text::Anchor>,
cx: &AppContext, cx: &App,
) -> bool { ) -> bool {
let version = &self.buffer.read(cx).version; let version = &self.buffer.read(cx).version;
let observed_start = range.start == language::Anchor::MIN let observed_start = range.start == language::Anchor::MIN
@ -1081,12 +1077,12 @@ impl Context {
observed_start && observed_end observed_start && observed_end
} }
fn push_op(&mut self, op: ContextOperation, cx: &mut ModelContext<Self>) { fn push_op(&mut self, op: ContextOperation, cx: &mut Context<Self>) {
self.operations.push(op.clone()); self.operations.push(op.clone());
cx.emit(ContextEvent::Operation(op)); cx.emit(ContextEvent::Operation(op));
} }
pub fn buffer(&self) -> &Model<Buffer> { pub fn buffer(&self) -> &Entity<Buffer> {
&self.buffer &self.buffer
} }
@ -1094,7 +1090,7 @@ impl Context {
self.language_registry.clone() self.language_registry.clone()
} }
pub fn project(&self) -> Option<Model<Project>> { pub fn project(&self) -> Option<Entity<Project>> {
self.project.clone() self.project.clone()
} }
@ -1110,7 +1106,7 @@ impl Context {
self.summary.as_ref() self.summary.as_ref()
} }
pub fn patch_containing(&self, position: Point, cx: &AppContext) -> Option<&AssistantPatch> { pub fn patch_containing(&self, position: Point, cx: &App) -> Option<&AssistantPatch> {
let buffer = self.buffer.read(cx); let buffer = self.buffer.read(cx);
let index = self.patches.binary_search_by(|patch| { let index = self.patches.binary_search_by(|patch| {
let patch_range = patch.range.to_point(&buffer); let patch_range = patch.range.to_point(&buffer);
@ -1136,7 +1132,7 @@ impl Context {
pub fn patch_for_range( pub fn patch_for_range(
&self, &self,
range: &Range<language::Anchor>, range: &Range<language::Anchor>,
cx: &AppContext, cx: &App,
) -> Option<&AssistantPatch> { ) -> Option<&AssistantPatch> {
let buffer = self.buffer.read(cx); let buffer = self.buffer.read(cx);
let index = self.patch_index_for_range(range, buffer).ok()?; let index = self.patch_index_for_range(range, buffer).ok()?;
@ -1167,7 +1163,7 @@ impl Context {
&self.slash_command_output_sections &self.slash_command_output_sections
} }
pub fn contains_files(&self, cx: &AppContext) -> bool { pub fn contains_files(&self, cx: &App) -> bool {
let buffer = self.buffer.read(cx); let buffer = self.buffer.read(cx);
self.slash_command_output_sections.iter().any(|section| { self.slash_command_output_sections.iter().any(|section| {
section.is_valid(buffer) section.is_valid(buffer)
@ -1189,7 +1185,7 @@ impl Context {
self.pending_tool_uses_by_id.get(id) self.pending_tool_uses_by_id.get(id)
} }
fn set_language(&mut self, cx: &mut ModelContext<Self>) { fn set_language(&mut self, cx: &mut Context<Self>) {
let markdown = self.language_registry.language_for_name("Markdown"); let markdown = self.language_registry.language_for_name("Markdown");
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
let markdown = markdown.await?; let markdown = markdown.await?;
@ -1203,9 +1199,9 @@ impl Context {
fn handle_buffer_event( fn handle_buffer_event(
&mut self, &mut self,
_: Model<Buffer>, _: Entity<Buffer>,
event: &language::BufferEvent, event: &language::BufferEvent,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
match event { match event {
language::BufferEvent::Operation { language::BufferEvent::Operation {
@ -1227,7 +1223,7 @@ impl Context {
self.token_count self.token_count
} }
pub(crate) fn count_remaining_tokens(&mut self, cx: &mut ModelContext<Self>) { pub(crate) fn count_remaining_tokens(&mut self, cx: &mut Context<Self>) {
// Assume it will be a Chat request, even though that takes fewer tokens (and risks going over the limit), // Assume it will be a Chat request, even though that takes fewer tokens (and risks going over the limit),
// because otherwise you see in the UI that your empty message has a bunch of tokens already used. // because otherwise you see in the UI that your empty message has a bunch of tokens already used.
let request = self.to_completion_request(RequestType::Chat, cx); let request = self.to_completion_request(RequestType::Chat, cx);
@ -1255,7 +1251,7 @@ impl Context {
&mut self, &mut self,
cache_configuration: &Option<LanguageModelCacheConfiguration>, cache_configuration: &Option<LanguageModelCacheConfiguration>,
speculative: bool, speculative: bool,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> bool { ) -> bool {
let cache_configuration = let cache_configuration =
cache_configuration cache_configuration
@ -1357,7 +1353,7 @@ impl Context {
new_anchor_needs_caching new_anchor_needs_caching
} }
fn start_cache_warming(&mut self, model: &Arc<dyn LanguageModel>, cx: &mut ModelContext<Self>) { fn start_cache_warming(&mut self, model: &Arc<dyn LanguageModel>, cx: &mut Context<Self>) {
let cache_configuration = model.cache_configuration(); let cache_configuration = model.cache_configuration();
if !self.mark_cache_anchors(&cache_configuration, true, cx) { if !self.mark_cache_anchors(&cache_configuration, true, cx) {
@ -1407,7 +1403,7 @@ impl Context {
}); });
} }
pub fn update_cache_status_for_completion(&mut self, cx: &mut ModelContext<Self>) { pub fn update_cache_status_for_completion(&mut self, cx: &mut Context<Self>) {
let cached_message_ids: Vec<MessageId> = self let cached_message_ids: Vec<MessageId> = self
.messages_metadata .messages_metadata
.iter() .iter()
@ -1432,7 +1428,7 @@ impl Context {
cx.notify(); cx.notify();
} }
pub fn reparse(&mut self, cx: &mut ModelContext<Self>) { pub fn reparse(&mut self, cx: &mut Context<Self>) {
let buffer = self.buffer.read(cx).text_snapshot(); let buffer = self.buffer.read(cx).text_snapshot();
let mut row_ranges = self let mut row_ranges = self
.edits_since_last_parse .edits_since_last_parse
@ -1505,7 +1501,7 @@ impl Context {
buffer: &BufferSnapshot, buffer: &BufferSnapshot,
updated: &mut Vec<ParsedSlashCommand>, updated: &mut Vec<ParsedSlashCommand>,
removed: &mut Vec<Range<text::Anchor>>, removed: &mut Vec<Range<text::Anchor>>,
cx: &AppContext, cx: &App,
) { ) {
let old_range = self.pending_command_indices_for_range(range.clone(), cx); let old_range = self.pending_command_indices_for_range(range.clone(), cx);
@ -1559,7 +1555,7 @@ impl Context {
fn invalidate_pending_slash_commands( fn invalidate_pending_slash_commands(
&mut self, &mut self,
buffer: &BufferSnapshot, buffer: &BufferSnapshot,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
let mut invalidated_command_ids = Vec::new(); let mut invalidated_command_ids = Vec::new();
for (&command_id, command) in self.invoked_slash_commands.iter_mut() { for (&command_id, command) in self.invoked_slash_commands.iter_mut() {
@ -1593,7 +1589,7 @@ impl Context {
buffer: &BufferSnapshot, buffer: &BufferSnapshot,
updated: &mut Vec<Range<text::Anchor>>, updated: &mut Vec<Range<text::Anchor>>,
removed: &mut Vec<Range<text::Anchor>>, removed: &mut Vec<Range<text::Anchor>>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
// Rebuild the XML tags in the edited range. // Rebuild the XML tags in the edited range.
let intersecting_tags_range = let intersecting_tags_range =
@ -1636,7 +1632,7 @@ impl Context {
&self, &self,
buffer: &BufferSnapshot, buffer: &BufferSnapshot,
range: Range<text::Anchor>, range: Range<text::Anchor>,
cx: &AppContext, cx: &App,
) -> Vec<XmlTag> { ) -> Vec<XmlTag> {
let mut messages = self.messages(cx).peekable(); let mut messages = self.messages(cx).peekable();
@ -1693,7 +1689,7 @@ impl Context {
tags_start_ix: usize, tags_start_ix: usize,
buffer_end: text::Anchor, buffer_end: text::Anchor,
buffer: &BufferSnapshot, buffer: &BufferSnapshot,
cx: &AppContext, cx: &App,
) -> Vec<AssistantPatch> { ) -> Vec<AssistantPatch> {
let mut new_patches = Vec::new(); let mut new_patches = Vec::new();
let mut pending_patch = None; let mut pending_patch = None;
@ -1851,7 +1847,7 @@ impl Context {
pub fn pending_command_for_position( pub fn pending_command_for_position(
&mut self, &mut self,
position: language::Anchor, position: language::Anchor,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Option<&mut ParsedSlashCommand> { ) -> Option<&mut ParsedSlashCommand> {
let buffer = self.buffer.read(cx); let buffer = self.buffer.read(cx);
match self match self
@ -1875,7 +1871,7 @@ impl Context {
pub fn pending_commands_for_range( pub fn pending_commands_for_range(
&self, &self,
range: Range<language::Anchor>, range: Range<language::Anchor>,
cx: &AppContext, cx: &App,
) -> &[ParsedSlashCommand] { ) -> &[ParsedSlashCommand] {
let range = self.pending_command_indices_for_range(range, cx); let range = self.pending_command_indices_for_range(range, cx);
&self.parsed_slash_commands[range] &self.parsed_slash_commands[range]
@ -1884,7 +1880,7 @@ impl Context {
fn pending_command_indices_for_range( fn pending_command_indices_for_range(
&self, &self,
range: Range<language::Anchor>, range: Range<language::Anchor>,
cx: &AppContext, cx: &App,
) -> Range<usize> { ) -> Range<usize> {
self.indices_intersecting_buffer_range(&self.parsed_slash_commands, range, cx) self.indices_intersecting_buffer_range(&self.parsed_slash_commands, range, cx)
} }
@ -1893,7 +1889,7 @@ impl Context {
&self, &self,
all_annotations: &[T], all_annotations: &[T],
range: Range<language::Anchor>, range: Range<language::Anchor>,
cx: &AppContext, cx: &App,
) -> Range<usize> { ) -> Range<usize> {
let buffer = self.buffer.read(cx); let buffer = self.buffer.read(cx);
let start_ix = match all_annotations let start_ix = match all_annotations
@ -1916,7 +1912,7 @@ impl Context {
name: &str, name: &str,
output: Task<SlashCommandResult>, output: Task<SlashCommandResult>,
ensure_trailing_newline: bool, ensure_trailing_newline: bool,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
let version = self.version.clone(); let version = self.version.clone();
let command_id = InvokedSlashCommandId(self.next_timestamp()); let command_id = InvokedSlashCommandId(self.next_timestamp());
@ -2184,7 +2180,7 @@ impl Context {
fn insert_slash_command_output_section( fn insert_slash_command_output_section(
&mut self, &mut self,
section: SlashCommandOutputSection<language::Anchor>, section: SlashCommandOutputSection<language::Anchor>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
let buffer = self.buffer.read(cx); let buffer = self.buffer.read(cx);
let insertion_ix = match self let insertion_ix = match self
@ -2214,7 +2210,7 @@ impl Context {
&mut self, &mut self,
tool_use_id: LanguageModelToolUseId, tool_use_id: LanguageModelToolUseId,
output: Task<Result<String>>, output: Task<Result<String>>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
let insert_output_task = cx.spawn(|this, mut cx| { let insert_output_task = cx.spawn(|this, mut cx| {
let tool_use_id = tool_use_id.clone(); let tool_use_id = tool_use_id.clone();
@ -2272,11 +2268,11 @@ impl Context {
} }
} }
pub fn completion_provider_changed(&mut self, cx: &mut ModelContext<Self>) { pub fn completion_provider_changed(&mut self, cx: &mut Context<Self>) {
self.count_remaining_tokens(cx); self.count_remaining_tokens(cx);
} }
fn get_last_valid_message_id(&self, cx: &ModelContext<Self>) -> Option<MessageId> { fn get_last_valid_message_id(&self, cx: &Context<Self>) -> Option<MessageId> {
self.message_anchors.iter().rev().find_map(|message| { self.message_anchors.iter().rev().find_map(|message| {
message message
.start .start
@ -2288,7 +2284,7 @@ impl Context {
pub fn assist( pub fn assist(
&mut self, &mut self,
request_type: RequestType, request_type: RequestType,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Option<MessageAnchor> { ) -> Option<MessageAnchor> {
let model_registry = LanguageModelRegistry::read_global(cx); let model_registry = LanguageModelRegistry::read_global(cx);
let provider = model_registry.active_provider()?; let provider = model_registry.active_provider()?;
@ -2519,7 +2515,7 @@ impl Context {
pub fn to_completion_request( pub fn to_completion_request(
&self, &self,
request_type: RequestType, request_type: RequestType,
cx: &AppContext, cx: &App,
) -> LanguageModelRequest { ) -> LanguageModelRequest {
let buffer = self.buffer.read(cx); let buffer = self.buffer.read(cx);
@ -2631,7 +2627,7 @@ impl Context {
completion_request completion_request
} }
pub fn cancel_last_assist(&mut self, cx: &mut ModelContext<Self>) -> bool { pub fn cancel_last_assist(&mut self, cx: &mut Context<Self>) -> bool {
if let Some(pending_completion) = self.pending_completions.pop() { if let Some(pending_completion) = self.pending_completions.pop() {
self.update_metadata(pending_completion.assistant_message_id, cx, |metadata| { self.update_metadata(pending_completion.assistant_message_id, cx, |metadata| {
if metadata.status == MessageStatus::Pending { if metadata.status == MessageStatus::Pending {
@ -2644,7 +2640,7 @@ impl Context {
} }
} }
pub fn cycle_message_roles(&mut self, ids: HashSet<MessageId>, cx: &mut ModelContext<Self>) { pub fn cycle_message_roles(&mut self, ids: HashSet<MessageId>, cx: &mut Context<Self>) {
for id in &ids { for id in &ids {
if let Some(metadata) = self.messages_metadata.get(id) { if let Some(metadata) = self.messages_metadata.get(id) {
let role = metadata.role.cycle(); let role = metadata.role.cycle();
@ -2655,7 +2651,7 @@ impl Context {
self.message_roles_updated(ids, cx); self.message_roles_updated(ids, cx);
} }
fn message_roles_updated(&mut self, ids: HashSet<MessageId>, cx: &mut ModelContext<Self>) { fn message_roles_updated(&mut self, ids: HashSet<MessageId>, cx: &mut Context<Self>) {
let mut ranges = Vec::new(); let mut ranges = Vec::new();
for message in self.messages(cx) { for message in self.messages(cx) {
if ids.contains(&message.id) { if ids.contains(&message.id) {
@ -2678,7 +2674,7 @@ impl Context {
pub fn update_metadata( pub fn update_metadata(
&mut self, &mut self,
id: MessageId, id: MessageId,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
f: impl FnOnce(&mut MessageMetadata), f: impl FnOnce(&mut MessageMetadata),
) { ) {
let version = self.version.clone(); let version = self.version.clone();
@ -2702,7 +2698,7 @@ impl Context {
message_id: MessageId, message_id: MessageId,
role: Role, role: Role,
status: MessageStatus, status: MessageStatus,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Option<MessageAnchor> { ) -> Option<MessageAnchor> {
if let Some(prev_message_ix) = self if let Some(prev_message_ix) = self
.message_anchors .message_anchors
@ -2736,7 +2732,7 @@ impl Context {
offset: usize, offset: usize,
role: Role, role: Role,
status: MessageStatus, status: MessageStatus,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> MessageAnchor { ) -> MessageAnchor {
let start = self.buffer.update(cx, |buffer, cx| { let start = self.buffer.update(cx, |buffer, cx| {
buffer.edit([(offset..offset, "\n")], None, cx); buffer.edit([(offset..offset, "\n")], None, cx);
@ -2766,7 +2762,7 @@ impl Context {
anchor anchor
} }
pub fn insert_content(&mut self, content: Content, cx: &mut ModelContext<Self>) { pub fn insert_content(&mut self, content: Content, cx: &mut Context<Self>) {
let buffer = self.buffer.read(cx); let buffer = self.buffer.read(cx);
let insertion_ix = match self let insertion_ix = match self
.contents .contents
@ -2782,7 +2778,7 @@ impl Context {
cx.emit(ContextEvent::MessagesEdited); cx.emit(ContextEvent::MessagesEdited);
} }
pub fn contents<'a>(&'a self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Content> { pub fn contents<'a>(&'a self, cx: &'a App) -> impl 'a + Iterator<Item = Content> {
let buffer = self.buffer.read(cx); let buffer = self.buffer.read(cx);
self.contents self.contents
.iter() .iter()
@ -2796,7 +2792,7 @@ impl Context {
pub fn split_message( pub fn split_message(
&mut self, &mut self,
range: Range<usize>, range: Range<usize>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> (Option<MessageAnchor>, Option<MessageAnchor>) { ) -> (Option<MessageAnchor>, Option<MessageAnchor>) {
let start_message = self.message_for_offset(range.start, cx); let start_message = self.message_for_offset(range.start, cx);
let end_message = self.message_for_offset(range.end, cx); let end_message = self.message_for_offset(range.end, cx);
@ -2922,7 +2918,7 @@ impl Context {
&mut self, &mut self,
new_anchor: MessageAnchor, new_anchor: MessageAnchor,
new_metadata: MessageMetadata, new_metadata: MessageMetadata,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
cx.emit(ContextEvent::MessagesEdited); cx.emit(ContextEvent::MessagesEdited);
@ -2940,7 +2936,7 @@ impl Context {
self.message_anchors.insert(insertion_ix, new_anchor); self.message_anchors.insert(insertion_ix, new_anchor);
} }
pub fn summarize(&mut self, replace_old: bool, cx: &mut ModelContext<Self>) { pub fn summarize(&mut self, replace_old: bool, cx: &mut Context<Self>) {
let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else { let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
return; return;
}; };
@ -3018,14 +3014,14 @@ impl Context {
} }
} }
fn message_for_offset(&self, offset: usize, cx: &AppContext) -> Option<Message> { fn message_for_offset(&self, offset: usize, cx: &App) -> Option<Message> {
self.messages_for_offsets([offset], cx).pop() self.messages_for_offsets([offset], cx).pop()
} }
pub fn messages_for_offsets( pub fn messages_for_offsets(
&self, &self,
offsets: impl IntoIterator<Item = usize>, offsets: impl IntoIterator<Item = usize>,
cx: &AppContext, cx: &App,
) -> Vec<Message> { ) -> Vec<Message> {
let mut result = Vec::new(); let mut result = Vec::new();
@ -3058,14 +3054,14 @@ impl Context {
fn messages_from_anchors<'a>( fn messages_from_anchors<'a>(
&'a self, &'a self,
message_anchors: impl Iterator<Item = &'a MessageAnchor> + 'a, message_anchors: impl Iterator<Item = &'a MessageAnchor> + 'a,
cx: &'a AppContext, cx: &'a App,
) -> impl 'a + Iterator<Item = Message> { ) -> impl 'a + Iterator<Item = Message> {
let buffer = self.buffer.read(cx); let buffer = self.buffer.read(cx);
Self::messages_from_iters(buffer, &self.messages_metadata, message_anchors.enumerate()) Self::messages_from_iters(buffer, &self.messages_metadata, message_anchors.enumerate())
} }
pub fn messages<'a>(&'a self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Message> { pub fn messages<'a>(&'a self, cx: &'a App) -> impl 'a + Iterator<Item = Message> {
self.messages_from_anchors(self.message_anchors.iter(), cx) self.messages_from_anchors(self.message_anchors.iter(), cx)
} }
@ -3113,7 +3109,7 @@ impl Context {
&mut self, &mut self,
debounce: Option<Duration>, debounce: Option<Duration>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
cx: &mut ModelContext<Context>, cx: &mut Context<AssistantContext>,
) { ) {
if self.replica_id() != ReplicaId::default() { if self.replica_id() != ReplicaId::default() {
// Prevent saving a remote context for now. // Prevent saving a remote context for now.
@ -3179,7 +3175,7 @@ impl Context {
}); });
} }
pub fn custom_summary(&mut self, custom_summary: String, cx: &mut ModelContext<Self>) { pub fn custom_summary(&mut self, custom_summary: String, cx: &mut Context<Self>) {
let timestamp = self.next_timestamp(); let timestamp = self.next_timestamp();
let summary = self.summary.get_or_insert(ContextSummary::default()); let summary = self.summary.get_or_insert(ContextSummary::default());
summary.timestamp = timestamp; summary.timestamp = timestamp;
@ -3339,8 +3335,8 @@ impl SavedContext {
fn into_ops( fn into_ops(
self, self,
buffer: &Model<Buffer>, buffer: &Entity<Buffer>,
cx: &mut ModelContext<Context>, cx: &mut Context<AssistantContext>,
) -> Vec<ContextOperation> { ) -> Vec<ContextOperation> {
let mut operations = Vec::new(); let mut operations = Vec::new();
let mut version = clock::Global::new(); let mut version = clock::Global::new();

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
AssistantEdit, AssistantEditKind, CacheStatus, Context, ContextEvent, ContextId, AssistantContext, AssistantEdit, AssistantEditKind, CacheStatus, ContextEvent, ContextId,
ContextOperation, InvokedSlashCommandId, MessageCacheMetadata, MessageId, MessageStatus, ContextOperation, InvokedSlashCommandId, MessageCacheMetadata, MessageId, MessageStatus,
}; };
use anyhow::Result; use anyhow::Result;
@ -15,7 +15,7 @@ use futures::{
channel::mpsc, channel::mpsc,
stream::{self, StreamExt}, stream::{self, StreamExt},
}; };
use gpui::{prelude::*, AppContext, Model, SharedString, Task, TestAppContext, WeakView}; use gpui::{prelude::*, App, Entity, SharedString, Task, TestAppContext, WeakEntity};
use language::{Buffer, BufferSnapshot, LanguageRegistry, LspAdapterDelegate}; use language::{Buffer, BufferSnapshot, LanguageRegistry, LspAdapterDelegate};
use language_model::{LanguageModelCacheConfiguration, LanguageModelRegistry, Role}; use language_model::{LanguageModelCacheConfiguration, LanguageModelRegistry, Role};
use parking_lot::Mutex; use parking_lot::Mutex;
@ -34,7 +34,7 @@ use std::{
sync::{atomic::AtomicBool, Arc}, sync::{atomic::AtomicBool, Arc},
}; };
use text::{network::Network, OffsetRangeExt as _, ReplicaId, ToOffset}; use text::{network::Network, OffsetRangeExt as _, ReplicaId, ToOffset};
use ui::{IconName, WindowContext}; use ui::{IconName, Window};
use unindent::Unindent; use unindent::Unindent;
use util::{ use util::{
test::{generate_marked_text, marked_text_ranges}, test::{generate_marked_text, marked_text_ranges},
@ -43,14 +43,14 @@ use util::{
use workspace::Workspace; use workspace::Workspace;
#[gpui::test] #[gpui::test]
fn test_inserting_and_removing_messages(cx: &mut AppContext) { fn test_inserting_and_removing_messages(cx: &mut App) {
let settings_store = SettingsStore::test(cx); let settings_store = SettingsStore::test(cx);
LanguageModelRegistry::test(cx); LanguageModelRegistry::test(cx);
cx.set_global(settings_store); cx.set_global(settings_store);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone())); let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new_model(|cx| { let context = cx.new(|cx| {
Context::local( AssistantContext::local(
registry, registry,
None, None,
None, None,
@ -183,15 +183,15 @@ fn test_inserting_and_removing_messages(cx: &mut AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_message_splitting(cx: &mut AppContext) { fn test_message_splitting(cx: &mut App) {
let settings_store = SettingsStore::test(cx); let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store); cx.set_global(settings_store);
LanguageModelRegistry::test(cx); LanguageModelRegistry::test(cx);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone())); let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new_model(|cx| { let context = cx.new(|cx| {
Context::local( AssistantContext::local(
registry.clone(), registry.clone(),
None, None,
None, None,
@ -287,14 +287,14 @@ fn test_message_splitting(cx: &mut AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_messages_for_offsets(cx: &mut AppContext) { fn test_messages_for_offsets(cx: &mut App) {
let settings_store = SettingsStore::test(cx); let settings_store = SettingsStore::test(cx);
LanguageModelRegistry::test(cx); LanguageModelRegistry::test(cx);
cx.set_global(settings_store); cx.set_global(settings_store);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone())); let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new_model(|cx| { let context = cx.new(|cx| {
Context::local( AssistantContext::local(
registry, registry,
None, None,
None, None,
@ -367,9 +367,9 @@ fn test_messages_for_offsets(cx: &mut AppContext) {
); );
fn message_ids_for_offsets( fn message_ids_for_offsets(
context: &Model<Context>, context: &Entity<AssistantContext>,
offsets: &[usize], offsets: &[usize],
cx: &AppContext, cx: &App,
) -> Vec<MessageId> { ) -> Vec<MessageId> {
context context
.read(cx) .read(cx)
@ -407,8 +407,8 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
let registry = Arc::new(LanguageRegistry::test(cx.executor())); let registry = Arc::new(LanguageRegistry::test(cx.executor()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new_model(|cx| { let context = cx.new(|cx| {
Context::local( AssistantContext::local(
registry.clone(), registry.clone(),
None, None,
None, None,
@ -608,7 +608,7 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
#[track_caller] #[track_caller]
fn assert_text_and_context_ranges( fn assert_text_and_context_ranges(
buffer: &Model<Buffer>, buffer: &Entity<Buffer>,
ranges: &RefCell<ContextRanges>, ranges: &RefCell<ContextRanges>,
expected_marked_text: &str, expected_marked_text: &str,
cx: &mut TestAppContext, cx: &mut TestAppContext,
@ -697,8 +697,8 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
// Create a new context // Create a new context
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new_model(|cx| { let context = cx.new(|cx| {
Context::local( AssistantContext::local(
registry.clone(), registry.clone(),
Some(project), Some(project),
None, None,
@ -962,8 +962,8 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
// Ensure steps are re-parsed when deserializing. // Ensure steps are re-parsed when deserializing.
let serialized_context = context.read_with(cx, |context, cx| context.serialize(cx)); let serialized_context = context.read_with(cx, |context, cx| context.serialize(cx));
let deserialized_context = cx.new_model(|cx| { let deserialized_context = cx.new(|cx| {
Context::deserialize( AssistantContext::deserialize(
serialized_context, serialized_context,
Default::default(), Default::default(),
registry.clone(), registry.clone(),
@ -1006,7 +1006,11 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
cx, cx,
); );
fn edit(context: &Model<Context>, new_text_marked_with_edits: &str, cx: &mut TestAppContext) { fn edit(
context: &Entity<AssistantContext>,
new_text_marked_with_edits: &str,
cx: &mut TestAppContext,
) {
context.update(cx, |context, cx| { context.update(cx, |context, cx| {
context.buffer.update(cx, |buffer, cx| { context.buffer.update(cx, |buffer, cx| {
buffer.edit_via_marked_text(&new_text_marked_with_edits.unindent(), None, cx); buffer.edit_via_marked_text(&new_text_marked_with_edits.unindent(), None, cx);
@ -1017,7 +1021,7 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
#[track_caller] #[track_caller]
fn expect_patches( fn expect_patches(
context: &Model<Context>, context: &Entity<AssistantContext>,
expected_marked_text: &str, expected_marked_text: &str,
expected_suggestions: &[&[AssistantEdit]], expected_suggestions: &[&[AssistantEdit]],
cx: &mut TestAppContext, cx: &mut TestAppContext,
@ -1077,8 +1081,8 @@ async fn test_serialization(cx: &mut TestAppContext) {
cx.update(LanguageModelRegistry::test); cx.update(LanguageModelRegistry::test);
let registry = Arc::new(LanguageRegistry::test(cx.executor())); let registry = Arc::new(LanguageRegistry::test(cx.executor()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new_model(|cx| { let context = cx.new(|cx| {
Context::local( AssistantContext::local(
registry.clone(), registry.clone(),
None, None,
None, None,
@ -1121,8 +1125,8 @@ async fn test_serialization(cx: &mut TestAppContext) {
); );
let serialized_context = context.read_with(cx, |context, cx| context.serialize(cx)); let serialized_context = context.read_with(cx, |context, cx| context.serialize(cx));
let deserialized_context = cx.new_model(|cx| { let deserialized_context = cx.new(|cx| {
Context::deserialize( AssistantContext::deserialize(
serialized_context, serialized_context,
Default::default(), Default::default(),
registry.clone(), registry.clone(),
@ -1179,8 +1183,8 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
let context_id = ContextId::new(); let context_id = ContextId::new();
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
for i in 0..num_peers { for i in 0..num_peers {
let context = cx.new_model(|cx| { let context = cx.new(|cx| {
Context::new( AssistantContext::new(
context_id.clone(), context_id.clone(),
i as ReplicaId, i as ReplicaId,
language::Capability::ReadWrite, language::Capability::ReadWrite,
@ -1434,14 +1438,14 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
} }
#[gpui::test] #[gpui::test]
fn test_mark_cache_anchors(cx: &mut AppContext) { fn test_mark_cache_anchors(cx: &mut App) {
let settings_store = SettingsStore::test(cx); let settings_store = SettingsStore::test(cx);
LanguageModelRegistry::test(cx); LanguageModelRegistry::test(cx);
cx.set_global(settings_store); cx.set_global(settings_store);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone())); let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new_model(|cx| { let context = cx.new(|cx| {
Context::local( AssistantContext::local(
registry, registry,
None, None,
None, None,
@ -1594,7 +1598,7 @@ fn test_mark_cache_anchors(cx: &mut AppContext) {
); );
} }
fn messages(context: &Model<Context>, cx: &AppContext) -> Vec<(MessageId, Role, Range<usize>)> { fn messages(context: &Entity<AssistantContext>, cx: &App) -> Vec<(MessageId, Role, Range<usize>)> {
context context
.read(cx) .read(cx)
.messages(cx) .messages(cx)
@ -1603,8 +1607,8 @@ fn messages(context: &Model<Context>, cx: &AppContext) -> Vec<(MessageId, Role,
} }
fn messages_cache( fn messages_cache(
context: &Model<Context>, context: &Entity<AssistantContext>,
cx: &AppContext, cx: &App,
) -> Vec<(MessageId, Option<MessageCacheMetadata>)> { ) -> Vec<(MessageId, Option<MessageCacheMetadata>)> {
context context
.read(cx) .read(cx)
@ -1633,8 +1637,9 @@ impl SlashCommand for FakeSlashCommand {
self: Arc<Self>, self: Arc<Self>,
_arguments: &[String], _arguments: &[String],
_cancel: Arc<AtomicBool>, _cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>, _workspace: Option<WeakEntity<Workspace>>,
_cx: &mut WindowContext, _window: &mut Window,
_cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> { ) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(vec![])) Task::ready(Ok(vec![]))
} }
@ -1648,9 +1653,10 @@ impl SlashCommand for FakeSlashCommand {
_arguments: &[String], _arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>], _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot, _context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>, _workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>, _delegate: Option<Arc<dyn LspAdapterDelegate>>,
_cx: &mut WindowContext, _window: &mut Window,
_cx: &mut App,
) -> Task<SlashCommandResult> { ) -> Task<SlashCommandResult> {
Task::ready(Ok(SlashCommandOutput { Task::ready(Ok(SlashCommandOutput {
text: format!("Executed fake command: {}", self.0), text: format!("Executed fake command: {}", self.0),

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use gpui::{ use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity};
AppContext, EventEmitter, FocusHandle, FocusableView, Model, Subscription, Task, View, WeakView,
};
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use project::Project; use project::Project;
use ui::utils::{format_distance_from_now, DateTimeType}; use ui::utils::{format_distance_from_now, DateTimeType};
@ -25,21 +23,23 @@ enum SavedContextPickerEvent {
} }
pub struct ContextHistory { pub struct ContextHistory {
picker: View<Picker<SavedContextPickerDelegate>>, picker: Entity<Picker<SavedContextPickerDelegate>>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
} }
impl ContextHistory { impl ContextHistory {
pub fn new( pub fn new(
project: Model<Project>, project: Entity<Project>,
context_store: Model<ContextStore>, context_store: Entity<ContextStore>,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) -> Self { ) -> Self {
let picker = cx.new_view(|cx| { let picker = cx.new(|cx| {
Picker::uniform_list( Picker::uniform_list(
SavedContextPickerDelegate::new(project, context_store.clone()), SavedContextPickerDelegate::new(project, context_store.clone()),
window,
cx, cx,
) )
.modal(false) .modal(false)
@ -47,10 +47,11 @@ impl ContextHistory {
}); });
let subscriptions = vec![ let subscriptions = vec![
cx.observe(&context_store, |this, _, cx| { cx.observe_in(&context_store, window, |this, _, window, cx| {
this.picker.update(cx, |picker, cx| picker.refresh(cx)); this.picker
.update(cx, |picker, cx| picker.refresh(window, cx));
}), }),
cx.subscribe(&picker, Self::handle_picker_event), cx.subscribe_in(&picker, window, Self::handle_picker_event),
]; ];
Self { Self {
@ -62,9 +63,10 @@ impl ContextHistory {
fn handle_picker_event( fn handle_picker_event(
&mut self, &mut self,
_: View<Picker<SavedContextPickerDelegate>>, _: &Entity<Picker<SavedContextPickerDelegate>>,
event: &SavedContextPickerEvent, event: &SavedContextPickerEvent,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) { ) {
let SavedContextPickerEvent::Confirmed(context) = event; let SavedContextPickerEvent::Confirmed(context) = event;
@ -76,12 +78,12 @@ impl ContextHistory {
.update(cx, |workspace, cx| match context { .update(cx, |workspace, cx| match context {
ContextMetadata::Remote(metadata) => { ContextMetadata::Remote(metadata) => {
assistant_panel_delegate assistant_panel_delegate
.open_remote_context(workspace, metadata.id.clone(), cx) .open_remote_context(workspace, metadata.id.clone(), window, cx)
.detach_and_log_err(cx); .detach_and_log_err(cx);
} }
ContextMetadata::Saved(metadata) => { ContextMetadata::Saved(metadata) => {
assistant_panel_delegate assistant_panel_delegate
.open_saved_context(workspace, metadata.path.clone(), cx) .open_saved_context(workspace, metadata.path.clone(), window, cx)
.detach_and_log_err(cx); .detach_and_log_err(cx);
} }
}) })
@ -90,13 +92,13 @@ impl ContextHistory {
} }
impl Render for ContextHistory { impl Render for ContextHistory {
fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div().size_full().child(self.picker.clone()) div().size_full().child(self.picker.clone())
} }
} }
impl FocusableView for ContextHistory { impl Focusable for ContextHistory {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle { fn focus_handle(&self, cx: &App) -> FocusHandle {
self.picker.focus_handle(cx) self.picker.focus_handle(cx)
} }
} }
@ -106,14 +108,14 @@ impl EventEmitter<()> for ContextHistory {}
impl Item for ContextHistory { impl Item for ContextHistory {
type Event = (); type Event = ();
fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> { fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
Some("History".into()) Some("History".into())
} }
} }
struct SavedContextPickerDelegate { struct SavedContextPickerDelegate {
store: Model<ContextStore>, store: Entity<ContextStore>,
project: Model<Project>, project: Entity<Project>,
matches: Vec<ContextMetadata>, matches: Vec<ContextMetadata>,
selected_index: usize, selected_index: usize,
} }
@ -121,7 +123,7 @@ struct SavedContextPickerDelegate {
impl EventEmitter<SavedContextPickerEvent> for Picker<SavedContextPickerDelegate> {} impl EventEmitter<SavedContextPickerEvent> for Picker<SavedContextPickerDelegate> {}
impl SavedContextPickerDelegate { impl SavedContextPickerDelegate {
fn new(project: Model<Project>, store: Model<ContextStore>) -> Self { fn new(project: Entity<Project>, store: Entity<ContextStore>) -> Self {
Self { Self {
project, project,
store, store,
@ -142,15 +144,25 @@ impl PickerDelegate for SavedContextPickerDelegate {
self.selected_index 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; 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...".into() "Search...".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 search = self.store.read(cx).search(query, cx); let search = self.store.read(cx).search(query, cx);
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
let matches = search.await; let matches = search.await;
@ -169,19 +181,20 @@ impl PickerDelegate for SavedContextPickerDelegate {
}) })
} }
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>>) {
if let Some(metadata) = self.matches.get(self.selected_index) { if let Some(metadata) = self.matches.get(self.selected_index) {
cx.emit(SavedContextPickerEvent::Confirmed(metadata.clone())); cx.emit(SavedContextPickerEvent::Confirmed(metadata.clone()));
} }
} }
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {} fn dismissed(&mut self, _window: &mut Window, _cx: &mut Context<Picker<Self>>) {}
fn render_match( fn render_match(
&self, &self,
ix: usize, ix: usize,
selected: bool, selected: bool,
cx: &mut ViewContext<Picker<Self>>, _window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> { ) -> Option<Self::ListItem> {
let context = self.matches.get(ix)?; let context = self.matches.get(ix)?;
let item = match context { let item = match context {

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
Context, ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext, AssistantContext, ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext,
SavedContextMetadata, SavedContextMetadata,
}; };
use anyhow::{anyhow, Context as _, Result}; use anyhow::{anyhow, Context as _, Result};
@ -14,7 +14,7 @@ use fs::Fs;
use futures::StreamExt; use futures::StreamExt;
use fuzzy::StringMatchCandidate; use fuzzy::StringMatchCandidate;
use gpui::{ use gpui::{
AppContext, AsyncAppContext, Context as _, EventEmitter, Model, ModelContext, Task, WeakModel, App, AppContext as _, AsyncAppContext, Context, Entity, EventEmitter, Task, WeakEntity,
}; };
use language::LanguageRegistry; use language::LanguageRegistry;
use paths::contexts_dir; use paths::contexts_dir;
@ -50,7 +50,7 @@ pub struct RemoteContextMetadata {
pub struct ContextStore { pub struct ContextStore {
contexts: Vec<ContextHandle>, contexts: Vec<ContextHandle>,
contexts_metadata: Vec<SavedContextMetadata>, contexts_metadata: Vec<SavedContextMetadata>,
context_server_manager: Model<ContextServerManager>, context_server_manager: Entity<ContextServerManager>,
context_server_slash_command_ids: HashMap<Arc<str>, Vec<SlashCommandId>>, context_server_slash_command_ids: HashMap<Arc<str>, Vec<SlashCommandId>>,
context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>, context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>,
host_contexts: Vec<RemoteContextMetadata>, host_contexts: Vec<RemoteContextMetadata>,
@ -61,7 +61,7 @@ pub struct ContextStore {
telemetry: Arc<Telemetry>, telemetry: Arc<Telemetry>,
_watch_updates: Task<Option<()>>, _watch_updates: Task<Option<()>>,
client: Arc<Client>, client: Arc<Client>,
project: Model<Project>, project: Entity<Project>,
project_is_shared: bool, project_is_shared: bool,
client_subscription: Option<client::Subscription>, client_subscription: Option<client::Subscription>,
_project_subscriptions: Vec<gpui::Subscription>, _project_subscriptions: Vec<gpui::Subscription>,
@ -75,19 +75,19 @@ pub enum ContextStoreEvent {
impl EventEmitter<ContextStoreEvent> for ContextStore {} impl EventEmitter<ContextStoreEvent> for ContextStore {}
enum ContextHandle { enum ContextHandle {
Weak(WeakModel<Context>), Weak(WeakEntity<AssistantContext>),
Strong(Model<Context>), Strong(Entity<AssistantContext>),
} }
impl ContextHandle { impl ContextHandle {
fn upgrade(&self) -> Option<Model<Context>> { fn upgrade(&self) -> Option<Entity<AssistantContext>> {
match self { match self {
ContextHandle::Weak(weak) => weak.upgrade(), ContextHandle::Weak(weak) => weak.upgrade(),
ContextHandle::Strong(strong) => Some(strong.clone()), ContextHandle::Strong(strong) => Some(strong.clone()),
} }
} }
fn downgrade(&self) -> WeakModel<Context> { fn downgrade(&self) -> WeakEntity<AssistantContext> {
match self { match self {
ContextHandle::Weak(weak) => weak.clone(), ContextHandle::Weak(weak) => weak.clone(),
ContextHandle::Strong(strong) => strong.downgrade(), ContextHandle::Strong(strong) => strong.downgrade(),
@ -97,12 +97,12 @@ impl ContextHandle {
impl ContextStore { impl ContextStore {
pub fn new( pub fn new(
project: Model<Project>, project: Entity<Project>,
prompt_builder: Arc<PromptBuilder>, prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>, slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>, tools: Arc<ToolWorkingSet>,
cx: &mut AppContext, cx: &mut App,
) -> Task<Result<Model<Self>>> { ) -> Task<Result<Entity<Self>>> {
let fs = project.read(cx).fs().clone(); let fs = project.read(cx).fs().clone();
let languages = project.read(cx).languages().clone(); let languages = project.read(cx).languages().clone();
let telemetry = project.read(cx).client().telemetry().clone(); let telemetry = project.read(cx).client().telemetry().clone();
@ -110,10 +110,10 @@ impl ContextStore {
const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100); const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100);
let (mut events, _) = fs.watch(contexts_dir(), CONTEXT_WATCH_DURATION).await; let (mut events, _) = fs.watch(contexts_dir(), CONTEXT_WATCH_DURATION).await;
let this = cx.new_model(|cx: &mut ModelContext<Self>| { let this = cx.new(|cx: &mut Context<Self>| {
let context_server_factory_registry = let context_server_factory_registry =
ContextServerFactoryRegistry::default_global(cx); 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) ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
}); });
let mut this = Self { let mut this = Self {
@ -163,7 +163,7 @@ impl ContextStore {
} }
async fn handle_advertise_contexts( async fn handle_advertise_contexts(
this: Model<Self>, this: Entity<Self>,
envelope: TypedEnvelope<proto::AdvertiseContexts>, envelope: TypedEnvelope<proto::AdvertiseContexts>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
@ -182,7 +182,7 @@ impl ContextStore {
} }
async fn handle_open_context( async fn handle_open_context(
this: Model<Self>, this: Entity<Self>,
envelope: TypedEnvelope<proto::OpenContext>, envelope: TypedEnvelope<proto::OpenContext>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<proto::OpenContextResponse> { ) -> Result<proto::OpenContextResponse> {
@ -212,7 +212,7 @@ impl ContextStore {
} }
async fn handle_create_context( async fn handle_create_context(
this: Model<Self>, this: Entity<Self>,
_: TypedEnvelope<proto::CreateContext>, _: TypedEnvelope<proto::CreateContext>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<proto::CreateContextResponse> { ) -> Result<proto::CreateContextResponse> {
@ -240,7 +240,7 @@ impl ContextStore {
} }
async fn handle_update_context( async fn handle_update_context(
this: Model<Self>, this: Entity<Self>,
envelope: TypedEnvelope<proto::UpdateContext>, envelope: TypedEnvelope<proto::UpdateContext>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
@ -256,7 +256,7 @@ impl ContextStore {
} }
async fn handle_synchronize_contexts( async fn handle_synchronize_contexts(
this: Model<Self>, this: Entity<Self>,
envelope: TypedEnvelope<proto::SynchronizeContexts>, envelope: TypedEnvelope<proto::SynchronizeContexts>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<proto::SynchronizeContextsResponse> { ) -> Result<proto::SynchronizeContextsResponse> {
@ -299,7 +299,7 @@ impl ContextStore {
})? })?
} }
fn handle_project_changed(&mut self, _: Model<Project>, cx: &mut ModelContext<Self>) { fn handle_project_changed(&mut self, _: Entity<Project>, cx: &mut Context<Self>) {
let is_shared = self.project.read(cx).is_shared(); let is_shared = self.project.read(cx).is_shared();
let was_shared = mem::replace(&mut self.project_is_shared, is_shared); let was_shared = mem::replace(&mut self.project_is_shared, is_shared);
if is_shared == was_shared { if is_shared == was_shared {
@ -320,7 +320,7 @@ impl ContextStore {
.client .client
.subscribe_to_entity(remote_id) .subscribe_to_entity(remote_id)
.log_err() .log_err()
.map(|subscription| subscription.set_model(&cx.handle(), &mut cx.to_async())); .map(|subscription| subscription.set_model(&cx.model(), &mut cx.to_async()));
self.advertise_contexts(cx); self.advertise_contexts(cx);
} else { } else {
self.client_subscription = None; self.client_subscription = None;
@ -329,9 +329,9 @@ impl ContextStore {
fn handle_project_event( fn handle_project_event(
&mut self, &mut self,
_: Model<Project>, _: Entity<Project>,
event: &project::Event, event: &project::Event,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
match event { match event {
project::Event::Reshared => { project::Event::Reshared => {
@ -361,9 +361,9 @@ impl ContextStore {
} }
} }
pub fn create(&mut self, cx: &mut ModelContext<Self>) -> Model<Context> { pub fn create(&mut self, cx: &mut Context<Self>) -> Entity<AssistantContext> {
let context = cx.new_model(|cx| { let context = cx.new(|cx| {
Context::local( AssistantContext::local(
self.languages.clone(), self.languages.clone(),
Some(self.project.clone()), Some(self.project.clone()),
Some(self.telemetry.clone()), Some(self.telemetry.clone()),
@ -379,8 +379,8 @@ impl ContextStore {
pub fn create_remote_context( pub fn create_remote_context(
&mut self, &mut self,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<Model<Context>>> { ) -> Task<Result<Entity<AssistantContext>>> {
let project = self.project.read(cx); let project = self.project.read(cx);
let Some(project_id) = project.remote_id() else { let Some(project_id) = project.remote_id() else {
return Task::ready(Err(anyhow!("project was not remote"))); return Task::ready(Err(anyhow!("project was not remote")));
@ -399,8 +399,8 @@ impl ContextStore {
let response = request.await?; let response = request.await?;
let context_id = ContextId::from_proto(response.context_id); let context_id = ContextId::from_proto(response.context_id);
let context_proto = response.context.context("invalid context")?; let context_proto = response.context.context("invalid context")?;
let context = cx.new_model(|cx| { let context = cx.new(|cx| {
Context::new( AssistantContext::new(
context_id.clone(), context_id.clone(),
replica_id, replica_id,
capability, capability,
@ -439,8 +439,8 @@ impl ContextStore {
pub fn open_local_context( pub fn open_local_context(
&mut self, &mut self,
path: PathBuf, path: PathBuf,
cx: &ModelContext<Self>, cx: &Context<Self>,
) -> Task<Result<Model<Context>>> { ) -> Task<Result<Entity<AssistantContext>>> {
if let Some(existing_context) = self.loaded_context_for_path(&path, cx) { if let Some(existing_context) = self.loaded_context_for_path(&path, cx) {
return Task::ready(Ok(existing_context)); return Task::ready(Ok(existing_context));
} }
@ -462,8 +462,8 @@ impl ContextStore {
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
let saved_context = load.await?; let saved_context = load.await?;
let context = cx.new_model(|cx| { let context = cx.new(|cx| {
Context::deserialize( AssistantContext::deserialize(
saved_context, saved_context,
path.clone(), path.clone(),
languages, languages,
@ -486,7 +486,7 @@ impl ContextStore {
}) })
} }
fn loaded_context_for_path(&self, path: &Path, cx: &AppContext) -> Option<Model<Context>> { fn loaded_context_for_path(&self, path: &Path, cx: &App) -> Option<Entity<AssistantContext>> {
self.contexts.iter().find_map(|context| { self.contexts.iter().find_map(|context| {
let context = context.upgrade()?; let context = context.upgrade()?;
if context.read(cx).path() == Some(path) { if context.read(cx).path() == Some(path) {
@ -497,7 +497,11 @@ impl ContextStore {
}) })
} }
pub fn loaded_context_for_id(&self, id: &ContextId, cx: &AppContext) -> Option<Model<Context>> { pub fn loaded_context_for_id(
&self,
id: &ContextId,
cx: &App,
) -> Option<Entity<AssistantContext>> {
self.contexts.iter().find_map(|context| { self.contexts.iter().find_map(|context| {
let context = context.upgrade()?; let context = context.upgrade()?;
if context.read(cx).id() == id { if context.read(cx).id() == id {
@ -511,8 +515,8 @@ impl ContextStore {
pub fn open_remote_context( pub fn open_remote_context(
&mut self, &mut self,
context_id: ContextId, context_id: ContextId,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<Model<Context>>> { ) -> Task<Result<Entity<AssistantContext>>> {
let project = self.project.read(cx); let project = self.project.read(cx);
let Some(project_id) = project.remote_id() else { let Some(project_id) = project.remote_id() else {
return Task::ready(Err(anyhow!("project was not remote"))); return Task::ready(Err(anyhow!("project was not remote")));
@ -537,8 +541,8 @@ impl ContextStore {
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
let response = request.await?; let response = request.await?;
let context_proto = response.context.context("invalid context")?; let context_proto = response.context.context("invalid context")?;
let context = cx.new_model(|cx| { let context = cx.new(|cx| {
Context::new( AssistantContext::new(
context_id.clone(), context_id.clone(),
replica_id, replica_id,
capability, capability,
@ -574,7 +578,7 @@ impl ContextStore {
}) })
} }
fn register_context(&mut self, context: &Model<Context>, cx: &mut ModelContext<Self>) { fn register_context(&mut self, context: &Entity<AssistantContext>, cx: &mut Context<Self>) {
let handle = if self.project_is_shared { let handle = if self.project_is_shared {
ContextHandle::Strong(context.clone()) ContextHandle::Strong(context.clone())
} else { } else {
@ -587,9 +591,9 @@ impl ContextStore {
fn handle_context_event( fn handle_context_event(
&mut self, &mut self,
context: Model<Context>, context: Entity<AssistantContext>,
event: &ContextEvent, event: &ContextEvent,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
let Some(project_id) = self.project.read(cx).remote_id() else { let Some(project_id) = self.project.read(cx).remote_id() else {
return; return;
@ -614,7 +618,7 @@ impl ContextStore {
} }
} }
fn advertise_contexts(&self, cx: &AppContext) { fn advertise_contexts(&self, cx: &App) {
let Some(project_id) = self.project.read(cx).remote_id() else { let Some(project_id) = self.project.read(cx).remote_id() else {
return; return;
}; };
@ -648,7 +652,7 @@ impl ContextStore {
.ok(); .ok();
} }
fn synchronize_contexts(&mut self, cx: &mut ModelContext<Self>) { fn synchronize_contexts(&mut self, cx: &mut Context<Self>) {
let Some(project_id) = self.project.read(cx).remote_id() else { let Some(project_id) = self.project.read(cx).remote_id() else {
return; return;
}; };
@ -703,7 +707,7 @@ impl ContextStore {
.detach_and_log_err(cx); .detach_and_log_err(cx);
} }
pub fn search(&self, query: String, cx: &AppContext) -> Task<Vec<SavedContextMetadata>> { pub fn search(&self, query: String, cx: &App) -> Task<Vec<SavedContextMetadata>> {
let metadata = self.contexts_metadata.clone(); let metadata = self.contexts_metadata.clone();
let executor = cx.background_executor().clone(); let executor = cx.background_executor().clone();
cx.background_executor().spawn(async move { cx.background_executor().spawn(async move {
@ -737,7 +741,7 @@ impl ContextStore {
&self.host_contexts &self.host_contexts
} }
fn reload(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> { fn reload(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
let fs = self.fs.clone(); let fs = self.fs.clone();
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
fs.create_dir(contexts_dir()).await?; fs.create_dir(contexts_dir()).await?;
@ -786,7 +790,7 @@ impl ContextStore {
}) })
} }
pub fn restart_context_servers(&mut self, cx: &mut ModelContext<Self>) { pub fn restart_context_servers(&mut self, cx: &mut Context<Self>) {
cx.update_model( cx.update_model(
&self.context_server_manager, &self.context_server_manager,
|context_server_manager, cx| { |context_server_manager, cx| {
@ -799,7 +803,7 @@ impl ContextStore {
); );
} }
fn register_context_server_handlers(&self, cx: &mut ModelContext<Self>) { fn register_context_server_handlers(&self, cx: &mut Context<Self>) {
cx.subscribe( cx.subscribe(
&self.context_server_manager.clone(), &self.context_server_manager.clone(),
Self::handle_context_server_event, Self::handle_context_server_event,
@ -809,9 +813,9 @@ impl ContextStore {
fn handle_context_server_event( fn handle_context_server_event(
&mut self, &mut self,
context_server_manager: Model<ContextServerManager>, context_server_manager: Entity<ContextServerManager>,
event: &context_server::manager::Event, event: &context_server::manager::Event,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
let slash_command_working_set = self.slash_commands.clone(); let slash_command_working_set = self.slash_commands.clone();
let tool_working_set = self.tools.clone(); let tool_working_set = self.tools.clone();

View file

@ -2,7 +2,7 @@ use anyhow::{anyhow, Context as _, Result};
use collections::HashMap; use collections::HashMap;
use editor::ProposedChangesEditor; use editor::ProposedChangesEditor;
use futures::{future, TryFutureExt as _}; use futures::{future, TryFutureExt as _};
use gpui::{AppContext, AsyncAppContext, Model, SharedString}; use gpui::{App, AsyncAppContext, Entity, SharedString};
use language::{AutoindentMode, Buffer, BufferSnapshot}; use language::{AutoindentMode, Buffer, BufferSnapshot};
use project::{Project, ProjectPath}; use project::{Project, ProjectPath};
use std::{cmp, ops::Range, path::Path, sync::Arc}; use std::{cmp, ops::Range, path::Path, sync::Arc};
@ -56,7 +56,7 @@ pub enum AssistantEditKind {
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct ResolvedPatch { pub struct ResolvedPatch {
pub edit_groups: HashMap<Model<Buffer>, Vec<ResolvedEditGroup>>, pub edit_groups: HashMap<Entity<Buffer>, Vec<ResolvedEditGroup>>,
pub errors: Vec<AssistantPatchResolutionError>, pub errors: Vec<AssistantPatchResolutionError>,
} }
@ -121,7 +121,7 @@ impl SearchMatrix {
} }
impl ResolvedPatch { impl ResolvedPatch {
pub fn apply(&self, editor: &ProposedChangesEditor, cx: &mut AppContext) { pub fn apply(&self, editor: &ProposedChangesEditor, cx: &mut App) {
for (buffer, groups) in &self.edit_groups { for (buffer, groups) in &self.edit_groups {
let branch = editor.branch_buffer_for_base(buffer).unwrap(); let branch = editor.branch_buffer_for_base(buffer).unwrap();
Self::apply_edit_groups(groups, &branch, cx); Self::apply_edit_groups(groups, &branch, cx);
@ -129,11 +129,7 @@ impl ResolvedPatch {
editor.recalculate_all_buffer_diffs(); editor.recalculate_all_buffer_diffs();
} }
fn apply_edit_groups( fn apply_edit_groups(groups: &Vec<ResolvedEditGroup>, buffer: &Entity<Buffer>, cx: &mut App) {
groups: &Vec<ResolvedEditGroup>,
buffer: &Model<Buffer>,
cx: &mut AppContext,
) {
let mut edits = Vec::new(); let mut edits = Vec::new();
for group in groups { for group in groups {
for suggestion in &group.edits { for suggestion in &group.edits {
@ -232,9 +228,9 @@ impl AssistantEdit {
pub async fn resolve( pub async fn resolve(
&self, &self,
project: Model<Project>, project: Entity<Project>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<(Model<Buffer>, ResolvedEdit)> { ) -> Result<(Entity<Buffer>, ResolvedEdit)> {
let path = self.path.clone(); let path = self.path.clone();
let kind = self.kind.clone(); let kind = self.kind.clone();
let buffer = project let buffer = project
@ -427,7 +423,7 @@ impl AssistantEditKind {
impl AssistantPatch { impl AssistantPatch {
pub async fn resolve( pub async fn resolve(
&self, &self,
project: Model<Project>, project: Entity<Project>,
cx: &mut AsyncAppContext, cx: &mut AsyncAppContext,
) -> ResolvedPatch { ) -> ResolvedPatch {
let mut resolve_tasks = Vec::new(); let mut resolve_tasks = Vec::new();
@ -555,7 +551,7 @@ impl Eq for AssistantPatch {}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use gpui::{AppContext, Context}; use gpui::{App, AppContext as _};
use language::{ use language::{
language_settings::AllLanguageSettings, Language, LanguageConfig, LanguageMatcher, language_settings::AllLanguageSettings, Language, LanguageConfig, LanguageMatcher,
}; };
@ -565,7 +561,7 @@ mod tests {
use util::test::{generate_marked_text, marked_text_ranges}; use util::test::{generate_marked_text, marked_text_ranges};
#[gpui::test] #[gpui::test]
fn test_resolve_location(cx: &mut AppContext) { fn test_resolve_location(cx: &mut App) {
assert_location_resolution( assert_location_resolution(
concat!( concat!(
" Lorem\n", " Lorem\n",
@ -636,7 +632,7 @@ mod tests {
} }
#[gpui::test] #[gpui::test]
fn test_resolve_edits(cx: &mut AppContext) { fn test_resolve_edits(cx: &mut App) {
init_test(cx); init_test(cx);
assert_edits( assert_edits(
@ -902,7 +898,7 @@ mod tests {
); );
} }
fn init_test(cx: &mut AppContext) { fn init_test(cx: &mut App) {
let settings_store = SettingsStore::test(cx); let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store); cx.set_global(settings_store);
language::init(cx); language::init(cx);
@ -912,13 +908,9 @@ mod tests {
} }
#[track_caller] #[track_caller]
fn assert_location_resolution( fn assert_location_resolution(text_with_expected_range: &str, query: &str, cx: &mut App) {
text_with_expected_range: &str,
query: &str,
cx: &mut AppContext,
) {
let (text, _) = marked_text_ranges(text_with_expected_range, false); let (text, _) = marked_text_ranges(text_with_expected_range, false);
let buffer = cx.new_model(|cx| Buffer::local(text.clone(), cx)); let buffer = cx.new(|cx| Buffer::local(text.clone(), cx));
let snapshot = buffer.read(cx).snapshot(); let snapshot = buffer.read(cx).snapshot();
let range = AssistantEditKind::resolve_location(&snapshot, query).to_offset(&snapshot); let range = AssistantEditKind::resolve_location(&snapshot, query).to_offset(&snapshot);
let text_with_actual_range = generate_marked_text(&text, &[range], false); let text_with_actual_range = generate_marked_text(&text, &[range], false);
@ -930,10 +922,10 @@ mod tests {
old_text: String, old_text: String,
edits: Vec<AssistantEditKind>, edits: Vec<AssistantEditKind>,
new_text: String, new_text: String,
cx: &mut AppContext, cx: &mut App,
) { ) {
let buffer = let buffer =
cx.new_model(|cx| Buffer::local(old_text, cx).with_language(Arc::new(rust_lang()), cx)); cx.new(|cx| Buffer::local(old_text, cx).with_language(Arc::new(rust_lang()), cx));
let snapshot = buffer.read(cx).snapshot(); let snapshot = buffer.read(cx).snapshot();
let resolved_edits = edits let resolved_edits = edits
.into_iter() .into_iter()

View file

@ -4,7 +4,7 @@ pub use assistant_slash_command::SlashCommand;
use assistant_slash_command::{AfterCompletion, SlashCommandLine, SlashCommandWorkingSet}; use assistant_slash_command::{AfterCompletion, SlashCommandLine, SlashCommandWorkingSet};
use editor::{CompletionProvider, Editor}; use editor::{CompletionProvider, Editor};
use fuzzy::{match_strings, StringMatchCandidate}; use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{Model, Task, ViewContext, WeakView, WindowContext}; use gpui::{App, Context, Entity, Task, WeakEntity, Window};
use language::{Anchor, Buffer, Documentation, LanguageServerId, ToPoint}; use language::{Anchor, Buffer, Documentation, LanguageServerId, ToPoint};
use parking_lot::Mutex; use parking_lot::Mutex;
use project::CompletionIntent; use project::CompletionIntent;
@ -23,15 +23,15 @@ use workspace::Workspace;
pub struct SlashCommandCompletionProvider { pub struct SlashCommandCompletionProvider {
cancel_flag: Mutex<Arc<AtomicBool>>, cancel_flag: Mutex<Arc<AtomicBool>>,
slash_commands: Arc<SlashCommandWorkingSet>, slash_commands: Arc<SlashCommandWorkingSet>,
editor: Option<WeakView<ContextEditor>>, editor: Option<WeakEntity<ContextEditor>>,
workspace: Option<WeakView<Workspace>>, workspace: Option<WeakEntity<Workspace>>,
} }
impl SlashCommandCompletionProvider { impl SlashCommandCompletionProvider {
pub fn new( pub fn new(
slash_commands: Arc<SlashCommandWorkingSet>, slash_commands: Arc<SlashCommandWorkingSet>,
editor: Option<WeakView<ContextEditor>>, editor: Option<WeakEntity<ContextEditor>>,
workspace: Option<WeakView<Workspace>>, workspace: Option<WeakEntity<Workspace>>,
) -> Self { ) -> Self {
Self { Self {
cancel_flag: Mutex::new(Arc::new(AtomicBool::new(false))), cancel_flag: Mutex::new(Arc::new(AtomicBool::new(false))),
@ -46,7 +46,8 @@ impl SlashCommandCompletionProvider {
command_name: &str, command_name: &str,
command_range: Range<Anchor>, command_range: Range<Anchor>,
name_range: Range<Anchor>, name_range: Range<Anchor>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Task<Result<Vec<project::Completion>>> { ) -> Task<Result<Vec<project::Completion>>> {
let slash_commands = self.slash_commands.clone(); let slash_commands = self.slash_commands.clone();
let candidates = slash_commands let candidates = slash_commands
@ -58,7 +59,7 @@ impl SlashCommandCompletionProvider {
let command_name = command_name.to_string(); let command_name = command_name.to_string();
let editor = self.editor.clone(); let editor = self.editor.clone();
let workspace = self.workspace.clone(); let workspace = self.workspace.clone();
cx.spawn(|mut cx| async move { window.spawn(cx, |mut cx| async move {
let matches = match_strings( let matches = match_strings(
&candidates, &candidates,
&command_name, &command_name,
@ -69,7 +70,7 @@ impl SlashCommandCompletionProvider {
) )
.await; .await;
cx.update(|cx| { cx.update(|_, cx| {
matches matches
.into_iter() .into_iter()
.filter_map(|mat| { .filter_map(|mat| {
@ -91,28 +92,31 @@ impl SlashCommandCompletionProvider {
let editor = editor.clone(); let editor = editor.clone();
let workspace = workspace.clone(); let workspace = workspace.clone();
Arc::new( Arc::new(
move |intent: CompletionIntent, cx: &mut WindowContext| { move |intent: CompletionIntent,
if !requires_argument window: &mut Window,
&& (!accepts_arguments || intent.is_complete()) cx: &mut App| {
{ if !requires_argument
editor && (!accepts_arguments || intent.is_complete())
.update(cx, |editor, cx| { {
editor.run_command( editor
command_range.clone(), .update(cx, |editor, cx| {
&command_name, editor.run_command(
&[], command_range.clone(),
true, &command_name,
workspace.clone(), &[],
cx, true,
); workspace.clone(),
}) window,
.ok(); cx,
false );
} else { })
requires_argument || accepts_arguments .ok();
} false
}, } else {
) as Arc<_> requires_argument || accepts_arguments
}
},
) as Arc<_>
}); });
Some(project::Completion { Some(project::Completion {
old_range: name_range.clone(), old_range: name_range.clone(),
@ -130,6 +134,7 @@ impl SlashCommandCompletionProvider {
}) })
} }
#[allow(clippy::too_many_arguments)]
fn complete_command_argument( fn complete_command_argument(
&self, &self,
command_name: &str, command_name: &str,
@ -137,7 +142,8 @@ impl SlashCommandCompletionProvider {
command_range: Range<Anchor>, command_range: Range<Anchor>,
argument_range: Range<Anchor>, argument_range: Range<Anchor>,
last_argument_range: Range<Anchor>, last_argument_range: Range<Anchor>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Task<Result<Vec<project::Completion>>> { ) -> Task<Result<Vec<project::Completion>>> {
let new_cancel_flag = Arc::new(AtomicBool::new(false)); let new_cancel_flag = Arc::new(AtomicBool::new(false));
let mut flag = self.cancel_flag.lock(); let mut flag = self.cancel_flag.lock();
@ -148,6 +154,7 @@ impl SlashCommandCompletionProvider {
arguments, arguments,
new_cancel_flag.clone(), new_cancel_flag.clone(),
self.workspace.clone(), self.workspace.clone(),
window,
cx, cx,
); );
let command_name: Arc<str> = command_name.into(); let command_name: Arc<str> = command_name.into();
@ -175,7 +182,9 @@ impl SlashCommandCompletionProvider {
let command_range = command_range.clone(); let command_range = command_range.clone();
let command_name = command_name.clone(); let command_name = command_name.clone();
move |intent: CompletionIntent, cx: &mut WindowContext| { move |intent: CompletionIntent,
window: &mut Window,
cx: &mut App| {
if new_argument.after_completion.run() if new_argument.after_completion.run()
|| intent.is_complete() || intent.is_complete()
{ {
@ -187,6 +196,7 @@ impl SlashCommandCompletionProvider {
&completed_arguments, &completed_arguments,
true, true,
workspace.clone(), workspace.clone(),
window,
cx, cx,
); );
}) })
@ -230,10 +240,11 @@ impl SlashCommandCompletionProvider {
impl CompletionProvider for SlashCommandCompletionProvider { impl CompletionProvider for SlashCommandCompletionProvider {
fn completions( fn completions(
&self, &self,
buffer: &Model<Buffer>, buffer: &Entity<Buffer>,
buffer_position: Anchor, buffer_position: Anchor,
_: editor::CompletionContext, _: editor::CompletionContext,
cx: &mut ViewContext<Editor>, window: &mut Window,
cx: &mut Context<Editor>,
) -> Task<Result<Vec<project::Completion>>> { ) -> Task<Result<Vec<project::Completion>>> {
let Some((name, arguments, command_range, last_argument_range)) = let Some((name, arguments, command_range, last_argument_range)) =
buffer.update(cx, |buffer, _cx| { buffer.update(cx, |buffer, _cx| {
@ -288,30 +299,31 @@ impl CompletionProvider for SlashCommandCompletionProvider {
command_range, command_range,
argument_range, argument_range,
last_argument_range, last_argument_range,
window,
cx, cx,
) )
} else { } else {
self.complete_command_name(&name, command_range, last_argument_range, cx) self.complete_command_name(&name, command_range, last_argument_range, window, cx)
} }
} }
fn resolve_completions( fn resolve_completions(
&self, &self,
_: Model<Buffer>, _: Entity<Buffer>,
_: Vec<usize>, _: Vec<usize>,
_: Rc<RefCell<Box<[project::Completion]>>>, _: Rc<RefCell<Box<[project::Completion]>>>,
_: &mut ViewContext<Editor>, _: &mut Context<Editor>,
) -> Task<Result<bool>> { ) -> Task<Result<bool>> {
Task::ready(Ok(true)) Task::ready(Ok(true))
} }
fn is_completion_trigger( fn is_completion_trigger(
&self, &self,
buffer: &Model<Buffer>, buffer: &Entity<Buffer>,
position: language::Anchor, position: language::Anchor,
_text: &str, _text: &str,
_trigger_in_words: bool, _trigger_in_words: bool,
cx: &mut ViewContext<Editor>, cx: &mut Context<Editor>,
) -> bool { ) -> bool {
let buffer = buffer.read(cx); let buffer = buffer.read(cx);
let position = position.to_point(buffer); let position = position.to_point(buffer);

View file

@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use assistant_slash_command::SlashCommandWorkingSet; use assistant_slash_command::SlashCommandWorkingSet;
use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakView}; use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakEntity};
use picker::{Picker, PickerDelegate, PickerEditorPosition}; use picker::{Picker, PickerDelegate, PickerEditorPosition};
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger, Tooltip}; use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger, Tooltip};
@ -10,7 +10,7 @@ use crate::context_editor::ContextEditor;
#[derive(IntoElement)] #[derive(IntoElement)]
pub(super) struct SlashCommandSelector<T: PopoverTrigger> { pub(super) struct SlashCommandSelector<T: PopoverTrigger> {
working_set: Arc<SlashCommandWorkingSet>, working_set: Arc<SlashCommandWorkingSet>,
active_context_editor: WeakView<ContextEditor>, active_context_editor: WeakEntity<ContextEditor>,
trigger: T, trigger: T,
} }
@ -27,8 +27,8 @@ enum SlashCommandEntry {
Info(SlashCommandInfo), Info(SlashCommandInfo),
Advert { Advert {
name: SharedString, name: SharedString,
renderer: fn(&mut WindowContext) -> AnyElement, renderer: fn(&mut Window, &mut App) -> AnyElement,
on_confirm: fn(&mut WindowContext), on_confirm: fn(&mut Window, &mut App),
}, },
} }
@ -44,14 +44,14 @@ impl AsRef<str> for SlashCommandEntry {
pub(crate) struct SlashCommandDelegate { pub(crate) struct SlashCommandDelegate {
all_commands: Vec<SlashCommandEntry>, all_commands: Vec<SlashCommandEntry>,
filtered_commands: Vec<SlashCommandEntry>, filtered_commands: Vec<SlashCommandEntry>,
active_context_editor: WeakView<ContextEditor>, active_context_editor: WeakEntity<ContextEditor>,
selected_index: usize, selected_index: usize,
} }
impl<T: PopoverTrigger> SlashCommandSelector<T> { impl<T: PopoverTrigger> SlashCommandSelector<T> {
pub(crate) fn new( pub(crate) fn new(
working_set: Arc<SlashCommandWorkingSet>, working_set: Arc<SlashCommandWorkingSet>,
active_context_editor: WeakView<ContextEditor>, active_context_editor: WeakEntity<ContextEditor>,
trigger: T, trigger: T,
) -> Self { ) -> Self {
SlashCommandSelector { SlashCommandSelector {
@ -73,18 +73,23 @@ impl PickerDelegate for SlashCommandDelegate {
self.selected_index self.selected_index
} }
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) { fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<Picker<Self>>) {
self.selected_index = ix.min(self.filtered_commands.len().saturating_sub(1)); self.selected_index = ix.min(self.filtered_commands.len().saturating_sub(1));
cx.notify(); cx.notify();
} }
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> { fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
"Select a command...".into() "Select a command...".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 all_commands = self.all_commands.clone(); let all_commands = self.all_commands.clone();
cx.spawn(|this, mut cx| async move { cx.spawn_in(window, |this, mut cx| async move {
let filtered_commands = cx let filtered_commands = cx
.background_executor() .background_executor()
.spawn(async move { .spawn(async move {
@ -104,9 +109,9 @@ impl PickerDelegate for SlashCommandDelegate {
}) })
.await; .await;
this.update(&mut cx, |this, cx| { this.update_in(&mut cx, |this, window, cx| {
this.delegate.filtered_commands = filtered_commands; this.delegate.filtered_commands = filtered_commands;
this.delegate.set_selected_index(0, cx); this.delegate.set_selected_index(0, window, cx);
cx.notify(); cx.notify();
}) })
.ok(); .ok();
@ -139,25 +144,25 @@ impl PickerDelegate for SlashCommandDelegate {
ret ret
} }
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>>) {
if let Some(command) = self.filtered_commands.get(self.selected_index) { if let Some(command) = self.filtered_commands.get(self.selected_index) {
match command { match command {
SlashCommandEntry::Info(info) => { SlashCommandEntry::Info(info) => {
self.active_context_editor self.active_context_editor
.update(cx, |context_editor, cx| { .update(cx, |context_editor, cx| {
context_editor.insert_command(&info.name, cx) context_editor.insert_command(&info.name, window, cx)
}) })
.ok(); .ok();
} }
SlashCommandEntry::Advert { on_confirm, .. } => { SlashCommandEntry::Advert { on_confirm, .. } => {
on_confirm(cx); on_confirm(window, cx);
} }
} }
cx.emit(DismissEvent); cx.emit(DismissEvent);
} }
} }
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {} fn dismissed(&mut self, _window: &mut Window, _cx: &mut Context<Picker<Self>>) {}
fn editor_position(&self) -> PickerEditorPosition { fn editor_position(&self) -> PickerEditorPosition {
PickerEditorPosition::End PickerEditorPosition::End
@ -167,7 +172,8 @@ impl PickerDelegate for SlashCommandDelegate {
&self, &self,
ix: usize, ix: usize,
selected: bool, selected: bool,
cx: &mut ViewContext<Picker<Self>>, window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> { ) -> Option<Self::ListItem> {
let command_info = self.filtered_commands.get(ix)?; let command_info = self.filtered_commands.get(ix)?;
@ -179,7 +185,7 @@ impl PickerDelegate for SlashCommandDelegate {
.toggle_state(selected) .toggle_state(selected)
.tooltip({ .tooltip({
let description = info.description.clone(); let description = info.description.clone();
move |cx| cx.new_view(|_| Tooltip::new(description.clone())).into() move |_, cx| cx.new(|_| Tooltip::new(description.clone())).into()
}) })
.child( .child(
v_flex() v_flex()
@ -229,14 +235,14 @@ impl PickerDelegate for SlashCommandDelegate {
.inset(true) .inset(true)
.spacing(ListItemSpacing::Dense) .spacing(ListItemSpacing::Dense)
.toggle_state(selected) .toggle_state(selected)
.child(renderer(cx)), .child(renderer(window, cx)),
), ),
} }
} }
} }
impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> { impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
fn render(self, cx: &mut WindowContext) -> impl IntoElement { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let all_models = self let all_models = self
.working_set .working_set
.featured_command_names(cx) .featured_command_names(cx)
@ -259,7 +265,7 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
}) })
.chain([SlashCommandEntry::Advert { .chain([SlashCommandEntry::Advert {
name: "create-your-command".into(), name: "create-your-command".into(),
renderer: |cx| { renderer: |_, cx| {
v_flex() v_flex()
.w_full() .w_full()
.child( .child(
@ -293,7 +299,7 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
) )
.into_any_element() .into_any_element()
}, },
on_confirm: |cx| cx.open_url("https://zed.dev/docs/extensions/slash-commands"), on_confirm: |_, cx| cx.open_url("https://zed.dev/docs/extensions/slash-commands"),
}]) }])
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -304,8 +310,9 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
selected_index: 0, selected_index: 0,
}; };
let picker_view = cx.new_view(|cx| { let picker_view = cx.new(|cx| {
let picker = Picker::uniform_list(delegate, cx).max_height(Some(rems(20.).into())); let picker =
Picker::uniform_list(delegate, window, cx).max_height(Some(rems(20.).into()));
picker picker
}); });
@ -314,7 +321,7 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
.update(cx, |this, _| this.slash_menu_handle.clone()) .update(cx, |this, _| this.slash_menu_handle.clone())
.ok(); .ok();
PopoverMenu::new("model-switcher") PopoverMenu::new("model-switcher")
.menu(move |_cx| Some(picker_view.clone())) .menu(move |_window, _cx| Some(picker_view.clone()))
.trigger(self.trigger) .trigger(self.trigger)
.attach(gpui::Corner::TopLeft) .attach(gpui::Corner::TopLeft)
.anchor(gpui::Corner::BottomLeft) .anchor(gpui::Corner::BottomLeft)

View file

@ -3,7 +3,7 @@ use std::sync::Arc;
use ::open_ai::Model as OpenAiModel; use ::open_ai::Model as OpenAiModel;
use anthropic::Model as AnthropicModel; use anthropic::Model as AnthropicModel;
use feature_flags::FeatureFlagAppExt; use feature_flags::FeatureFlagAppExt;
use gpui::{AppContext, Pixels}; use gpui::{App, Pixels};
use language_model::{CloudModel, LanguageModel}; use language_model::{CloudModel, LanguageModel};
use lmstudio::Model as LmStudioModel; use lmstudio::Model as LmStudioModel;
use ollama::Model as OllamaModel; use ollama::Model as OllamaModel;
@ -62,7 +62,7 @@ pub struct AssistantSettings {
} }
impl AssistantSettings { impl AssistantSettings {
pub fn are_live_diffs_enabled(&self, cx: &AppContext) -> bool { pub fn are_live_diffs_enabled(&self, cx: &App) -> bool {
cx.is_staff() || self.enable_experimental_live_diffs cx.is_staff() || self.enable_experimental_live_diffs
} }
} }
@ -422,7 +422,7 @@ impl Settings for AssistantSettings {
fn load( fn load(
sources: SettingsSources<Self::FileContent>, sources: SettingsSources<Self::FileContent>,
_: &mut gpui::AppContext, _: &mut gpui::App,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
let mut settings = AssistantSettings::default(); let mut settings = AssistantSettings::default();

View file

@ -8,7 +8,7 @@ pub use crate::slash_command_working_set::*;
use anyhow::Result; use anyhow::Result;
use futures::stream::{self, BoxStream}; use futures::stream::{self, BoxStream};
use futures::StreamExt; use futures::StreamExt;
use gpui::{AppContext, SharedString, Task, WeakView, WindowContext}; use gpui::{App, SharedString, Task, WeakEntity, Window};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt}; use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
pub use language_model::Role; pub use language_model::Role;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -18,7 +18,7 @@ use std::{
}; };
use workspace::{ui::IconName, Workspace}; use workspace::{ui::IconName, Workspace};
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut App) {
SlashCommandRegistry::default_global(cx); SlashCommandRegistry::default_global(cx);
extension_slash_command::init(cx); extension_slash_command::init(cx);
} }
@ -71,7 +71,7 @@ pub trait SlashCommand: 'static + Send + Sync {
fn icon(&self) -> IconName { fn icon(&self) -> IconName {
IconName::Slash IconName::Slash
} }
fn label(&self, _cx: &AppContext) -> CodeLabel { fn label(&self, _cx: &App) -> CodeLabel {
CodeLabel::plain(self.name(), None) CodeLabel::plain(self.name(), None)
} }
fn description(&self) -> String; fn description(&self) -> String;
@ -80,26 +80,29 @@ pub trait SlashCommand: 'static + Send + Sync {
self: Arc<Self>, self: Arc<Self>,
arguments: &[String], arguments: &[String],
cancel: Arc<AtomicBool>, cancel: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>, workspace: Option<WeakEntity<Workspace>>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>>; ) -> Task<Result<Vec<ArgumentCompletion>>>;
fn requires_argument(&self) -> bool; fn requires_argument(&self) -> bool;
fn accepts_arguments(&self) -> bool { fn accepts_arguments(&self) -> bool {
self.requires_argument() self.requires_argument()
} }
#[allow(clippy::too_many_arguments)]
fn run( fn run(
self: Arc<Self>, self: Arc<Self>,
arguments: &[String], arguments: &[String],
context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>], context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
context_buffer: BufferSnapshot, context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
// TODO: We're just using the `LspAdapterDelegate` here because that is // TODO: We're just using the `LspAdapterDelegate` here because that is
// what the extension API is already expecting. // what the extension API is already expecting.
// //
// It may be that `LspAdapterDelegate` needs a more general name, or // It may be that `LspAdapterDelegate` needs a more general name, or
// perhaps another kind of delegate is needed here. // perhaps another kind of delegate is needed here.
delegate: Option<Arc<dyn LspAdapterDelegate>>, delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult>; ) -> Task<SlashCommandResult>;
} }

View file

@ -4,7 +4,7 @@ use std::sync::{atomic::AtomicBool, Arc};
use anyhow::Result; use anyhow::Result;
use async_trait::async_trait; use async_trait::async_trait;
use extension::{Extension, ExtensionHostProxy, ExtensionSlashCommandProxy, WorktreeDelegate}; use extension::{Extension, ExtensionHostProxy, ExtensionSlashCommandProxy, WorktreeDelegate};
use gpui::{AppContext, Task, WeakView, WindowContext}; use gpui::{App, Task, WeakEntity, Window};
use language::{BufferSnapshot, LspAdapterDelegate}; use language::{BufferSnapshot, LspAdapterDelegate};
use ui::prelude::*; use ui::prelude::*;
use workspace::Workspace; use workspace::Workspace;
@ -14,7 +14,7 @@ use crate::{
SlashCommandRegistry, SlashCommandResult, SlashCommandRegistry, SlashCommandResult,
}; };
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut App) {
let proxy = ExtensionHostProxy::default_global(cx); let proxy = ExtensionHostProxy::default_global(cx);
proxy.register_slash_command_proxy(SlashCommandRegistryProxy { proxy.register_slash_command_proxy(SlashCommandRegistryProxy {
slash_command_registry: SlashCommandRegistry::global(cx), slash_command_registry: SlashCommandRegistry::global(cx),
@ -97,8 +97,9 @@ impl SlashCommand for ExtensionSlashCommand {
self: Arc<Self>, self: Arc<Self>,
arguments: &[String], arguments: &[String],
_cancel: Arc<AtomicBool>, _cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>, _workspace: Option<WeakEntity<Workspace>>,
cx: &mut WindowContext, _window: &mut Window,
cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> { ) -> Task<Result<Vec<ArgumentCompletion>>> {
let command = self.command.clone(); let command = self.command.clone();
let arguments = arguments.to_owned(); let arguments = arguments.to_owned();
@ -127,9 +128,10 @@ impl SlashCommand for ExtensionSlashCommand {
arguments: &[String], arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>], _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot, _context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>, _workspace: WeakEntity<Workspace>,
delegate: Option<Arc<dyn LspAdapterDelegate>>, delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext, _window: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> { ) -> Task<SlashCommandResult> {
let command = self.command.clone(); let command = self.command.clone();
let arguments = arguments.to_owned(); let arguments = arguments.to_owned();

View file

@ -3,7 +3,7 @@ use std::sync::Arc;
use collections::{BTreeSet, HashMap}; use collections::{BTreeSet, HashMap};
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use gpui::Global; use gpui::Global;
use gpui::{AppContext, ReadGlobal}; use gpui::{App, ReadGlobal};
use parking_lot::RwLock; use parking_lot::RwLock;
use crate::SlashCommand; use crate::SlashCommand;
@ -26,14 +26,14 @@ pub struct SlashCommandRegistry {
impl SlashCommandRegistry { impl SlashCommandRegistry {
/// Returns the global [`SlashCommandRegistry`]. /// Returns the global [`SlashCommandRegistry`].
pub fn global(cx: &AppContext) -> Arc<Self> { pub fn global(cx: &App) -> Arc<Self> {
GlobalSlashCommandRegistry::global(cx).0.clone() GlobalSlashCommandRegistry::global(cx).0.clone()
} }
/// Returns the global [`SlashCommandRegistry`]. /// Returns the global [`SlashCommandRegistry`].
/// ///
/// Inserts a default [`SlashCommandRegistry`] if one does not yet exist. /// Inserts a default [`SlashCommandRegistry`] if one does not yet exist.
pub fn default_global(cx: &mut AppContext) -> Arc<Self> { pub fn default_global(cx: &mut App) -> Arc<Self> {
cx.default_global::<GlobalSlashCommandRegistry>().0.clone() cx.default_global::<GlobalSlashCommandRegistry>().0.clone()
} }

View file

@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use collections::HashMap; use collections::HashMap;
use gpui::AppContext; use gpui::App;
use parking_lot::Mutex; use parking_lot::Mutex;
use crate::{SlashCommand, SlashCommandRegistry}; use crate::{SlashCommand, SlashCommandRegistry};
@ -23,7 +23,7 @@ struct WorkingSetState {
} }
impl SlashCommandWorkingSet { impl SlashCommandWorkingSet {
pub fn command(&self, name: &str, cx: &AppContext) -> Option<Arc<dyn SlashCommand>> { pub fn command(&self, name: &str, cx: &App) -> Option<Arc<dyn SlashCommand>> {
self.state self.state
.lock() .lock()
.context_server_commands_by_name .context_server_commands_by_name
@ -32,7 +32,7 @@ impl SlashCommandWorkingSet {
.or_else(|| SlashCommandRegistry::global(cx).command(name)) .or_else(|| SlashCommandRegistry::global(cx).command(name))
} }
pub fn command_names(&self, cx: &AppContext) -> Vec<Arc<str>> { pub fn command_names(&self, cx: &App) -> Vec<Arc<str>> {
let mut command_names = SlashCommandRegistry::global(cx).command_names(); let mut command_names = SlashCommandRegistry::global(cx).command_names();
command_names.extend( command_names.extend(
self.state self.state
@ -45,7 +45,7 @@ impl SlashCommandWorkingSet {
command_names command_names
} }
pub fn featured_command_names(&self, cx: &AppContext) -> Vec<Arc<str>> { pub fn featured_command_names(&self, cx: &App) -> Vec<Arc<str>> {
SlashCommandRegistry::global(cx).featured_command_names() SlashCommandRegistry::global(cx).featured_command_names()
} }

View file

@ -17,7 +17,7 @@ mod symbols_command;
mod tab_command; mod tab_command;
mod terminal_command; mod terminal_command;
use gpui::AppContext; use gpui::App;
use language::{CodeLabel, HighlightId}; use language::{CodeLabel, HighlightId};
use ui::ActiveTheme as _; use ui::ActiveTheme as _;
@ -40,11 +40,7 @@ pub use crate::symbols_command::*;
pub use crate::tab_command::*; pub use crate::tab_command::*;
pub use crate::terminal_command::*; pub use crate::terminal_command::*;
pub fn create_label_for_command( pub fn create_label_for_command(command_name: &str, arguments: &[&str], cx: &App) -> CodeLabel {
command_name: &str,
arguments: &[&str],
cx: &AppContext,
) -> CodeLabel {
let mut label = CodeLabel::default(); let mut label = CodeLabel::default();
label.push_str(command_name, None); label.push_str(command_name, None);
label.push_str(" ", None); label.push_str(" ", None);

View file

@ -5,7 +5,7 @@ use assistant_slash_command::{
}; };
use feature_flags::FeatureFlag; use feature_flags::FeatureFlag;
use futures::StreamExt; use futures::StreamExt;
use gpui::{AppContext, AsyncAppContext, AsyncWindowContext, Task, WeakView, WindowContext}; use gpui::{App, AsyncAppContext, Task, WeakEntity, Window};
use language::{CodeLabel, LspAdapterDelegate}; use language::{CodeLabel, LspAdapterDelegate};
use language_model::{ use language_model::{
LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
@ -45,7 +45,7 @@ impl SlashCommand for AutoCommand {
self.description() self.description()
} }
fn label(&self, cx: &AppContext) -> CodeLabel { fn label(&self, cx: &App) -> CodeLabel {
create_label_for_command("auto", &["--prompt"], cx) create_label_for_command("auto", &["--prompt"], cx)
} }
@ -53,8 +53,9 @@ impl SlashCommand for AutoCommand {
self: Arc<Self>, self: Arc<Self>,
_arguments: &[String], _arguments: &[String],
_cancel: Arc<AtomicBool>, _cancel: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>, workspace: Option<WeakEntity<Workspace>>,
cx: &mut WindowContext, _window: &mut Window,
cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> { ) -> Task<Result<Vec<ArgumentCompletion>>> {
// There's no autocomplete for a prompt, since it's arbitrary text. // There's no autocomplete for a prompt, since it's arbitrary text.
// However, we can use this opportunity to kick off a drain of the backlog. // However, we can use this opportunity to kick off a drain of the backlog.
@ -74,7 +75,7 @@ impl SlashCommand for AutoCommand {
return Task::ready(Err(anyhow!("No project indexer, cannot use /auto"))); return Task::ready(Err(anyhow!("No project indexer, cannot use /auto")));
}; };
let cx: &mut AppContext = cx; let cx: &mut App = cx;
cx.spawn(|cx: gpui::AsyncAppContext| async move { cx.spawn(|cx: gpui::AsyncAppContext| async move {
let task = project_index.read_with(&cx, |project_index, cx| { let task = project_index.read_with(&cx, |project_index, cx| {
@ -96,9 +97,10 @@ impl SlashCommand for AutoCommand {
arguments: &[String], arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>], _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: language::BufferSnapshot, _context_buffer: language::BufferSnapshot,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>, _delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> { ) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else { let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow::anyhow!("workspace was dropped"))); return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
@ -115,7 +117,7 @@ impl SlashCommand for AutoCommand {
return Task::ready(Err(anyhow!("no project indexer"))); return Task::ready(Err(anyhow!("no project indexer")));
}; };
let task = cx.spawn(|cx: AsyncWindowContext| async move { let task = window.spawn(cx, |cx| async move {
let summaries = project_index let summaries = project_index
.read_with(&cx, |project_index, cx| project_index.all_summaries(cx))? .read_with(&cx, |project_index, cx| project_index.all_summaries(cx))?
.await?; .await?;

View file

@ -1,10 +1,10 @@
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context as _, Result};
use assistant_slash_command::{ use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection, ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult, SlashCommandResult,
}; };
use fs::Fs; use fs::Fs;
use gpui::{AppContext, Model, Task, WeakView}; use gpui::{App, Entity, Task, WeakEntity};
use language::{BufferSnapshot, LspAdapterDelegate}; use language::{BufferSnapshot, LspAdapterDelegate};
use project::{Project, ProjectPath}; use project::{Project, ProjectPath};
use std::{ use std::{
@ -76,7 +76,7 @@ impl CargoWorkspaceSlashCommand {
Ok(message) Ok(message)
} }
fn path_to_cargo_toml(project: Model<Project>, cx: &mut AppContext) -> Option<Arc<Path>> { fn path_to_cargo_toml(project: Entity<Project>, cx: &mut App) -> Option<Arc<Path>> {
let worktree = project.read(cx).worktrees(cx).next()?; let worktree = project.read(cx).worktrees(cx).next()?;
let worktree = worktree.read(cx); let worktree = worktree.read(cx);
let entry = worktree.entry_for_path("Cargo.toml")?; let entry = worktree.entry_for_path("Cargo.toml")?;
@ -107,8 +107,9 @@ impl SlashCommand for CargoWorkspaceSlashCommand {
self: Arc<Self>, self: Arc<Self>,
_arguments: &[String], _arguments: &[String],
_cancel: Arc<AtomicBool>, _cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>, _workspace: Option<WeakEntity<Workspace>>,
_cx: &mut WindowContext, _window: &mut Window,
_cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> { ) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Err(anyhow!("this command does not require argument"))) Task::ready(Err(anyhow!("this command does not require argument")))
} }
@ -122,9 +123,10 @@ impl SlashCommand for CargoWorkspaceSlashCommand {
_arguments: &[String], _arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>], _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot, _context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>, _delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext, _window: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> { ) -> Task<SlashCommandResult> {
let output = workspace.update(cx, |workspace, cx| { let output = workspace.update(cx, |workspace, cx| {
let project = workspace.project().clone(); let project = workspace.project().clone();

View file

@ -8,7 +8,7 @@ use context_server::{
manager::{ContextServer, ContextServerManager}, manager::{ContextServer, ContextServerManager},
types::Prompt, types::Prompt,
}; };
use gpui::{AppContext, Model, Task, WeakView, WindowContext}; use gpui::{App, Entity, Task, WeakEntity, Window};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate}; use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
use std::sync::Arc; use std::sync::Arc;
@ -19,14 +19,14 @@ use workspace::Workspace;
use crate::create_label_for_command; use crate::create_label_for_command;
pub struct ContextServerSlashCommand { pub struct ContextServerSlashCommand {
server_manager: Model<ContextServerManager>, server_manager: Entity<ContextServerManager>,
server_id: Arc<str>, server_id: Arc<str>,
prompt: Prompt, prompt: Prompt,
} }
impl ContextServerSlashCommand { impl ContextServerSlashCommand {
pub fn new( pub fn new(
server_manager: Model<ContextServerManager>, server_manager: Entity<ContextServerManager>,
server: &Arc<ContextServer>, server: &Arc<ContextServer>,
prompt: Prompt, prompt: Prompt,
) -> Self { ) -> Self {
@ -43,7 +43,7 @@ impl SlashCommand for ContextServerSlashCommand {
self.prompt.name.clone() self.prompt.name.clone()
} }
fn label(&self, cx: &AppContext) -> language::CodeLabel { fn label(&self, cx: &App) -> language::CodeLabel {
let mut parts = vec![self.prompt.name.as_str()]; let mut parts = vec![self.prompt.name.as_str()];
if let Some(args) = &self.prompt.arguments { if let Some(args) = &self.prompt.arguments {
if let Some(arg) = args.first() { if let Some(arg) = args.first() {
@ -77,8 +77,9 @@ impl SlashCommand for ContextServerSlashCommand {
self: Arc<Self>, self: Arc<Self>,
arguments: &[String], arguments: &[String],
_cancel: Arc<AtomicBool>, _cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>, _workspace: Option<WeakEntity<Workspace>>,
cx: &mut WindowContext, _window: &mut Window,
cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> { ) -> Task<Result<Vec<ArgumentCompletion>>> {
let Ok((arg_name, arg_value)) = completion_argument(&self.prompt, arguments) else { let Ok((arg_name, arg_value)) = completion_argument(&self.prompt, arguments) else {
return Task::ready(Err(anyhow!("Failed to complete argument"))); return Task::ready(Err(anyhow!("Failed to complete argument")));
@ -128,9 +129,10 @@ impl SlashCommand for ContextServerSlashCommand {
arguments: &[String], arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>], _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot, _context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>, _workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>, _delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext, _window: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> { ) -> Task<SlashCommandResult> {
let server_id = self.server_id.clone(); let server_id = self.server_id.clone();
let prompt_name = self.prompt.name.clone(); let prompt_name = self.prompt.name.clone();

View file

@ -3,7 +3,7 @@ use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection, ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult, SlashCommandResult,
}; };
use gpui::{Task, WeakView}; use gpui::{Task, WeakEntity};
use language::{BufferSnapshot, LspAdapterDelegate}; use language::{BufferSnapshot, LspAdapterDelegate};
use prompt_library::PromptStore; use prompt_library::PromptStore;
use std::{ use std::{
@ -36,8 +36,9 @@ impl SlashCommand for DefaultSlashCommand {
self: Arc<Self>, self: Arc<Self>,
_arguments: &[String], _arguments: &[String],
_cancellation_flag: Arc<AtomicBool>, _cancellation_flag: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>, _workspace: Option<WeakEntity<Workspace>>,
_cx: &mut WindowContext, _window: &mut Window,
_cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> { ) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Err(anyhow!("this command does not require argument"))) Task::ready(Err(anyhow!("this command does not require argument")))
} }
@ -47,9 +48,10 @@ impl SlashCommand for DefaultSlashCommand {
_arguments: &[String], _arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>], _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot, _context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>, _workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>, _delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext, _window: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> { ) -> Task<SlashCommandResult> {
let store = PromptStore::global(cx); let store = PromptStore::global(cx);
cx.background_executor().spawn(async move { cx.background_executor().spawn(async move {

View file

@ -6,7 +6,7 @@ use assistant_slash_command::{
}; };
use collections::HashSet; use collections::HashSet;
use futures::future; use futures::future;
use gpui::{Task, WeakView, WindowContext}; use gpui::{App, Task, WeakEntity, Window};
use language::{BufferSnapshot, LspAdapterDelegate}; use language::{BufferSnapshot, LspAdapterDelegate};
use std::sync::{atomic::AtomicBool, Arc}; use std::sync::{atomic::AtomicBool, Arc};
use text::OffsetRangeExt; use text::OffsetRangeExt;
@ -40,8 +40,9 @@ impl SlashCommand for DeltaSlashCommand {
self: Arc<Self>, self: Arc<Self>,
_arguments: &[String], _arguments: &[String],
_cancellation_flag: Arc<AtomicBool>, _cancellation_flag: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>, _workspace: Option<WeakEntity<Workspace>>,
_cx: &mut WindowContext, _window: &mut Window,
_cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> { ) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Err(anyhow!("this command does not require argument"))) Task::ready(Err(anyhow!("this command does not require argument")))
} }
@ -51,9 +52,10 @@ impl SlashCommand for DeltaSlashCommand {
_arguments: &[String], _arguments: &[String],
context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>], context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
context_buffer: BufferSnapshot, context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
delegate: Option<Arc<dyn LspAdapterDelegate>>, delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> { ) -> Task<SlashCommandResult> {
let mut paths = HashSet::default(); let mut paths = HashSet::default();
let mut file_command_old_outputs = Vec::new(); let mut file_command_old_outputs = Vec::new();
@ -77,6 +79,7 @@ impl SlashCommand for DeltaSlashCommand {
context_buffer.clone(), context_buffer.clone(),
workspace.clone(), workspace.clone(),
delegate.clone(), delegate.clone(),
window,
cx, cx,
)); ));
} }

View file

@ -4,7 +4,7 @@ use assistant_slash_command::{
SlashCommandResult, SlashCommandResult,
}; };
use fuzzy::{PathMatch, StringMatchCandidate}; use fuzzy::{PathMatch, StringMatchCandidate};
use gpui::{AppContext, Model, Task, View, WeakView}; use gpui::{App, Entity, Task, WeakEntity};
use language::{ use language::{
Anchor, BufferSnapshot, DiagnosticEntry, DiagnosticSeverity, LspAdapterDelegate, Anchor, BufferSnapshot, DiagnosticEntry, DiagnosticSeverity, LspAdapterDelegate,
OffsetRangeExt, ToOffset, OffsetRangeExt, ToOffset,
@ -30,8 +30,8 @@ impl DiagnosticsSlashCommand {
&self, &self,
query: String, query: String,
cancellation_flag: Arc<AtomicBool>, cancellation_flag: Arc<AtomicBool>,
workspace: &View<Workspace>, workspace: &Entity<Workspace>,
cx: &mut AppContext, cx: &mut App,
) -> Task<Vec<PathMatch>> { ) -> Task<Vec<PathMatch>> {
if query.is_empty() { if query.is_empty() {
let workspace = workspace.read(cx); let workspace = workspace.read(cx);
@ -90,7 +90,7 @@ impl SlashCommand for DiagnosticsSlashCommand {
"diagnostics".into() "diagnostics".into()
} }
fn label(&self, cx: &AppContext) -> language::CodeLabel { fn label(&self, cx: &App) -> language::CodeLabel {
create_label_for_command("diagnostics", &[INCLUDE_WARNINGS_ARGUMENT], cx) create_label_for_command("diagnostics", &[INCLUDE_WARNINGS_ARGUMENT], cx)
} }
@ -118,8 +118,9 @@ impl SlashCommand for DiagnosticsSlashCommand {
self: Arc<Self>, self: Arc<Self>,
arguments: &[String], arguments: &[String],
cancellation_flag: Arc<AtomicBool>, cancellation_flag: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>, workspace: Option<WeakEntity<Workspace>>,
cx: &mut WindowContext, _: &mut Window,
cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> { ) -> Task<Result<Vec<ArgumentCompletion>>> {
let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else { let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else {
return Task::ready(Err(anyhow!("workspace was dropped"))); return Task::ready(Err(anyhow!("workspace was dropped")));
@ -172,9 +173,10 @@ impl SlashCommand for DiagnosticsSlashCommand {
arguments: &[String], arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>], _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot, _context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>, _delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> { ) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else { let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow!("workspace was dropped"))); return Task::ready(Err(anyhow!("workspace was dropped")));
@ -184,7 +186,7 @@ impl SlashCommand for DiagnosticsSlashCommand {
let task = collect_diagnostics(workspace.read(cx).project().clone(), options, cx); let task = collect_diagnostics(workspace.read(cx).project().clone(), options, cx);
cx.spawn(move |_| async move { window.spawn(cx, move |_| async move {
task.await? task.await?
.map(|output| output.to_event_stream()) .map(|output| output.to_event_stream())
.ok_or_else(|| anyhow!("No diagnostics found")) .ok_or_else(|| anyhow!("No diagnostics found"))
@ -223,9 +225,9 @@ impl Options {
} }
fn collect_diagnostics( fn collect_diagnostics(
project: Model<Project>, project: Entity<Project>,
options: Options, options: Options,
cx: &mut AppContext, cx: &mut App,
) -> Task<Result<Option<SlashCommandOutput>>> { ) -> Task<Result<Option<SlashCommandOutput>>> {
let error_source = if let Some(path_matcher) = &options.path_matcher { let error_source = if let Some(path_matcher) = &options.path_matcher {
debug_assert_eq!(path_matcher.sources().len(), 1); debug_assert_eq!(path_matcher.sources().len(), 1);

View file

@ -8,7 +8,7 @@ use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection, ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult, SlashCommandResult,
}; };
use gpui::{AppContext, BackgroundExecutor, Model, Task, WeakView}; use gpui::{App, BackgroundExecutor, Entity, Task, WeakEntity};
use indexed_docs::{ use indexed_docs::{
DocsDotRsProvider, IndexedDocsRegistry, IndexedDocsStore, LocalRustdocProvider, PackageName, DocsDotRsProvider, IndexedDocsRegistry, IndexedDocsStore, LocalRustdocProvider, PackageName,
ProviderId, ProviderId,
@ -24,7 +24,7 @@ pub struct DocsSlashCommand;
impl DocsSlashCommand { impl DocsSlashCommand {
pub const NAME: &'static str = "docs"; pub const NAME: &'static str = "docs";
fn path_to_cargo_toml(project: Model<Project>, cx: &mut AppContext) -> Option<Arc<Path>> { fn path_to_cargo_toml(project: Entity<Project>, cx: &mut App) -> Option<Arc<Path>> {
let worktree = project.read(cx).worktrees(cx).next()?; let worktree = project.read(cx).worktrees(cx).next()?;
let worktree = worktree.read(cx); let worktree = worktree.read(cx);
let entry = worktree.entry_for_path("Cargo.toml")?; let entry = worktree.entry_for_path("Cargo.toml")?;
@ -43,8 +43,8 @@ impl DocsSlashCommand {
/// access the workspace so we can read the project. /// access the workspace so we can read the project.
fn ensure_rust_doc_providers_are_registered( fn ensure_rust_doc_providers_are_registered(
&self, &self,
workspace: Option<WeakView<Workspace>>, workspace: Option<WeakEntity<Workspace>>,
cx: &mut AppContext, cx: &mut App,
) { ) {
let indexed_docs_registry = IndexedDocsRegistry::global(cx); let indexed_docs_registry = IndexedDocsRegistry::global(cx);
if indexed_docs_registry if indexed_docs_registry
@ -164,8 +164,9 @@ impl SlashCommand for DocsSlashCommand {
self: Arc<Self>, self: Arc<Self>,
arguments: &[String], arguments: &[String],
_cancel: Arc<AtomicBool>, _cancel: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>, workspace: Option<WeakEntity<Workspace>>,
cx: &mut WindowContext, _: &mut Window,
cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> { ) -> Task<Result<Vec<ArgumentCompletion>>> {
self.ensure_rust_doc_providers_are_registered(workspace, cx); self.ensure_rust_doc_providers_are_registered(workspace, cx);
@ -272,9 +273,10 @@ impl SlashCommand for DocsSlashCommand {
arguments: &[String], arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>], _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot, _context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>, _workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>, _delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext, _: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> { ) -> Task<SlashCommandResult> {
if arguments.is_empty() { if arguments.is_empty() {
return Task::ready(Err(anyhow!("missing an argument"))); return Task::ready(Err(anyhow!("missing an argument")));

View file

@ -9,7 +9,7 @@ use assistant_slash_command::{
SlashCommandResult, SlashCommandResult,
}; };
use futures::AsyncReadExt; use futures::AsyncReadExt;
use gpui::{Task, WeakView}; use gpui::{Task, WeakEntity};
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler}; use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl}; use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use language::{BufferSnapshot, LspAdapterDelegate}; use language::{BufferSnapshot, LspAdapterDelegate};
@ -124,8 +124,9 @@ impl SlashCommand for FetchSlashCommand {
self: Arc<Self>, self: Arc<Self>,
_arguments: &[String], _arguments: &[String],
_cancel: Arc<AtomicBool>, _cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>, _workspace: Option<WeakEntity<Workspace>>,
_cx: &mut WindowContext, _window: &mut Window,
_cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> { ) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new())) Task::ready(Ok(Vec::new()))
} }
@ -135,9 +136,10 @@ impl SlashCommand for FetchSlashCommand {
arguments: &[String], arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>], _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot, _context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>, _delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext, _: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> { ) -> Task<SlashCommandResult> {
let Some(argument) = arguments.first() else { let Some(argument) = arguments.first() else {
return Task::ready(Err(anyhow!("missing URL"))); return Task::ready(Err(anyhow!("missing URL")));

View file

@ -6,7 +6,7 @@ use assistant_slash_command::{
use futures::channel::mpsc; use futures::channel::mpsc;
use futures::Stream; use futures::Stream;
use fuzzy::PathMatch; use fuzzy::PathMatch;
use gpui::{AppContext, Model, Task, View, WeakView}; use gpui::{App, Entity, Task, WeakEntity};
use language::{BufferSnapshot, CodeLabel, HighlightId, LineEnding, LspAdapterDelegate}; use language::{BufferSnapshot, CodeLabel, HighlightId, LineEnding, LspAdapterDelegate};
use project::{PathMatchCandidateSet, Project}; use project::{PathMatchCandidateSet, Project};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -28,8 +28,8 @@ impl FileSlashCommand {
&self, &self,
query: String, query: String,
cancellation_flag: Arc<AtomicBool>, cancellation_flag: Arc<AtomicBool>,
workspace: &View<Workspace>, workspace: &Entity<Workspace>,
cx: &mut AppContext, cx: &mut App,
) -> Task<Vec<PathMatch>> { ) -> Task<Vec<PathMatch>> {
if query.is_empty() { if query.is_empty() {
let workspace = workspace.read(cx); let workspace = workspace.read(cx);
@ -134,8 +134,9 @@ impl SlashCommand for FileSlashCommand {
self: Arc<Self>, self: Arc<Self>,
arguments: &[String], arguments: &[String],
cancellation_flag: Arc<AtomicBool>, cancellation_flag: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>, workspace: Option<WeakEntity<Workspace>>,
cx: &mut WindowContext, _: &mut Window,
cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> { ) -> Task<Result<Vec<ArgumentCompletion>>> {
let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else { let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else {
return Task::ready(Err(anyhow!("workspace was dropped"))); return Task::ready(Err(anyhow!("workspace was dropped")));
@ -187,9 +188,10 @@ impl SlashCommand for FileSlashCommand {
arguments: &[String], arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>], _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot, _context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>, _delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext, _: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> { ) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else { let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow!("workspace was dropped"))); return Task::ready(Err(anyhow!("workspace was dropped")));
@ -209,9 +211,9 @@ impl SlashCommand for FileSlashCommand {
} }
fn collect_files( fn collect_files(
project: Model<Project>, project: Entity<Project>,
glob_inputs: &[String], glob_inputs: &[String],
cx: &mut AppContext, cx: &mut App,
) -> impl Stream<Item = Result<SlashCommandEvent>> { ) -> impl Stream<Item = Result<SlashCommandEvent>> {
let Ok(matchers) = glob_inputs let Ok(matchers) = glob_inputs
.into_iter() .into_iter()

View file

@ -7,7 +7,7 @@ use assistant_slash_command::{
SlashCommandResult, SlashCommandResult,
}; };
use chrono::Local; use chrono::Local;
use gpui::{Task, WeakView}; use gpui::{Task, WeakEntity};
use language::{BufferSnapshot, LspAdapterDelegate}; use language::{BufferSnapshot, LspAdapterDelegate};
use ui::prelude::*; use ui::prelude::*;
use workspace::Workspace; use workspace::Workspace;
@ -35,8 +35,9 @@ impl SlashCommand for NowSlashCommand {
self: Arc<Self>, self: Arc<Self>,
_arguments: &[String], _arguments: &[String],
_cancel: Arc<AtomicBool>, _cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>, _workspace: Option<WeakEntity<Workspace>>,
_cx: &mut WindowContext, _window: &mut Window,
_cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> { ) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new())) Task::ready(Ok(Vec::new()))
} }
@ -46,9 +47,10 @@ impl SlashCommand for NowSlashCommand {
_arguments: &[String], _arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>], _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot, _context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>, _workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>, _delegate: Option<Arc<dyn LspAdapterDelegate>>,
_cx: &mut WindowContext, _window: &mut Window,
_cx: &mut App,
) -> Task<SlashCommandResult> { ) -> Task<SlashCommandResult> {
let now = Local::now(); let now = Local::now();
let text = format!("Today is {now}.", now = now.to_rfc2822()); let text = format!("Today is {now}.", now = now.to_rfc2822());

View file

@ -10,7 +10,7 @@ use assistant_slash_command::{
SlashCommandResult, SlashCommandResult,
}; };
use feature_flags::FeatureFlag; use feature_flags::FeatureFlag;
use gpui::{AppContext, Task, WeakView, WindowContext}; use gpui::{App, Task, WeakEntity};
use language::{Anchor, CodeLabel, LspAdapterDelegate}; use language::{Anchor, CodeLabel, LspAdapterDelegate};
use language_model::{LanguageModelRegistry, LanguageModelTool}; use language_model::{LanguageModelRegistry, LanguageModelTool};
use prompt_library::PromptBuilder; use prompt_library::PromptBuilder;
@ -43,7 +43,7 @@ impl SlashCommand for ProjectSlashCommand {
"project".into() "project".into()
} }
fn label(&self, cx: &AppContext) -> CodeLabel { fn label(&self, cx: &App) -> CodeLabel {
create_label_for_command("project", &[], cx) create_label_for_command("project", &[], cx)
} }
@ -67,8 +67,9 @@ impl SlashCommand for ProjectSlashCommand {
self: Arc<Self>, self: Arc<Self>,
_arguments: &[String], _arguments: &[String],
_cancel: Arc<AtomicBool>, _cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>, _workspace: Option<WeakEntity<Workspace>>,
_cx: &mut WindowContext, _window: &mut Window,
_cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> { ) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new())) Task::ready(Ok(Vec::new()))
} }
@ -78,9 +79,10 @@ impl SlashCommand for ProjectSlashCommand {
_arguments: &[String], _arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<Anchor>], _context_slash_command_output_sections: &[SlashCommandOutputSection<Anchor>],
context_buffer: language::BufferSnapshot, context_buffer: language::BufferSnapshot,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>, _delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> { ) -> Task<SlashCommandResult> {
let model_registry = LanguageModelRegistry::read_global(cx); let model_registry = LanguageModelRegistry::read_global(cx);
let current_model = model_registry.active_model(); let current_model = model_registry.active_model();
@ -97,7 +99,7 @@ impl SlashCommand for ProjectSlashCommand {
return Task::ready(Err(anyhow::anyhow!("no project indexer"))); return Task::ready(Err(anyhow::anyhow!("no project indexer")));
}; };
cx.spawn(|mut cx| async move { window.spawn(cx, |mut cx| async move {
let current_model = current_model.ok_or_else(|| anyhow!("no model selected"))?; let current_model = current_model.ok_or_else(|| anyhow!("no model selected"))?;
let prompt = let prompt =

View file

@ -1,9 +1,9 @@
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context as _, Result};
use assistant_slash_command::{ use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection, ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult, SlashCommandResult,
}; };
use gpui::{Task, WeakView}; use gpui::{Task, WeakEntity};
use language::{BufferSnapshot, LspAdapterDelegate}; use language::{BufferSnapshot, LspAdapterDelegate};
use prompt_library::PromptStore; use prompt_library::PromptStore;
use std::sync::{atomic::AtomicBool, Arc}; use std::sync::{atomic::AtomicBool, Arc};
@ -37,8 +37,9 @@ impl SlashCommand for PromptSlashCommand {
self: Arc<Self>, self: Arc<Self>,
arguments: &[String], arguments: &[String],
_cancellation_flag: Arc<AtomicBool>, _cancellation_flag: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>, _workspace: Option<WeakEntity<Workspace>>,
cx: &mut WindowContext, _: &mut Window,
cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> { ) -> Task<Result<Vec<ArgumentCompletion>>> {
let store = PromptStore::global(cx); let store = PromptStore::global(cx);
let query = arguments.to_owned().join(" "); let query = arguments.to_owned().join(" ");
@ -64,9 +65,10 @@ impl SlashCommand for PromptSlashCommand {
arguments: &[String], arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>], _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot, _context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>, _workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>, _delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext, _: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> { ) -> Task<SlashCommandResult> {
let title = arguments.to_owned().join(" "); let title = arguments.to_owned().join(" ");
if title.trim().is_empty() { if title.trim().is_empty() {

View file

@ -4,7 +4,7 @@ use assistant_slash_command::{
SlashCommandResult, SlashCommandResult,
}; };
use feature_flags::FeatureFlag; use feature_flags::FeatureFlag;
use gpui::{AppContext, Task, WeakView}; use gpui::{App, Task, WeakEntity};
use language::{CodeLabel, LspAdapterDelegate}; use language::{CodeLabel, LspAdapterDelegate};
use semantic_index::{LoadedSearchResult, SemanticDb}; use semantic_index::{LoadedSearchResult, SemanticDb};
use std::{ use std::{
@ -34,7 +34,7 @@ impl SlashCommand for SearchSlashCommand {
"search".into() "search".into()
} }
fn label(&self, cx: &AppContext) -> CodeLabel { fn label(&self, cx: &App) -> CodeLabel {
create_label_for_command("search", &["--n"], cx) create_label_for_command("search", &["--n"], cx)
} }
@ -58,8 +58,9 @@ impl SlashCommand for SearchSlashCommand {
self: Arc<Self>, self: Arc<Self>,
_arguments: &[String], _arguments: &[String],
_cancel: Arc<AtomicBool>, _cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>, _workspace: Option<WeakEntity<Workspace>>,
_cx: &mut WindowContext, _window: &mut Window,
_cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> { ) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new())) Task::ready(Ok(Vec::new()))
} }
@ -69,9 +70,10 @@ impl SlashCommand for SearchSlashCommand {
arguments: &[String], arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>], _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: language::BufferSnapshot, _context_buffer: language::BufferSnapshot,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>, _delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> { ) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else { let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow::anyhow!("workspace was dropped"))); return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
@ -107,7 +109,7 @@ impl SlashCommand for SearchSlashCommand {
return Task::ready(Err(anyhow::anyhow!("no project indexer"))); return Task::ready(Err(anyhow::anyhow!("no project indexer")));
}; };
cx.spawn(|cx| async move { window.spawn(cx, |cx| async move {
let results = project_index let results = project_index
.read_with(&cx, |project_index, cx| { .read_with(&cx, |project_index, cx| {
project_index.search(vec![query.clone()], limit.unwrap_or(5), cx) project_index.search(vec![query.clone()], limit.unwrap_or(5), cx)

View file

@ -5,8 +5,7 @@ use assistant_slash_command::{
}; };
use editor::Editor; use editor::Editor;
use futures::StreamExt; use futures::StreamExt;
use gpui::{AppContext, Task, WeakView}; use gpui::{App, Context, SharedString, Task, WeakEntity, Window};
use gpui::{SharedString, ViewContext, WindowContext};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate}; use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
use std::sync::Arc; use std::sync::Arc;
@ -22,7 +21,7 @@ impl SlashCommand for SelectionCommand {
"selection".into() "selection".into()
} }
fn label(&self, _cx: &AppContext) -> CodeLabel { fn label(&self, _cx: &App) -> CodeLabel {
CodeLabel::plain(self.name(), None) CodeLabel::plain(self.name(), None)
} }
@ -50,8 +49,9 @@ impl SlashCommand for SelectionCommand {
self: Arc<Self>, self: Arc<Self>,
_arguments: &[String], _arguments: &[String],
_cancel: Arc<AtomicBool>, _cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>, _workspace: Option<WeakEntity<Workspace>>,
_cx: &mut WindowContext, _window: &mut Window,
_cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> { ) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Err(anyhow!("this command does not require argument"))) Task::ready(Err(anyhow!("this command does not require argument")))
} }
@ -61,9 +61,10 @@ impl SlashCommand for SelectionCommand {
_arguments: &[String], _arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>], _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot, _context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>, _delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext, _window: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> { ) -> Task<SlashCommandResult> {
let mut events = vec![]; let mut events = vec![];
@ -102,7 +103,7 @@ impl SlashCommand for SelectionCommand {
pub fn selections_creases( pub fn selections_creases(
workspace: &mut workspace::Workspace, workspace: &mut workspace::Workspace,
cx: &mut ViewContext<Workspace>, cx: &mut Context<Workspace>,
) -> Option<Vec<(String, String)>> { ) -> Option<Vec<(String, String)>> {
let editor = workspace let editor = workspace
.active_item(cx) .active_item(cx)

View file

@ -9,7 +9,7 @@ use assistant_slash_command::{
}; };
use feature_flags::FeatureFlag; use feature_flags::FeatureFlag;
use futures::channel::mpsc; use futures::channel::mpsc;
use gpui::{Task, WeakView}; use gpui::{Task, WeakEntity};
use language::{BufferSnapshot, LspAdapterDelegate}; use language::{BufferSnapshot, LspAdapterDelegate};
use smol::stream::StreamExt; use smol::stream::StreamExt;
use smol::Timer; use smol::Timer;
@ -45,8 +45,9 @@ impl SlashCommand for StreamingExampleSlashCommand {
self: Arc<Self>, self: Arc<Self>,
_arguments: &[String], _arguments: &[String],
_cancel: Arc<AtomicBool>, _cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>, _workspace: Option<WeakEntity<Workspace>>,
_cx: &mut WindowContext, _window: &mut Window,
_cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> { ) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new())) Task::ready(Ok(Vec::new()))
} }
@ -56,9 +57,10 @@ impl SlashCommand for StreamingExampleSlashCommand {
_arguments: &[String], _arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>], _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot, _context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>, _workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>, _delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext, _: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> { ) -> Task<SlashCommandResult> {
let (events_tx, events_rx) = mpsc::unbounded(); let (events_tx, events_rx) = mpsc::unbounded();
cx.background_executor() cx.background_executor()

View file

@ -4,11 +4,11 @@ use assistant_slash_command::{
SlashCommandResult, SlashCommandResult,
}; };
use editor::Editor; use editor::Editor;
use gpui::{Task, WeakView}; use gpui::{Task, WeakEntity};
use language::{BufferSnapshot, LspAdapterDelegate}; use language::{BufferSnapshot, LspAdapterDelegate};
use std::sync::Arc; use std::sync::Arc;
use std::{path::Path, sync::atomic::AtomicBool}; use std::{path::Path, sync::atomic::AtomicBool};
use ui::{IconName, WindowContext}; use ui::{App, IconName, Window};
use workspace::Workspace; use workspace::Workspace;
pub struct OutlineSlashCommand; pub struct OutlineSlashCommand;
@ -34,8 +34,9 @@ impl SlashCommand for OutlineSlashCommand {
self: Arc<Self>, self: Arc<Self>,
_arguments: &[String], _arguments: &[String],
_cancel: Arc<AtomicBool>, _cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>, _workspace: Option<WeakEntity<Workspace>>,
_cx: &mut WindowContext, _window: &mut Window,
_cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> { ) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Err(anyhow!("this command does not require argument"))) Task::ready(Err(anyhow!("this command does not require argument")))
} }
@ -49,9 +50,10 @@ impl SlashCommand for OutlineSlashCommand {
_arguments: &[String], _arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>], _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot, _context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>, _delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext, _: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> { ) -> Task<SlashCommandResult> {
let output = workspace.update(cx, |workspace, cx| { let output = workspace.update(cx, |workspace, cx| {
let Some(active_item) = workspace.active_item(cx) else { let Some(active_item) = workspace.active_item(cx) else {

View file

@ -1,4 +1,4 @@
use anyhow::{Context, Result}; use anyhow::{Context as _, Result};
use assistant_slash_command::{ use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection, ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult, SlashCommandResult,
@ -6,13 +6,13 @@ use assistant_slash_command::{
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use editor::Editor; use editor::Editor;
use futures::future::join_all; use futures::future::join_all;
use gpui::{Entity, Task, WeakView}; use gpui::{Task, WeakEntity};
use language::{BufferSnapshot, CodeLabel, HighlightId, LspAdapterDelegate}; use language::{BufferSnapshot, CodeLabel, HighlightId, LspAdapterDelegate};
use std::{ use std::{
path::PathBuf, path::PathBuf,
sync::{atomic::AtomicBool, Arc}, sync::{atomic::AtomicBool, Arc},
}; };
use ui::{prelude::*, ActiveTheme, WindowContext}; use ui::{prelude::*, ActiveTheme, App, Window};
use util::ResultExt; use util::ResultExt;
use workspace::Workspace; use workspace::Workspace;
@ -51,8 +51,9 @@ impl SlashCommand for TabSlashCommand {
self: Arc<Self>, self: Arc<Self>,
arguments: &[String], arguments: &[String],
cancel: Arc<AtomicBool>, cancel: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>, workspace: Option<WeakEntity<Workspace>>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> { ) -> Task<Result<Vec<ArgumentCompletion>>> {
let mut has_all_tabs_completion_item = false; let mut has_all_tabs_completion_item = false;
let argument_set = arguments let argument_set = arguments
@ -82,10 +83,10 @@ impl SlashCommand for TabSlashCommand {
}); });
let current_query = arguments.last().cloned().unwrap_or_default(); let current_query = arguments.last().cloned().unwrap_or_default();
let tab_items_search = let tab_items_search =
tab_items_for_queries(workspace, &[current_query], cancel, false, cx); tab_items_for_queries(workspace, &[current_query], cancel, false, window, cx);
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId); let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
cx.spawn(|_| async move { window.spawn(cx, |_| async move {
let tab_items = tab_items_search.await?; let tab_items = tab_items_search.await?;
let run_command = tab_items.len() == 1; let run_command = tab_items.len() == 1;
let tab_completion_items = tab_items.into_iter().filter_map(|(path, ..)| { let tab_completion_items = tab_items.into_iter().filter_map(|(path, ..)| {
@ -137,15 +138,17 @@ impl SlashCommand for TabSlashCommand {
arguments: &[String], arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>], _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot, _context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>, _delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> { ) -> Task<SlashCommandResult> {
let tab_items_search = tab_items_for_queries( let tab_items_search = tab_items_for_queries(
Some(workspace), Some(workspace),
arguments, arguments,
Arc::new(AtomicBool::new(false)), Arc::new(AtomicBool::new(false)),
true, true,
window,
cx, cx,
); );
@ -160,15 +163,16 @@ impl SlashCommand for TabSlashCommand {
} }
fn tab_items_for_queries( fn tab_items_for_queries(
workspace: Option<WeakView<Workspace>>, workspace: Option<WeakEntity<Workspace>>,
queries: &[String], queries: &[String],
cancel: Arc<AtomicBool>, cancel: Arc<AtomicBool>,
strict_match: bool, strict_match: bool,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Task<anyhow::Result<Vec<(Option<PathBuf>, BufferSnapshot, usize)>>> { ) -> Task<anyhow::Result<Vec<(Option<PathBuf>, BufferSnapshot, usize)>>> {
let empty_query = queries.is_empty() || queries.iter().all(|query| query.trim().is_empty()); let empty_query = queries.is_empty() || queries.iter().all(|query| query.trim().is_empty());
let queries = queries.to_owned(); let queries = queries.to_owned();
cx.spawn(|mut cx| async move { window.spawn(cx, |mut cx| async move {
let mut open_buffers = let mut open_buffers =
workspace workspace
.context("no workspace")? .context("no workspace")?
@ -281,7 +285,7 @@ fn tab_items_for_queries(
fn active_item_buffer( fn active_item_buffer(
workspace: &mut Workspace, workspace: &mut Workspace,
cx: &mut ViewContext<Workspace>, cx: &mut Context<Workspace>,
) -> anyhow::Result<BufferSnapshot> { ) -> anyhow::Result<BufferSnapshot> {
let active_editor = workspace let active_editor = workspace
.active_item(cx) .active_item(cx)

View file

@ -6,7 +6,7 @@ use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection, ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult, SlashCommandResult,
}; };
use gpui::{AppContext, Task, View, WeakView}; use gpui::{App, Entity, Task, WeakEntity};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate}; use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
use terminal_view::{terminal_panel::TerminalPanel, TerminalView}; use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
use ui::prelude::*; use ui::prelude::*;
@ -25,7 +25,7 @@ impl SlashCommand for TerminalSlashCommand {
"terminal".into() "terminal".into()
} }
fn label(&self, cx: &AppContext) -> CodeLabel { fn label(&self, cx: &App) -> CodeLabel {
create_label_for_command("terminal", &[LINE_COUNT_ARG], cx) create_label_for_command("terminal", &[LINE_COUNT_ARG], cx)
} }
@ -53,8 +53,9 @@ impl SlashCommand for TerminalSlashCommand {
self: Arc<Self>, self: Arc<Self>,
_arguments: &[String], _arguments: &[String],
_cancel: Arc<AtomicBool>, _cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>, _workspace: Option<WeakEntity<Workspace>>,
_cx: &mut WindowContext, _window: &mut Window,
_cx: &mut App,
) -> Task<Result<Vec<ArgumentCompletion>>> { ) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new())) Task::ready(Ok(Vec::new()))
} }
@ -64,9 +65,10 @@ impl SlashCommand for TerminalSlashCommand {
arguments: &[String], arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>], _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot, _context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>, _delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext, _: &mut Window,
cx: &mut App,
) -> Task<SlashCommandResult> { ) -> Task<SlashCommandResult> {
let Some(workspace) = workspace.upgrade() else { let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow::anyhow!("workspace was dropped"))); return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
@ -107,9 +109,9 @@ impl SlashCommand for TerminalSlashCommand {
} }
fn resolve_active_terminal( fn resolve_active_terminal(
workspace: &View<Workspace>, workspace: &Entity<Workspace>,
cx: &WindowContext, cx: &mut App,
) -> Option<View<TerminalView>> { ) -> Option<Entity<TerminalView>> {
if let Some(terminal_view) = workspace if let Some(terminal_view) = workspace
.read(cx) .read(cx)
.active_item(cx) .active_item(cx)

View file

@ -4,13 +4,13 @@ mod tool_working_set;
use std::sync::Arc; use std::sync::Arc;
use anyhow::Result; use anyhow::Result;
use gpui::{AppContext, Task, WeakView, WindowContext}; use gpui::{App, Task, WeakEntity, Window};
use workspace::Workspace; use workspace::Workspace;
pub use crate::tool_registry::*; pub use crate::tool_registry::*;
pub use crate::tool_working_set::*; pub use crate::tool_working_set::*;
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut App) {
ToolRegistry::default_global(cx); ToolRegistry::default_global(cx);
} }
@ -31,7 +31,8 @@ pub trait Tool: 'static + Send + Sync {
fn run( fn run(
self: Arc<Self>, self: Arc<Self>,
input: serde_json::Value, input: serde_json::Value,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
cx: &mut WindowContext, window: &mut Window,
cx: &mut App,
) -> Task<Result<String>>; ) -> Task<Result<String>>;
} }

View file

@ -3,7 +3,7 @@ use std::sync::Arc;
use collections::HashMap; use collections::HashMap;
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use gpui::Global; use gpui::Global;
use gpui::{AppContext, ReadGlobal}; use gpui::{App, ReadGlobal};
use parking_lot::RwLock; use parking_lot::RwLock;
use crate::Tool; use crate::Tool;
@ -25,14 +25,14 @@ pub struct ToolRegistry {
impl ToolRegistry { impl ToolRegistry {
/// Returns the global [`ToolRegistry`]. /// Returns the global [`ToolRegistry`].
pub fn global(cx: &AppContext) -> Arc<Self> { pub fn global(cx: &App) -> Arc<Self> {
GlobalToolRegistry::global(cx).0.clone() GlobalToolRegistry::global(cx).0.clone()
} }
/// Returns the global [`ToolRegistry`]. /// Returns the global [`ToolRegistry`].
/// ///
/// Inserts a default [`ToolRegistry`] if one does not yet exist. /// Inserts a default [`ToolRegistry`] if one does not yet exist.
pub fn default_global(cx: &mut AppContext) -> Arc<Self> { pub fn default_global(cx: &mut App) -> Arc<Self> {
cx.default_global::<GlobalToolRegistry>().0.clone() cx.default_global::<GlobalToolRegistry>().0.clone()
} }

View file

@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use collections::HashMap; use collections::HashMap;
use gpui::AppContext; use gpui::App;
use parking_lot::Mutex; use parking_lot::Mutex;
use crate::{Tool, ToolRegistry}; use crate::{Tool, ToolRegistry};
@ -23,7 +23,7 @@ struct WorkingSetState {
} }
impl ToolWorkingSet { impl ToolWorkingSet {
pub fn tool(&self, name: &str, cx: &AppContext) -> Option<Arc<dyn Tool>> { pub fn tool(&self, name: &str, cx: &App) -> Option<Arc<dyn Tool>> {
self.state self.state
.lock() .lock()
.context_server_tools_by_name .context_server_tools_by_name
@ -32,7 +32,7 @@ impl ToolWorkingSet {
.or_else(|| ToolRegistry::global(cx).tool(name)) .or_else(|| ToolRegistry::global(cx).tool(name))
} }
pub fn tools(&self, cx: &AppContext) -> Vec<Arc<dyn Tool>> { pub fn tools(&self, cx: &App) -> Vec<Arc<dyn Tool>> {
let mut tools = ToolRegistry::global(cx).tools(); let mut tools = ToolRegistry::global(cx).tools();
tools.extend( tools.extend(
self.state self.state

View file

@ -1,11 +1,11 @@
mod now_tool; mod now_tool;
use assistant_tool::ToolRegistry; use assistant_tool::ToolRegistry;
use gpui::AppContext; use gpui::App;
use crate::now_tool::NowTool; use crate::now_tool::NowTool;
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut App) {
assistant_tool::init(cx); assistant_tool::init(cx);
let registry = ToolRegistry::global(cx); let registry = ToolRegistry::global(cx);

View file

@ -3,7 +3,7 @@ use std::sync::Arc;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use assistant_tool::Tool; use assistant_tool::Tool;
use chrono::{Local, Utc}; use chrono::{Local, Utc};
use gpui::{Task, WeakView, WindowContext}; use gpui::{App, Task, WeakEntity, Window};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -41,8 +41,9 @@ impl Tool for NowTool {
fn run( fn run(
self: Arc<Self>, self: Arc<Self>,
input: serde_json::Value, input: serde_json::Value,
_workspace: WeakView<workspace::Workspace>, _workspace: WeakEntity<workspace::Workspace>,
_cx: &mut WindowContext, _window: &mut Window,
_cx: &mut App,
) -> Task<Result<String>> { ) -> Task<Result<String>> {
let input: FileToolInput = match serde_json::from_value(input) { let input: FileToolInput = match serde_json::from_value(input) {
Ok(input) => input, Ok(input) => input,

View file

@ -2,7 +2,7 @@ use std::{io::Cursor, sync::Arc};
use anyhow::Result; use anyhow::Result;
use collections::HashMap; use collections::HashMap;
use gpui::{AppContext, AssetSource, Global}; use gpui::{App, AssetSource, Global};
use rodio::{ use rodio::{
source::{Buffered, SamplesConverter}, source::{Buffered, SamplesConverter},
Decoder, Source, Decoder, Source,
@ -27,11 +27,11 @@ impl SoundRegistry {
}) })
} }
pub fn global(cx: &AppContext) -> Arc<Self> { pub fn global(cx: &App) -> Arc<Self> {
cx.global::<GlobalSoundRegistry>().0.clone() cx.global::<GlobalSoundRegistry>().0.clone()
} }
pub(crate) fn set_global(source: impl AssetSource, cx: &mut AppContext) { pub(crate) fn set_global(source: impl AssetSource, cx: &mut App) {
cx.set_global(GlobalSoundRegistry(SoundRegistry::new(source))); cx.set_global(GlobalSoundRegistry(SoundRegistry::new(source)));
} }

View file

@ -1,12 +1,12 @@
use assets::SoundRegistry; use assets::SoundRegistry;
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use gpui::{AppContext, AssetSource, BorrowAppContext, Global}; use gpui::{App, AssetSource, BorrowAppContext, Global};
use rodio::{OutputStream, OutputStreamHandle}; use rodio::{OutputStream, OutputStreamHandle};
use util::ResultExt; use util::ResultExt;
mod assets; mod assets;
pub fn init(source: impl AssetSource, cx: &mut AppContext) { pub fn init(source: impl AssetSource, cx: &mut App) {
SoundRegistry::set_global(source, cx); SoundRegistry::set_global(source, cx);
cx.set_global(GlobalAudio(Audio::new())); cx.set_global(GlobalAudio(Audio::new()));
} }
@ -59,7 +59,7 @@ impl Audio {
self.output_handle.as_ref() self.output_handle.as_ref()
} }
pub fn play_sound(sound: Sound, cx: &mut AppContext) { pub fn play_sound(sound: Sound, cx: &mut App) {
if !cx.has_global::<GlobalAudio>() { if !cx.has_global::<GlobalAudio>() {
return; return;
} }
@ -72,7 +72,7 @@ impl Audio {
}); });
} }
pub fn end_call(cx: &mut AppContext) { pub fn end_call(cx: &mut App) {
if !cx.has_global::<GlobalAudio>() { if !cx.has_global::<GlobalAudio>() {
return; return;
} }

View file

@ -1,10 +1,10 @@
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context as _, Result};
use client::{Client, TelemetrySettings}; use client::{Client, TelemetrySettings};
use db::kvp::KEY_VALUE_STORE; use db::kvp::KEY_VALUE_STORE;
use db::RELEASE_CHANNEL; use db::RELEASE_CHANNEL;
use gpui::{ use gpui::{
actions, AppContext, AsyncAppContext, Context as _, Global, Model, ModelContext, actions, App, AppContext as _, AsyncAppContext, Context, Entity, Global, SemanticVersion, Task,
SemanticVersion, Task, WindowContext, Window,
}; };
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl}; use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use paths::remote_servers_dir; use paths::remote_servers_dir;
@ -112,7 +112,7 @@ impl Settings for AutoUpdateSetting {
type FileContent = Option<AutoUpdateSettingContent>; type FileContent = Option<AutoUpdateSettingContent>;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> { fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
let auto_update = [sources.server, sources.release_channel, sources.user] let auto_update = [sources.server, sources.release_channel, sources.user]
.into_iter() .into_iter()
.find_map(|value| value.copied().flatten()) .find_map(|value| value.copied().flatten())
@ -123,24 +123,24 @@ impl Settings for AutoUpdateSetting {
} }
#[derive(Default)] #[derive(Default)]
struct GlobalAutoUpdate(Option<Model<AutoUpdater>>); struct GlobalAutoUpdate(Option<Entity<AutoUpdater>>);
impl Global for GlobalAutoUpdate {} impl Global for GlobalAutoUpdate {}
pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) { pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut App) {
AutoUpdateSetting::register(cx); AutoUpdateSetting::register(cx);
cx.observe_new_views(|workspace: &mut Workspace, _cx| { cx.observe_new(|workspace: &mut Workspace, _window, _cx| {
workspace.register_action(|_, action: &Check, cx| check(action, cx)); workspace.register_action(|_, action: &Check, window, cx| check(action, window, cx));
workspace.register_action(|_, action, cx| { workspace.register_action(|_, action, _, cx| {
view_release_notes(action, cx); view_release_notes(action, cx);
}); });
}) })
.detach(); .detach();
let version = release_channel::AppVersion::global(cx); let version = release_channel::AppVersion::global(cx);
let auto_updater = cx.new_model(|cx| { let auto_updater = cx.new(|cx| {
let updater = AutoUpdater::new(version, http_client); let updater = AutoUpdater::new(version, http_client);
let poll_for_updates = ReleaseChannel::try_global(cx) let poll_for_updates = ReleaseChannel::try_global(cx)
@ -155,7 +155,7 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
.0 .0
.then(|| updater.start_polling(cx)); .then(|| updater.start_polling(cx));
cx.observe_global::<SettingsStore>(move |updater, cx| { cx.observe_global::<SettingsStore>(move |updater: &mut AutoUpdater, cx| {
if AutoUpdateSetting::get_global(cx).0 { if AutoUpdateSetting::get_global(cx).0 {
if update_subscription.is_none() { if update_subscription.is_none() {
update_subscription = Some(updater.start_polling(cx)) update_subscription = Some(updater.start_polling(cx))
@ -172,23 +172,25 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
cx.set_global(GlobalAutoUpdate(Some(auto_updater))); cx.set_global(GlobalAutoUpdate(Some(auto_updater)));
} }
pub fn check(_: &Check, cx: &mut WindowContext) { pub fn check(_: &Check, window: &mut Window, cx: &mut App) {
if let Some(message) = option_env!("ZED_UPDATE_EXPLANATION") { if let Some(message) = option_env!("ZED_UPDATE_EXPLANATION") {
drop(cx.prompt( drop(window.prompt(
gpui::PromptLevel::Info, gpui::PromptLevel::Info,
"Zed was installed via a package manager.", "Zed was installed via a package manager.",
Some(message), Some(message),
&["Ok"], &["Ok"],
cx,
)); ));
return; return;
} }
if let Ok(message) = env::var("ZED_UPDATE_EXPLANATION") { if let Ok(message) = env::var("ZED_UPDATE_EXPLANATION") {
drop(cx.prompt( drop(window.prompt(
gpui::PromptLevel::Info, gpui::PromptLevel::Info,
"Zed was installed via a package manager.", "Zed was installed via a package manager.",
Some(&message), Some(&message),
&["Ok"], &["Ok"],
cx,
)); ));
return; return;
} }
@ -203,16 +205,17 @@ pub fn check(_: &Check, cx: &mut WindowContext) {
if let Some(updater) = AutoUpdater::get(cx) { if let Some(updater) = AutoUpdater::get(cx) {
updater.update(cx, |updater, cx| updater.poll(cx)); updater.update(cx, |updater, cx| updater.poll(cx));
} else { } else {
drop(cx.prompt( drop(window.prompt(
gpui::PromptLevel::Info, gpui::PromptLevel::Info,
"Could not check for updates", "Could not check for updates",
Some("Auto-updates disabled for non-bundled app."), Some("Auto-updates disabled for non-bundled app."),
&["Ok"], &["Ok"],
cx,
)); ));
} }
} }
pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<()> { pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut App) -> Option<()> {
let auto_updater = AutoUpdater::get(cx)?; let auto_updater = AutoUpdater::get(cx)?;
let release_channel = ReleaseChannel::try_global(cx)?; let release_channel = ReleaseChannel::try_global(cx)?;
@ -236,7 +239,7 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<(
} }
impl AutoUpdater { impl AutoUpdater {
pub fn get(cx: &mut AppContext) -> Option<Model<Self>> { pub fn get(cx: &mut App) -> Option<Entity<Self>> {
cx.default_global::<GlobalAutoUpdate>().0.clone() cx.default_global::<GlobalAutoUpdate>().0.clone()
} }
@ -249,7 +252,7 @@ impl AutoUpdater {
} }
} }
pub fn start_polling(&self, cx: &mut ModelContext<Self>) -> Task<Result<()>> { pub fn start_polling(&self, cx: &mut Context<Self>) -> Task<Result<()>> {
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
loop { loop {
this.update(&mut cx, |this, cx| this.poll(cx))?; this.update(&mut cx, |this, cx| this.poll(cx))?;
@ -258,7 +261,7 @@ impl AutoUpdater {
}) })
} }
pub fn poll(&mut self, cx: &mut ModelContext<Self>) { pub fn poll(&mut self, cx: &mut Context<Self>) {
if self.pending_poll.is_some() || self.status.is_updated() { if self.pending_poll.is_some() || self.status.is_updated() {
return; return;
} }
@ -287,7 +290,7 @@ impl AutoUpdater {
self.status.clone() self.status.clone()
} }
pub fn dismiss_error(&mut self, cx: &mut ModelContext<Self>) { pub fn dismiss_error(&mut self, cx: &mut Context<Self>) {
self.status = AutoUpdateStatus::Idle; self.status = AutoUpdateStatus::Idle;
cx.notify(); cx.notify();
} }
@ -371,7 +374,7 @@ impl AutoUpdater {
} }
async fn get_release( async fn get_release(
this: &Model<Self>, this: &Entity<Self>,
asset: &str, asset: &str,
os: &str, os: &str,
arch: &str, arch: &str,
@ -421,7 +424,7 @@ impl AutoUpdater {
} }
async fn get_latest_release( async fn get_latest_release(
this: &Model<Self>, this: &Entity<Self>,
asset: &str, asset: &str,
os: &str, os: &str,
arch: &str, arch: &str,
@ -431,7 +434,7 @@ impl AutoUpdater {
Self::get_release(this, asset, os, arch, None, release_channel, cx).await Self::get_release(this, asset, os, arch, None, release_channel, cx).await
} }
async fn update(this: Model<Self>, mut cx: AsyncAppContext) -> Result<()> { async fn update(this: Entity<Self>, mut cx: AsyncAppContext) -> Result<()> {
let (client, current_version, release_channel) = this.update(&mut cx, |this, cx| { let (client, current_version, release_channel) = this.update(&mut cx, |this, cx| {
this.status = AutoUpdateStatus::Checking; this.status = AutoUpdateStatus::Checking;
cx.notify(); cx.notify();
@ -509,7 +512,7 @@ impl AutoUpdater {
pub fn set_should_show_update_notification( pub fn set_should_show_update_notification(
&self, &self,
should_show: bool, should_show: bool,
cx: &AppContext, cx: &App,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
cx.background_executor().spawn(async move { cx.background_executor().spawn(async move {
if should_show { if should_show {
@ -528,7 +531,7 @@ impl AutoUpdater {
}) })
} }
pub fn should_show_update_notification(&self, cx: &AppContext) -> Task<Result<bool>> { pub fn should_show_update_notification(&self, cx: &App) -> Task<Result<bool>> {
cx.background_executor().spawn(async move { cx.background_executor().spawn(async move {
Ok(KEY_VALUE_STORE Ok(KEY_VALUE_STORE
.read_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)? .read_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)?

View file

@ -2,7 +2,7 @@ mod update_notification;
use auto_update::AutoUpdater; use auto_update::AutoUpdater;
use editor::{Editor, MultiBuffer}; use editor::{Editor, MultiBuffer};
use gpui::{actions, prelude::*, AppContext, SharedString, View, ViewContext}; use gpui::{actions, prelude::*, App, Context, Entity, SharedString, Window};
use http_client::HttpClient; use http_client::HttpClient;
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView}; use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
use release_channel::{AppVersion, ReleaseChannel}; use release_channel::{AppVersion, ReleaseChannel};
@ -16,10 +16,10 @@ use crate::update_notification::UpdateNotification;
actions!(auto_update, [ViewReleaseNotesLocally]); actions!(auto_update, [ViewReleaseNotesLocally]);
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut App) {
cx.observe_new_views(|workspace: &mut Workspace, _cx| { cx.observe_new(|workspace: &mut Workspace, _window, _cx| {
workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, cx| { workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, window, cx| {
view_release_notes_locally(workspace, cx); view_release_notes_locally(workspace, window, cx);
}); });
}) })
.detach(); .detach();
@ -31,7 +31,11 @@ struct ReleaseNotesBody {
release_notes: String, release_notes: String,
} }
fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) { fn view_release_notes_locally(
workspace: &mut Workspace,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
let release_channel = ReleaseChannel::global(cx); let release_channel = ReleaseChannel::global(cx);
let url = match release_channel { let url = match release_channel {
@ -60,8 +64,8 @@ fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Wo
.language_for_name("Markdown"); .language_for_name("Markdown");
workspace workspace
.with_local_workspace(cx, move |_, cx| { .with_local_workspace(window, cx, move |_, window, cx| {
cx.spawn(|workspace, mut cx| async move { cx.spawn_in(window, |workspace, mut cx| async move {
let markdown = markdown.await.log_err(); let markdown = markdown.await.log_err();
let response = client.get(&url, Default::default(), true).await; let response = client.get(&url, Default::default(), true).await;
let Some(mut response) = response.log_err() else { let Some(mut response) = response.log_err() else {
@ -76,7 +80,7 @@ fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Wo
if let Ok(body) = body { if let Ok(body) = body {
workspace workspace
.update(&mut cx, |workspace, cx| { .update_in(&mut cx, |workspace, window, cx| {
let project = workspace.project().clone(); let project = workspace.project().clone();
let buffer = project.update(cx, |project, cx| { let buffer = project.update(cx, |project, cx| {
project.create_local_buffer("", markdown, cx) project.create_local_buffer("", markdown, cx)
@ -86,25 +90,28 @@ fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Wo
}); });
let language_registry = project.read(cx).languages().clone(); let language_registry = project.read(cx).languages().clone();
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx)); let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let tab_description = SharedString::from(body.title.to_string()); let tab_description = SharedString::from(body.title.to_string());
let editor = cx.new_view(|cx| { let editor = cx.new(|cx| {
Editor::for_multibuffer(buffer, Some(project), true, cx) Editor::for_multibuffer(buffer, Some(project), true, window, cx)
}); });
let workspace_handle = workspace.weak_handle(); let workspace_handle = workspace.weak_handle();
let view: View<MarkdownPreviewView> = MarkdownPreviewView::new( let markdown_preview: Entity<MarkdownPreviewView> =
MarkdownPreviewMode::Default, MarkdownPreviewView::new(
editor, MarkdownPreviewMode::Default,
workspace_handle, editor,
language_registry, workspace_handle,
Some(tab_description), language_registry,
cx, Some(tab_description),
); window,
cx,
);
workspace.add_item_to_active_pane( workspace.add_item_to_active_pane(
Box::new(view.clone()), Box::new(markdown_preview.clone()),
None, None,
true, true,
window,
cx, cx,
); );
cx.notify(); cx.notify();
@ -117,12 +124,12 @@ fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Wo
.detach(); .detach();
} }
pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> { pub fn notify_of_any_new_update(window: &mut Window, cx: &mut Context<Workspace>) -> Option<()> {
let updater = AutoUpdater::get(cx)?; let updater = AutoUpdater::get(cx)?;
let version = updater.read(cx).current_version(); let version = updater.read(cx).current_version();
let should_show_notification = updater.read(cx).should_show_update_notification(cx); let should_show_notification = updater.read(cx).should_show_update_notification(cx);
cx.spawn(|workspace, mut cx| async move { cx.spawn_in(window, |workspace, mut cx| async move {
let should_show_notification = should_show_notification.await?; let should_show_notification = should_show_notification.await?;
if should_show_notification { if should_show_notification {
workspace.update(&mut cx, |workspace, cx| { workspace.update(&mut cx, |workspace, cx| {
@ -130,7 +137,7 @@ pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
workspace.show_notification( workspace.show_notification(
NotificationId::unique::<UpdateNotification>(), NotificationId::unique::<UpdateNotification>(),
cx, cx,
|cx| cx.new_view(|_| UpdateNotification::new(version, workspace_handle)), |cx| cx.new(|_| UpdateNotification::new(version, workspace_handle)),
); );
updater.update(cx, |updater, cx| { updater.update(cx, |updater, cx| {
updater updater

View file

@ -1,6 +1,6 @@
use gpui::{ use gpui::{
div, DismissEvent, EventEmitter, InteractiveElement, IntoElement, ParentElement, Render, div, Context, DismissEvent, EventEmitter, InteractiveElement, IntoElement, ParentElement,
SemanticVersion, StatefulInteractiveElement, Styled, ViewContext, WeakView, Render, SemanticVersion, StatefulInteractiveElement, Styled, WeakEntity, Window,
}; };
use menu::Cancel; use menu::Cancel;
use release_channel::ReleaseChannel; use release_channel::ReleaseChannel;
@ -12,13 +12,13 @@ use workspace::{
pub struct UpdateNotification { pub struct UpdateNotification {
version: SemanticVersion, version: SemanticVersion,
workspace: WeakView<Workspace>, workspace: WeakEntity<Workspace>,
} }
impl EventEmitter<DismissEvent> for UpdateNotification {} impl EventEmitter<DismissEvent> for UpdateNotification {}
impl Render for UpdateNotification { impl Render for UpdateNotification {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let app_name = ReleaseChannel::global(cx).display_name(); let app_name = ReleaseChannel::global(cx).display_name();
v_flex() v_flex()
@ -37,7 +37,9 @@ impl Render for UpdateNotification {
.id("cancel") .id("cancel")
.child(Icon::new(IconName::Close)) .child(Icon::new(IconName::Close))
.cursor_pointer() .cursor_pointer()
.on_click(cx.listener(|this, _, cx| this.dismiss(&menu::Cancel, cx))), .on_click(cx.listener(|this, _, window, cx| {
this.dismiss(&menu::Cancel, window, cx)
})),
), ),
) )
.child( .child(
@ -45,24 +47,24 @@ impl Render for UpdateNotification {
.id("notes") .id("notes")
.child(Label::new("View the release notes")) .child(Label::new("View the release notes"))
.cursor_pointer() .cursor_pointer()
.on_click(cx.listener(|this, _, cx| { .on_click(cx.listener(|this, _, window, cx| {
this.workspace this.workspace
.update(cx, |workspace, cx| { .update(cx, |workspace, cx| {
crate::view_release_notes_locally(workspace, cx); crate::view_release_notes_locally(workspace, window, cx);
}) })
.log_err(); .log_err();
this.dismiss(&menu::Cancel, cx) this.dismiss(&menu::Cancel, window, cx)
})), })),
) )
} }
} }
impl UpdateNotification { impl UpdateNotification {
pub fn new(version: SemanticVersion, workspace: WeakView<Workspace>) -> Self { pub fn new(version: SemanticVersion, workspace: WeakEntity<Workspace>) -> Self {
Self { version, workspace } Self { version, workspace }
} }
pub fn dismiss(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) { pub fn dismiss(&mut self, _: &Cancel, _: &mut Window, cx: &mut Context<Self>) {
cx.emit(DismissEvent); cx.emit(DismissEvent);
} }
} }

View file

@ -1,7 +1,7 @@
use editor::Editor; use editor::Editor;
use gpui::{ use gpui::{
Element, EventEmitter, FocusableView, IntoElement, ParentElement, Render, StyledText, Context, Element, EventEmitter, Focusable, IntoElement, ParentElement, Render, StyledText,
Subscription, ViewContext, Subscription, Window,
}; };
use itertools::Itertools; use itertools::Itertools;
use std::cmp; use std::cmp;
@ -37,7 +37,7 @@ impl Breadcrumbs {
impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {} impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
impl Render for Breadcrumbs { impl Render for Breadcrumbs {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
const MAX_SEGMENTS: usize = 12; const MAX_SEGMENTS: usize = 12;
let element = h_flex() let element = h_flex()
@ -72,7 +72,7 @@ impl Render for Breadcrumbs {
} }
let highlighted_segments = segments.into_iter().map(|segment| { let highlighted_segments = segments.into_iter().map(|segment| {
let mut text_style = cx.text_style(); let mut text_style = window.text_style();
if let Some(font) = segment.font { if let Some(font) = segment.font {
text_style.font_family = font.family; text_style.font_family = font.family;
text_style.font_features = font.features; text_style.font_features = font.features;
@ -101,28 +101,30 @@ impl Render for Breadcrumbs {
.style(ButtonStyle::Transparent) .style(ButtonStyle::Transparent)
.on_click({ .on_click({
let editor = editor.clone(); let editor = editor.clone();
move |_, cx| { move |_, window, cx| {
if let Some((editor, callback)) = editor if let Some((editor, callback)) = editor
.upgrade() .upgrade()
.zip(zed_actions::outline::TOGGLE_OUTLINE.get()) .zip(zed_actions::outline::TOGGLE_OUTLINE.get())
{ {
callback(editor.to_any(), cx); callback(editor.to_any(), window, cx);
} }
} }
}) })
.tooltip(move |cx| { .tooltip(move |window, cx| {
if let Some(editor) = editor.upgrade() { if let Some(editor) = editor.upgrade() {
let focus_handle = editor.read(cx).focus_handle(cx); let focus_handle = editor.read(cx).focus_handle(cx);
Tooltip::for_action_in( Tooltip::for_action_in(
"Show Symbol Outline", "Show Symbol Outline",
&zed_actions::outline::ToggleOutline, &zed_actions::outline::ToggleOutline,
&focus_handle, &focus_handle,
window,
cx, cx,
) )
} else { } else {
Tooltip::for_action( Tooltip::for_action(
"Show Symbol Outline", "Show Symbol Outline",
&zed_actions::outline::ToggleOutline, &zed_actions::outline::ToggleOutline,
window,
cx, cx,
) )
} }
@ -140,7 +142,8 @@ impl ToolbarItemView for Breadcrumbs {
fn set_active_pane_item( fn set_active_pane_item(
&mut self, &mut self,
active_pane_item: Option<&dyn ItemHandle>, active_pane_item: Option<&dyn ItemHandle>,
cx: &mut ViewContext<Self>, window: &mut Window,
cx: &mut Context<Self>,
) -> ToolbarItemLocation { ) -> ToolbarItemLocation {
cx.notify(); cx.notify();
self.active_item = None; self.active_item = None;
@ -149,10 +152,11 @@ impl ToolbarItemView for Breadcrumbs {
return ToolbarItemLocation::Hidden; return ToolbarItemLocation::Hidden;
}; };
let this = cx.view().downgrade(); let this = cx.model().downgrade();
self.subscription = Some(item.subscribe_to_item_events( self.subscription = Some(item.subscribe_to_item_events(
window,
cx, cx,
Box::new(move |event, cx| { Box::new(move |event, _, cx| {
if let ItemEvent::UpdateBreadcrumbs = event { if let ItemEvent::UpdateBreadcrumbs = event {
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
cx.notify(); cx.notify();
@ -170,7 +174,12 @@ impl ToolbarItemView for Breadcrumbs {
item.breadcrumb_location(cx) item.breadcrumb_location(cx)
} }
fn pane_focus_update(&mut self, pane_focused: bool, _: &mut ViewContext<Self>) { fn pane_focus_update(
&mut self,
pane_focused: bool,
_window: &mut Window,
_: &mut Context<Self>,
) {
self.pane_focused = pane_focused; self.pane_focused = pane_focused;
} }
} }

View file

@ -1,5 +1,5 @@
use anyhow::Result; use anyhow::Result;
use gpui::AppContext; use gpui::App;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{Settings, SettingsSources};
@ -29,7 +29,7 @@ impl Settings for CallSettings {
type FileContent = CallSettingsContent; type FileContent = CallSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> { fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
sources.json_merge() sources.json_merge()
} }
} }

View file

@ -8,8 +8,8 @@ use client::{proto, ChannelId, Client, TypedEnvelope, User, UserStore, ZED_ALWAY
use collections::HashSet; use collections::HashSet;
use futures::{channel::oneshot, future::Shared, Future, FutureExt}; use futures::{channel::oneshot, future::Shared, Future, FutureExt};
use gpui::{ use gpui::{
AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Subscription, App, AppContext as _, AsyncAppContext, Context, Entity, EventEmitter, Global, Subscription,
Task, WeakModel, Task, WeakEntity,
}; };
use postage::watch; use postage::watch;
use project::Project; use project::Project;
@ -23,18 +23,18 @@ pub use livekit_client::{
pub use participant::ParticipantLocation; pub use participant::ParticipantLocation;
pub use room::Room; pub use room::Room;
struct GlobalActiveCall(Model<ActiveCall>); struct GlobalActiveCall(Entity<ActiveCall>);
impl Global for GlobalActiveCall {} impl Global for GlobalActiveCall {}
pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) { pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
livekit_client::init( livekit_client::init(
cx.background_executor().dispatcher.clone(), cx.background_executor().dispatcher.clone(),
cx.http_client(), cx.http_client(),
); );
CallSettings::register(cx); CallSettings::register(cx);
let active_call = cx.new_model(|cx| ActiveCall::new(client, user_store, cx)); let active_call = cx.new(|cx| ActiveCall::new(client, user_store, cx));
cx.set_global(GlobalActiveCall(active_call)); cx.set_global(GlobalActiveCall(active_call));
} }
@ -46,7 +46,7 @@ impl OneAtATime {
/// spawn a task in the given context. /// spawn a task in the given context.
/// if another task is spawned before that resolves, or if the OneAtATime itself is dropped, the first task will be cancelled and return Ok(None) /// if another task is spawned before that resolves, or if the OneAtATime itself is dropped, the first task will be cancelled and return Ok(None)
/// otherwise you'll see the result of the task. /// otherwise you'll see the result of the task.
fn spawn<F, Fut, R>(&mut self, cx: &mut AppContext, f: F) -> Task<Result<Option<R>>> fn spawn<F, Fut, R>(&mut self, cx: &mut App, f: F) -> Task<Result<Option<R>>>
where where
F: 'static + FnOnce(AsyncAppContext) -> Fut, F: 'static + FnOnce(AsyncAppContext) -> Fut,
Fut: Future<Output = Result<R>>, Fut: Future<Output = Result<R>>,
@ -79,9 +79,9 @@ pub struct IncomingCall {
/// Singleton global maintaining the user's participation in a room across workspaces. /// Singleton global maintaining the user's participation in a room across workspaces.
pub struct ActiveCall { pub struct ActiveCall {
room: Option<(Model<Room>, Vec<Subscription>)>, room: Option<(Entity<Room>, Vec<Subscription>)>,
pending_room_creation: Option<Shared<Task<Result<Model<Room>, Arc<anyhow::Error>>>>>, pending_room_creation: Option<Shared<Task<Result<Entity<Room>, Arc<anyhow::Error>>>>>,
location: Option<WeakModel<Project>>, location: Option<WeakEntity<Project>>,
_join_debouncer: OneAtATime, _join_debouncer: OneAtATime,
pending_invites: HashSet<u64>, pending_invites: HashSet<u64>,
incoming_call: ( incoming_call: (
@ -89,14 +89,14 @@ pub struct ActiveCall {
watch::Receiver<Option<IncomingCall>>, watch::Receiver<Option<IncomingCall>>,
), ),
client: Arc<Client>, client: Arc<Client>,
user_store: Model<UserStore>, user_store: Entity<UserStore>,
_subscriptions: Vec<client::Subscription>, _subscriptions: Vec<client::Subscription>,
} }
impl EventEmitter<Event> for ActiveCall {} impl EventEmitter<Event> for ActiveCall {}
impl ActiveCall { impl ActiveCall {
fn new(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut ModelContext<Self>) -> Self { fn new(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut Context<Self>) -> Self {
Self { Self {
room: None, room: None,
pending_room_creation: None, pending_room_creation: None,
@ -113,12 +113,12 @@ impl ActiveCall {
} }
} }
pub fn channel_id(&self, cx: &AppContext) -> Option<ChannelId> { pub fn channel_id(&self, cx: &App) -> Option<ChannelId> {
self.room()?.read(cx).channel_id() self.room()?.read(cx).channel_id()
} }
async fn handle_incoming_call( async fn handle_incoming_call(
this: Model<Self>, this: Entity<Self>,
envelope: TypedEnvelope<proto::IncomingCall>, envelope: TypedEnvelope<proto::IncomingCall>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<proto::Ack> { ) -> Result<proto::Ack> {
@ -145,7 +145,7 @@ impl ActiveCall {
} }
async fn handle_call_canceled( async fn handle_call_canceled(
this: Model<Self>, this: Entity<Self>,
envelope: TypedEnvelope<proto::CallCanceled>, envelope: TypedEnvelope<proto::CallCanceled>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
@ -161,11 +161,11 @@ impl ActiveCall {
Ok(()) Ok(())
} }
pub fn global(cx: &AppContext) -> Model<Self> { pub fn global(cx: &App) -> Entity<Self> {
cx.global::<GlobalActiveCall>().0.clone() cx.global::<GlobalActiveCall>().0.clone()
} }
pub fn try_global(cx: &AppContext) -> Option<Model<Self>> { pub fn try_global(cx: &App) -> Option<Entity<Self>> {
cx.try_global::<GlobalActiveCall>() cx.try_global::<GlobalActiveCall>()
.map(|call| call.0.clone()) .map(|call| call.0.clone())
} }
@ -173,8 +173,8 @@ impl ActiveCall {
pub fn invite( pub fn invite(
&mut self, &mut self,
called_user_id: u64, called_user_id: u64,
initial_project: Option<Model<Project>>, initial_project: Option<Entity<Project>>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
if !self.pending_invites.insert(called_user_id) { if !self.pending_invites.insert(called_user_id) {
return Task::ready(Err(anyhow!("user was already invited"))); return Task::ready(Err(anyhow!("user was already invited")));
@ -269,7 +269,7 @@ impl ActiveCall {
pub fn cancel_invite( pub fn cancel_invite(
&mut self, &mut self,
called_user_id: u64, called_user_id: u64,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
let room_id = if let Some(room) = self.room() { let room_id = if let Some(room) = self.room() {
room.read(cx).id() room.read(cx).id()
@ -293,7 +293,7 @@ impl ActiveCall {
self.incoming_call.1.clone() self.incoming_call.1.clone()
} }
pub fn accept_incoming(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> { pub fn accept_incoming(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
if self.room.is_some() { if self.room.is_some() {
return Task::ready(Err(anyhow!("cannot join while on another call"))); return Task::ready(Err(anyhow!("cannot join while on another call")));
} }
@ -326,7 +326,7 @@ impl ActiveCall {
}) })
} }
pub fn decline_incoming(&mut self, _: &mut ModelContext<Self>) -> Result<()> { pub fn decline_incoming(&mut self, _: &mut Context<Self>) -> Result<()> {
let call = self let call = self
.incoming_call .incoming_call
.0 .0
@ -343,8 +343,8 @@ impl ActiveCall {
pub fn join_channel( pub fn join_channel(
&mut self, &mut self,
channel_id: ChannelId, channel_id: ChannelId,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<Option<Model<Room>>>> { ) -> Task<Result<Option<Entity<Room>>>> {
if let Some(room) = self.room().cloned() { if let Some(room) = self.room().cloned() {
if room.read(cx).channel_id() == Some(channel_id) { if room.read(cx).channel_id() == Some(channel_id) {
return Task::ready(Ok(Some(room))); return Task::ready(Ok(Some(room)));
@ -374,7 +374,7 @@ impl ActiveCall {
}) })
} }
pub fn hang_up(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> { pub fn hang_up(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
cx.notify(); cx.notify();
self.report_call_event("Call Ended", cx); self.report_call_event("Call Ended", cx);
@ -391,8 +391,8 @@ impl ActiveCall {
pub fn share_project( pub fn share_project(
&mut self, &mut self,
project: Model<Project>, project: Entity<Project>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<u64>> { ) -> Task<Result<u64>> {
if let Some((room, _)) = self.room.as_ref() { if let Some((room, _)) = self.room.as_ref() {
self.report_call_event("Project Shared", cx); self.report_call_event("Project Shared", cx);
@ -404,8 +404,8 @@ impl ActiveCall {
pub fn unshare_project( pub fn unshare_project(
&mut self, &mut self,
project: Model<Project>, project: Entity<Project>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Result<()> { ) -> Result<()> {
if let Some((room, _)) = self.room.as_ref() { if let Some((room, _)) = self.room.as_ref() {
self.report_call_event("Project Unshared", cx); self.report_call_event("Project Unshared", cx);
@ -415,14 +415,14 @@ impl ActiveCall {
} }
} }
pub fn location(&self) -> Option<&WeakModel<Project>> { pub fn location(&self) -> Option<&WeakEntity<Project>> {
self.location.as_ref() self.location.as_ref()
} }
pub fn set_location( pub fn set_location(
&mut self, &mut self,
project: Option<&Model<Project>>, project: Option<&Entity<Project>>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
if project.is_some() || !*ZED_ALWAYS_ACTIVE { if project.is_some() || !*ZED_ALWAYS_ACTIVE {
self.location = project.map(|project| project.downgrade()); self.location = project.map(|project| project.downgrade());
@ -433,11 +433,7 @@ impl ActiveCall {
Task::ready(Ok(())) Task::ready(Ok(()))
} }
fn set_room( fn set_room(&mut self, room: Option<Entity<Room>>, cx: &mut Context<Self>) -> Task<Result<()>> {
&mut self,
room: Option<Model<Room>>,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
if room.as_ref() == self.room.as_ref().map(|room| &room.0) { if room.as_ref() == self.room.as_ref().map(|room| &room.0) {
Task::ready(Ok(())) Task::ready(Ok(()))
} else { } else {
@ -473,7 +469,7 @@ impl ActiveCall {
} }
} }
pub fn room(&self) -> Option<&Model<Room>> { pub fn room(&self) -> Option<&Entity<Room>> {
self.room.as_ref().map(|(room, _)| room) self.room.as_ref().map(|(room, _)| room)
} }
@ -485,7 +481,7 @@ impl ActiveCall {
&self.pending_invites &self.pending_invites
} }
pub fn report_call_event(&self, operation: &'static str, cx: &mut AppContext) { pub fn report_call_event(&self, operation: &'static str, cx: &mut App) {
if let Some(room) = self.room() { if let Some(room) = self.room() {
let room = room.read(cx); let room = room.read(cx);
telemetry::event!( telemetry::event!(

View file

@ -1,7 +1,7 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use client::{proto, ParticipantIndex, User}; use client::{proto, ParticipantIndex, User};
use collections::HashMap; use collections::HashMap;
use gpui::WeakModel; use gpui::WeakEntity;
use livekit_client::AudioStream; use livekit_client::AudioStream;
use project::Project; use project::Project;
use std::sync::Arc; use std::sync::Arc;
@ -36,7 +36,7 @@ impl ParticipantLocation {
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct LocalParticipant { pub struct LocalParticipant {
pub projects: Vec<proto::ParticipantProject>, pub projects: Vec<proto::ParticipantProject>,
pub active_project: Option<WeakModel<Project>>, pub active_project: Option<WeakEntity<Project>>,
pub role: proto::ChannelRole, pub role: proto::ChannelRole,
} }

View file

@ -11,9 +11,7 @@ use client::{
use collections::{BTreeMap, HashMap, HashSet}; use collections::{BTreeMap, HashMap, HashSet};
use fs::Fs; use fs::Fs;
use futures::{FutureExt, StreamExt}; use futures::{FutureExt, StreamExt};
use gpui::{ use gpui::{App, AppContext, AsyncAppContext, Context, Entity, EventEmitter, Task, WeakEntity};
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel,
};
use language::LanguageRegistry; use language::LanguageRegistry;
use livekit::{ use livekit::{
capture_local_audio_track, capture_local_video_track, capture_local_audio_track, capture_local_video_track,
@ -71,8 +69,8 @@ pub struct Room {
channel_id: Option<ChannelId>, channel_id: Option<ChannelId>,
live_kit: Option<LiveKitRoom>, live_kit: Option<LiveKitRoom>,
status: RoomStatus, status: RoomStatus,
shared_projects: HashSet<WeakModel<Project>>, shared_projects: HashSet<WeakEntity<Project>>,
joined_projects: HashSet<WeakModel<Project>>, joined_projects: HashSet<WeakEntity<Project>>,
local_participant: LocalParticipant, local_participant: LocalParticipant,
remote_participants: BTreeMap<u64, RemoteParticipant>, remote_participants: BTreeMap<u64, RemoteParticipant>,
pending_participants: Vec<Arc<User>>, pending_participants: Vec<Arc<User>>,
@ -80,7 +78,7 @@ pub struct Room {
pending_call_count: usize, pending_call_count: usize,
leave_when_empty: bool, leave_when_empty: bool,
client: Arc<Client>, client: Arc<Client>,
user_store: Model<UserStore>, user_store: Entity<UserStore>,
follows_by_leader_id_project_id: HashMap<(PeerId, u64), Vec<PeerId>>, follows_by_leader_id_project_id: HashMap<(PeerId, u64), Vec<PeerId>>,
client_subscriptions: Vec<client::Subscription>, client_subscriptions: Vec<client::Subscription>,
_subscriptions: Vec<gpui::Subscription>, _subscriptions: Vec<gpui::Subscription>,
@ -115,8 +113,8 @@ impl Room {
channel_id: Option<ChannelId>, channel_id: Option<ChannelId>,
livekit_connection_info: Option<proto::LiveKitConnectionInfo>, livekit_connection_info: Option<proto::LiveKitConnectionInfo>,
client: Arc<Client>, client: Arc<Client>,
user_store: Model<UserStore>, user_store: Entity<UserStore>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Self { ) -> Self {
spawn_room_connection(livekit_connection_info, cx); spawn_room_connection(livekit_connection_info, cx);
@ -161,15 +159,15 @@ impl Room {
pub(crate) fn create( pub(crate) fn create(
called_user_id: u64, called_user_id: u64,
initial_project: Option<Model<Project>>, initial_project: Option<Entity<Project>>,
client: Arc<Client>, client: Arc<Client>,
user_store: Model<UserStore>, user_store: Entity<UserStore>,
cx: &mut AppContext, cx: &mut App,
) -> Task<Result<Model<Self>>> { ) -> Task<Result<Entity<Self>>> {
cx.spawn(move |mut cx| async move { cx.spawn(move |mut cx| async move {
let response = client.request(proto::CreateRoom {}).await?; let response = client.request(proto::CreateRoom {}).await?;
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?; let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
let room = cx.new_model(|cx| { let room = cx.new(|cx| {
let mut room = Self::new( let mut room = Self::new(
room_proto.id, room_proto.id,
None, None,
@ -211,9 +209,9 @@ impl Room {
pub(crate) async fn join_channel( pub(crate) async fn join_channel(
channel_id: ChannelId, channel_id: ChannelId,
client: Arc<Client>, client: Arc<Client>,
user_store: Model<UserStore>, user_store: Entity<UserStore>,
cx: AsyncAppContext, cx: AsyncAppContext,
) -> Result<Model<Self>> { ) -> Result<Entity<Self>> {
Self::from_join_response( Self::from_join_response(
client client
.request(proto::JoinChannel { .request(proto::JoinChannel {
@ -229,9 +227,9 @@ impl Room {
pub(crate) async fn join( pub(crate) async fn join(
room_id: u64, room_id: u64,
client: Arc<Client>, client: Arc<Client>,
user_store: Model<UserStore>, user_store: Entity<UserStore>,
cx: AsyncAppContext, cx: AsyncAppContext,
) -> Result<Model<Self>> { ) -> Result<Entity<Self>> {
Self::from_join_response( Self::from_join_response(
client.request(proto::JoinRoom { id: room_id }).await?, client.request(proto::JoinRoom { id: room_id }).await?,
client, client,
@ -240,13 +238,13 @@ impl Room {
) )
} }
fn released(&mut self, cx: &mut AppContext) { fn released(&mut self, cx: &mut App) {
if self.status.is_online() { if self.status.is_online() {
self.leave_internal(cx).detach_and_log_err(cx); self.leave_internal(cx).detach_and_log_err(cx);
} }
} }
fn app_will_quit(&mut self, cx: &mut ModelContext<Self>) -> impl Future<Output = ()> { fn app_will_quit(&mut self, cx: &mut Context<Self>) -> impl Future<Output = ()> {
let task = if self.status.is_online() { let task = if self.status.is_online() {
let leave = self.leave_internal(cx); let leave = self.leave_internal(cx);
Some(cx.background_executor().spawn(async move { Some(cx.background_executor().spawn(async move {
@ -263,18 +261,18 @@ impl Room {
} }
} }
pub fn mute_on_join(cx: &AppContext) -> bool { pub fn mute_on_join(cx: &App) -> bool {
CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some() CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some()
} }
fn from_join_response( fn from_join_response(
response: proto::JoinRoomResponse, response: proto::JoinRoomResponse,
client: Arc<Client>, client: Arc<Client>,
user_store: Model<UserStore>, user_store: Entity<UserStore>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<Model<Self>> { ) -> Result<Entity<Self>> {
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?; let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
let room = cx.new_model(|cx| { let room = cx.new(|cx| {
Self::new( Self::new(
room_proto.id, room_proto.id,
response.channel_id.map(ChannelId), response.channel_id.map(ChannelId),
@ -300,12 +298,12 @@ impl Room {
&& self.pending_call_count == 0 && self.pending_call_count == 0
} }
pub(crate) fn leave(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> { pub(crate) fn leave(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
cx.notify(); cx.notify();
self.leave_internal(cx) self.leave_internal(cx)
} }
fn leave_internal(&mut self, cx: &mut AppContext) -> Task<Result<()>> { fn leave_internal(&mut self, cx: &mut App) -> Task<Result<()>> {
if self.status.is_offline() { if self.status.is_offline() {
return Task::ready(Err(anyhow!("room is offline"))); return Task::ready(Err(anyhow!("room is offline")));
} }
@ -322,7 +320,7 @@ impl Room {
}) })
} }
pub(crate) fn clear_state(&mut self, cx: &mut AppContext) { pub(crate) fn clear_state(&mut self, cx: &mut App) {
for project in self.shared_projects.drain() { for project in self.shared_projects.drain() {
if let Some(project) = project.upgrade() { if let Some(project) = project.upgrade() {
project.update(cx, |project, cx| { project.update(cx, |project, cx| {
@ -350,7 +348,7 @@ impl Room {
} }
async fn maintain_connection( async fn maintain_connection(
this: WeakModel<Self>, this: WeakEntity<Self>,
client: Arc<Client>, client: Arc<Client>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
@ -436,7 +434,7 @@ impl Room {
)) ))
} }
fn rejoin(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> { fn rejoin(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
let mut projects = HashMap::default(); let mut projects = HashMap::default();
let mut reshared_projects = Vec::new(); let mut reshared_projects = Vec::new();
let mut rejoined_projects = Vec::new(); let mut rejoined_projects = Vec::new();
@ -562,7 +560,7 @@ impl Room {
&mut self, &mut self,
user_id: u64, user_id: u64,
role: proto::ChannelRole, role: proto::ChannelRole,
cx: &ModelContext<Self>, cx: &Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
let client = self.client.clone(); let client = self.client.clone();
let room_id = self.id; let room_id = self.id;
@ -594,7 +592,7 @@ impl Room {
} }
/// Returns the most 'active' projects, defined as most people in the project /// Returns the most 'active' projects, defined as most people in the project
pub fn most_active_project(&self, cx: &AppContext) -> Option<(u64, u64)> { pub fn most_active_project(&self, cx: &App) -> Option<(u64, u64)> {
let mut project_hosts_and_guest_counts = HashMap::<u64, (Option<u64>, u32)>::default(); let mut project_hosts_and_guest_counts = HashMap::<u64, (Option<u64>, u32)>::default();
for participant in self.remote_participants.values() { for participant in self.remote_participants.values() {
match participant.location { match participant.location {
@ -631,7 +629,7 @@ impl Room {
} }
async fn handle_room_updated( async fn handle_room_updated(
this: Model<Self>, this: Entity<Self>,
envelope: TypedEnvelope<proto::RoomUpdated>, envelope: TypedEnvelope<proto::RoomUpdated>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
@ -642,7 +640,7 @@ impl Room {
this.update(&mut cx, |this, cx| this.apply_room_update(room, cx))? this.update(&mut cx, |this, cx| this.apply_room_update(room, cx))?
} }
fn apply_room_update(&mut self, room: proto::Room, cx: &mut ModelContext<Self>) -> Result<()> { fn apply_room_update(&mut self, room: proto::Room, cx: &mut Context<Self>) -> Result<()> {
log::trace!( log::trace!(
"client {:?}. room update: {:?}", "client {:?}. room update: {:?}",
self.client.user_id(), self.client.user_id(),
@ -666,11 +664,7 @@ impl Room {
} }
} }
fn start_room_connection( fn start_room_connection(&self, mut room: proto::Room, cx: &mut Context<Self>) -> Task<()> {
&self,
mut room: proto::Room,
cx: &mut ModelContext<Self>,
) -> Task<()> {
// Filter ourselves out from the room's participants. // Filter ourselves out from the room's participants.
let local_participant_ix = room let local_participant_ix = room
.participants .participants
@ -916,11 +910,7 @@ impl Room {
}) })
} }
fn livekit_room_updated( fn livekit_room_updated(&mut self, event: RoomEvent, cx: &mut Context<Self>) -> Result<()> {
&mut self,
event: RoomEvent,
cx: &mut ModelContext<Self>,
) -> Result<()> {
log::trace!( log::trace!(
"client {:?}. livekit event: {:?}", "client {:?}. livekit event: {:?}",
self.client.user_id(), self.client.user_id(),
@ -1090,7 +1080,7 @@ impl Room {
&mut self, &mut self,
called_user_id: u64, called_user_id: u64,
initial_project_id: Option<u64>, initial_project_id: Option<u64>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
if self.status.is_offline() { if self.status.is_offline() {
return Task::ready(Err(anyhow!("room is offline"))); return Task::ready(Err(anyhow!("room is offline")));
@ -1124,8 +1114,8 @@ impl Room {
id: u64, id: u64,
language_registry: Arc<LanguageRegistry>, language_registry: Arc<LanguageRegistry>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<Model<Project>>> { ) -> Task<Result<Entity<Project>>> {
let client = self.client.clone(); let client = self.client.clone();
let user_store = self.user_store.clone(); let user_store = self.user_store.clone();
cx.emit(Event::RemoteProjectJoined { project_id: id }); cx.emit(Event::RemoteProjectJoined { project_id: id });
@ -1149,8 +1139,8 @@ impl Room {
pub fn share_project( pub fn share_project(
&mut self, &mut self,
project: Model<Project>, project: Entity<Project>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<u64>> { ) -> Task<Result<u64>> {
if let Some(project_id) = project.read(cx).remote_id() { if let Some(project_id) = project.read(cx).remote_id() {
return Task::ready(Ok(project_id)); return Task::ready(Ok(project_id));
@ -1187,8 +1177,8 @@ impl Room {
pub(crate) fn unshare_project( pub(crate) fn unshare_project(
&mut self, &mut self,
project: Model<Project>, project: Entity<Project>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Result<()> { ) -> Result<()> {
let project_id = match project.read(cx).remote_id() { let project_id = match project.read(cx).remote_id() {
Some(project_id) => project_id, Some(project_id) => project_id,
@ -1206,8 +1196,8 @@ impl Room {
pub(crate) fn set_location( pub(crate) fn set_location(
&mut self, &mut self,
project: Option<&Model<Project>>, project: Option<&Entity<Project>>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
if self.status.is_offline() { if self.status.is_offline() {
return Task::ready(Err(anyhow!("room is offline"))); return Task::ready(Err(anyhow!("room is offline")));
@ -1299,7 +1289,7 @@ impl Room {
} }
#[track_caller] #[track_caller]
pub fn share_microphone(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> { pub fn share_microphone(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
if self.status.is_offline() { if self.status.is_offline() {
return Task::ready(Err(anyhow!("room is offline"))); return Task::ready(Err(anyhow!("room is offline")));
} }
@ -1375,7 +1365,7 @@ impl Room {
}) })
} }
pub fn share_screen(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> { pub fn share_screen(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
if self.status.is_offline() { if self.status.is_offline() {
return Task::ready(Err(anyhow!("room is offline"))); return Task::ready(Err(anyhow!("room is offline")));
} }
@ -1460,7 +1450,7 @@ impl Room {
}) })
} }
pub fn toggle_mute(&mut self, cx: &mut ModelContext<Self>) { pub fn toggle_mute(&mut self, cx: &mut Context<Self>) {
if let Some(live_kit) = self.live_kit.as_mut() { if let Some(live_kit) = self.live_kit.as_mut() {
// When unmuting, undeafen if the user was deafened before. // When unmuting, undeafen if the user was deafened before.
let was_deafened = live_kit.deafened; let was_deafened = live_kit.deafened;
@ -1486,7 +1476,7 @@ impl Room {
} }
} }
pub fn toggle_deafen(&mut self, cx: &mut ModelContext<Self>) { pub fn toggle_deafen(&mut self, cx: &mut Context<Self>) {
if let Some(live_kit) = self.live_kit.as_mut() { if let Some(live_kit) = self.live_kit.as_mut() {
// When deafening, mute the microphone if it was not already muted. // When deafening, mute the microphone if it was not already muted.
// When un-deafening, unmute the microphone, unless it was explicitly muted. // When un-deafening, unmute the microphone, unless it was explicitly muted.
@ -1504,7 +1494,7 @@ impl Room {
} }
} }
pub fn unshare_screen(&mut self, cx: &mut ModelContext<Self>) -> Result<()> { pub fn unshare_screen(&mut self, cx: &mut Context<Self>) -> Result<()> {
if self.status.is_offline() { if self.status.is_offline() {
return Err(anyhow!("room is offline")); return Err(anyhow!("room is offline"));
} }
@ -1535,7 +1525,7 @@ impl Room {
} }
} }
fn set_deafened(&mut self, deafened: bool, cx: &mut ModelContext<Self>) -> Option<()> { fn set_deafened(&mut self, deafened: bool, cx: &mut Context<Self>) -> Option<()> {
let live_kit = self.live_kit.as_mut()?; let live_kit = self.live_kit.as_mut()?;
cx.notify(); cx.notify();
for (_, participant) in live_kit.room.remote_participants() { for (_, participant) in live_kit.room.remote_participants() {
@ -1549,11 +1539,7 @@ impl Room {
None None
} }
fn set_mute( fn set_mute(&mut self, should_mute: bool, cx: &mut Context<Room>) -> Option<Task<Result<()>>> {
&mut self,
should_mute: bool,
cx: &mut ModelContext<Room>,
) -> Option<Task<Result<()>>> {
let live_kit = self.live_kit.as_mut()?; let live_kit = self.live_kit.as_mut()?;
cx.notify(); cx.notify();
@ -1589,7 +1575,7 @@ impl Room {
fn spawn_room_connection( fn spawn_room_connection(
livekit_connection_info: Option<proto::LiveKitConnectionInfo>, livekit_connection_info: Option<proto::LiveKitConnectionInfo>,
cx: &mut ModelContext<'_, Room>, cx: &mut Context<'_, Room>,
) { ) {
if let Some(connection_info) = livekit_connection_info { if let Some(connection_info) = livekit_connection_info {
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
@ -1651,7 +1637,7 @@ struct LiveKitRoom {
} }
impl LiveKitRoom { impl LiveKitRoom {
fn stop_publishing(&mut self, cx: &mut ModelContext<Room>) { fn stop_publishing(&mut self, cx: &mut Context<Room>) {
let mut tracks_to_unpublish = Vec::new(); let mut tracks_to_unpublish = Vec::new();
if let LocalTrack::Published { if let LocalTrack::Published {
track_publication, .. track_publication, ..

View file

@ -8,8 +8,8 @@ use client::{proto, ChannelId, Client, TypedEnvelope, User, UserStore, ZED_ALWAY
use collections::HashSet; use collections::HashSet;
use futures::{channel::oneshot, future::Shared, Future, FutureExt}; use futures::{channel::oneshot, future::Shared, Future, FutureExt};
use gpui::{ use gpui::{
AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Subscription, App, AppContext as _, AsyncAppContext, Context, Entity, EventEmitter, Global, Subscription,
Task, WeakModel, Task, WeakEntity,
}; };
use postage::watch; use postage::watch;
use project::Project; use project::Project;
@ -20,14 +20,14 @@ use std::sync::Arc;
pub use participant::ParticipantLocation; pub use participant::ParticipantLocation;
pub use room::Room; pub use room::Room;
struct GlobalActiveCall(Model<ActiveCall>); struct GlobalActiveCall(Entity<ActiveCall>);
impl Global for GlobalActiveCall {} impl Global for GlobalActiveCall {}
pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) { pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
CallSettings::register(cx); CallSettings::register(cx);
let active_call = cx.new_model(|cx| ActiveCall::new(client, user_store, cx)); let active_call = cx.new(|cx| ActiveCall::new(client, user_store, cx));
cx.set_global(GlobalActiveCall(active_call)); cx.set_global(GlobalActiveCall(active_call));
} }
@ -39,7 +39,7 @@ impl OneAtATime {
/// spawn a task in the given context. /// spawn a task in the given context.
/// if another task is spawned before that resolves, or if the OneAtATime itself is dropped, the first task will be cancelled and return Ok(None) /// if another task is spawned before that resolves, or if the OneAtATime itself is dropped, the first task will be cancelled and return Ok(None)
/// otherwise you'll see the result of the task. /// otherwise you'll see the result of the task.
fn spawn<F, Fut, R>(&mut self, cx: &mut AppContext, f: F) -> Task<Result<Option<R>>> fn spawn<F, Fut, R>(&mut self, cx: &mut App, f: F) -> Task<Result<Option<R>>>
where where
F: 'static + FnOnce(AsyncAppContext) -> Fut, F: 'static + FnOnce(AsyncAppContext) -> Fut,
Fut: Future<Output = Result<R>>, Fut: Future<Output = Result<R>>,
@ -72,9 +72,9 @@ pub struct IncomingCall {
/// Singleton global maintaining the user's participation in a room across workspaces. /// Singleton global maintaining the user's participation in a room across workspaces.
pub struct ActiveCall { pub struct ActiveCall {
room: Option<(Model<Room>, Vec<Subscription>)>, room: Option<(Entity<Room>, Vec<Subscription>)>,
pending_room_creation: Option<Shared<Task<Result<Model<Room>, Arc<anyhow::Error>>>>>, pending_room_creation: Option<Shared<Task<Result<Entity<Room>, Arc<anyhow::Error>>>>>,
location: Option<WeakModel<Project>>, location: Option<WeakEntity<Project>>,
_join_debouncer: OneAtATime, _join_debouncer: OneAtATime,
pending_invites: HashSet<u64>, pending_invites: HashSet<u64>,
incoming_call: ( incoming_call: (
@ -82,14 +82,14 @@ pub struct ActiveCall {
watch::Receiver<Option<IncomingCall>>, watch::Receiver<Option<IncomingCall>>,
), ),
client: Arc<Client>, client: Arc<Client>,
user_store: Model<UserStore>, user_store: Entity<UserStore>,
_subscriptions: Vec<client::Subscription>, _subscriptions: Vec<client::Subscription>,
} }
impl EventEmitter<Event> for ActiveCall {} impl EventEmitter<Event> for ActiveCall {}
impl ActiveCall { impl ActiveCall {
fn new(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut ModelContext<Self>) -> Self { fn new(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut Context<Self>) -> Self {
Self { Self {
room: None, room: None,
pending_room_creation: None, pending_room_creation: None,
@ -106,12 +106,12 @@ impl ActiveCall {
} }
} }
pub fn channel_id(&self, cx: &AppContext) -> Option<ChannelId> { pub fn channel_id(&self, cx: &App) -> Option<ChannelId> {
self.room()?.read(cx).channel_id() self.room()?.read(cx).channel_id()
} }
async fn handle_incoming_call( async fn handle_incoming_call(
this: Model<Self>, this: Entity<Self>,
envelope: TypedEnvelope<proto::IncomingCall>, envelope: TypedEnvelope<proto::IncomingCall>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<proto::Ack> { ) -> Result<proto::Ack> {
@ -138,7 +138,7 @@ impl ActiveCall {
} }
async fn handle_call_canceled( async fn handle_call_canceled(
this: Model<Self>, this: Entity<Self>,
envelope: TypedEnvelope<proto::CallCanceled>, envelope: TypedEnvelope<proto::CallCanceled>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
@ -154,11 +154,11 @@ impl ActiveCall {
Ok(()) Ok(())
} }
pub fn global(cx: &AppContext) -> Model<Self> { pub fn global(cx: &App) -> Entity<Self> {
cx.global::<GlobalActiveCall>().0.clone() cx.global::<GlobalActiveCall>().0.clone()
} }
pub fn try_global(cx: &AppContext) -> Option<Model<Self>> { pub fn try_global(cx: &App) -> Option<Entity<Self>> {
cx.try_global::<GlobalActiveCall>() cx.try_global::<GlobalActiveCall>()
.map(|call| call.0.clone()) .map(|call| call.0.clone())
} }
@ -166,8 +166,8 @@ impl ActiveCall {
pub fn invite( pub fn invite(
&mut self, &mut self,
called_user_id: u64, called_user_id: u64,
initial_project: Option<Model<Project>>, initial_project: Option<Entity<Project>>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
if !self.pending_invites.insert(called_user_id) { if !self.pending_invites.insert(called_user_id) {
return Task::ready(Err(anyhow!("user was already invited"))); return Task::ready(Err(anyhow!("user was already invited")));
@ -262,7 +262,7 @@ impl ActiveCall {
pub fn cancel_invite( pub fn cancel_invite(
&mut self, &mut self,
called_user_id: u64, called_user_id: u64,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
let room_id = if let Some(room) = self.room() { let room_id = if let Some(room) = self.room() {
room.read(cx).id() room.read(cx).id()
@ -286,7 +286,7 @@ impl ActiveCall {
self.incoming_call.1.clone() self.incoming_call.1.clone()
} }
pub fn accept_incoming(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> { pub fn accept_incoming(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
if self.room.is_some() { if self.room.is_some() {
return Task::ready(Err(anyhow!("cannot join while on another call"))); return Task::ready(Err(anyhow!("cannot join while on another call")));
} }
@ -319,7 +319,7 @@ impl ActiveCall {
}) })
} }
pub fn decline_incoming(&mut self, _: &mut ModelContext<Self>) -> Result<()> { pub fn decline_incoming(&mut self, _: &mut Context<Self>) -> Result<()> {
let call = self let call = self
.incoming_call .incoming_call
.0 .0
@ -336,8 +336,8 @@ impl ActiveCall {
pub fn join_channel( pub fn join_channel(
&mut self, &mut self,
channel_id: ChannelId, channel_id: ChannelId,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<Option<Model<Room>>>> { ) -> Task<Result<Option<Entity<Room>>>> {
if let Some(room) = self.room().cloned() { if let Some(room) = self.room().cloned() {
if room.read(cx).channel_id() == Some(channel_id) { if room.read(cx).channel_id() == Some(channel_id) {
return Task::ready(Ok(Some(room))); return Task::ready(Ok(Some(room)));
@ -367,7 +367,7 @@ impl ActiveCall {
}) })
} }
pub fn hang_up(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> { pub fn hang_up(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
cx.notify(); cx.notify();
self.report_call_event("Call Ended", cx); self.report_call_event("Call Ended", cx);
@ -384,8 +384,8 @@ impl ActiveCall {
pub fn share_project( pub fn share_project(
&mut self, &mut self,
project: Model<Project>, project: Entity<Project>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<u64>> { ) -> Task<Result<u64>> {
if let Some((room, _)) = self.room.as_ref() { if let Some((room, _)) = self.room.as_ref() {
self.report_call_event("Project Shared", cx); self.report_call_event("Project Shared", cx);
@ -397,8 +397,8 @@ impl ActiveCall {
pub fn unshare_project( pub fn unshare_project(
&mut self, &mut self,
project: Model<Project>, project: Entity<Project>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Result<()> { ) -> Result<()> {
if let Some((room, _)) = self.room.as_ref() { if let Some((room, _)) = self.room.as_ref() {
self.report_call_event("Project Unshared", cx); self.report_call_event("Project Unshared", cx);
@ -408,14 +408,14 @@ impl ActiveCall {
} }
} }
pub fn location(&self) -> Option<&WeakModel<Project>> { pub fn location(&self) -> Option<&WeakEntity<Project>> {
self.location.as_ref() self.location.as_ref()
} }
pub fn set_location( pub fn set_location(
&mut self, &mut self,
project: Option<&Model<Project>>, project: Option<&Entity<Project>>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
if project.is_some() || !*ZED_ALWAYS_ACTIVE { if project.is_some() || !*ZED_ALWAYS_ACTIVE {
self.location = project.map(|project| project.downgrade()); self.location = project.map(|project| project.downgrade());
@ -426,11 +426,7 @@ impl ActiveCall {
Task::ready(Ok(())) Task::ready(Ok(()))
} }
fn set_room( fn set_room(&mut self, room: Option<Entity<Room>>, cx: &mut Context<Self>) -> Task<Result<()>> {
&mut self,
room: Option<Model<Room>>,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
if room.as_ref() == self.room.as_ref().map(|room| &room.0) { if room.as_ref() == self.room.as_ref().map(|room| &room.0) {
Task::ready(Ok(())) Task::ready(Ok(()))
} else { } else {
@ -466,7 +462,7 @@ impl ActiveCall {
} }
} }
pub fn room(&self) -> Option<&Model<Room>> { pub fn room(&self) -> Option<&Entity<Room>> {
self.room.as_ref().map(|(room, _)| room) self.room.as_ref().map(|(room, _)| room)
} }
@ -478,7 +474,7 @@ impl ActiveCall {
&self.pending_invites &self.pending_invites
} }
pub fn report_call_event(&self, operation: &'static str, cx: &mut AppContext) { pub fn report_call_event(&self, operation: &'static str, cx: &mut App) {
if let Some(room) = self.room() { if let Some(room) = self.room() {
let room = room.read(cx); let room = room.read(cx);
telemetry::event!( telemetry::event!(

View file

@ -2,7 +2,7 @@ use anyhow::{anyhow, Result};
use client::ParticipantIndex; use client::ParticipantIndex;
use client::{proto, User}; use client::{proto, User};
use collections::HashMap; use collections::HashMap;
use gpui::WeakModel; use gpui::WeakEntity;
pub use livekit_client_macos::Frame; pub use livekit_client_macos::Frame;
pub use livekit_client_macos::{RemoteAudioTrack, RemoteVideoTrack}; pub use livekit_client_macos::{RemoteAudioTrack, RemoteVideoTrack};
use project::Project; use project::Project;
@ -35,7 +35,7 @@ impl ParticipantLocation {
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct LocalParticipant { pub struct LocalParticipant {
pub projects: Vec<proto::ParticipantProject>, pub projects: Vec<proto::ParticipantProject>,
pub active_project: Option<WeakModel<Project>>, pub active_project: Option<WeakEntity<Project>>,
pub role: proto::ChannelRole, pub role: proto::ChannelRole,
} }

View file

@ -12,7 +12,7 @@ use collections::{BTreeMap, HashMap, HashSet};
use fs::Fs; use fs::Fs;
use futures::{FutureExt, StreamExt}; use futures::{FutureExt, StreamExt};
use gpui::{ use gpui::{
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel, App, AppContext as _, AsyncAppContext, Context, Entity, EventEmitter, Task, WeakEntity,
}; };
use language::LanguageRegistry; use language::LanguageRegistry;
use livekit_client_macos::{LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RoomUpdate}; use livekit_client_macos::{LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RoomUpdate};
@ -62,8 +62,8 @@ pub struct Room {
channel_id: Option<ChannelId>, channel_id: Option<ChannelId>,
live_kit: Option<LiveKitRoom>, live_kit: Option<LiveKitRoom>,
status: RoomStatus, status: RoomStatus,
shared_projects: HashSet<WeakModel<Project>>, shared_projects: HashSet<WeakEntity<Project>>,
joined_projects: HashSet<WeakModel<Project>>, joined_projects: HashSet<WeakEntity<Project>>,
local_participant: LocalParticipant, local_participant: LocalParticipant,
remote_participants: BTreeMap<u64, RemoteParticipant>, remote_participants: BTreeMap<u64, RemoteParticipant>,
pending_participants: Vec<Arc<User>>, pending_participants: Vec<Arc<User>>,
@ -71,7 +71,7 @@ pub struct Room {
pending_call_count: usize, pending_call_count: usize,
leave_when_empty: bool, leave_when_empty: bool,
client: Arc<Client>, client: Arc<Client>,
user_store: Model<UserStore>, user_store: Entity<UserStore>,
follows_by_leader_id_project_id: HashMap<(PeerId, u64), Vec<PeerId>>, follows_by_leader_id_project_id: HashMap<(PeerId, u64), Vec<PeerId>>,
client_subscriptions: Vec<client::Subscription>, client_subscriptions: Vec<client::Subscription>,
_subscriptions: Vec<gpui::Subscription>, _subscriptions: Vec<gpui::Subscription>,
@ -109,8 +109,8 @@ impl Room {
channel_id: Option<ChannelId>, channel_id: Option<ChannelId>,
live_kit_connection_info: Option<proto::LiveKitConnectionInfo>, live_kit_connection_info: Option<proto::LiveKitConnectionInfo>,
client: Arc<Client>, client: Arc<Client>,
user_store: Model<UserStore>, user_store: Entity<UserStore>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Self { ) -> Self {
let live_kit_room = if let Some(connection_info) = live_kit_connection_info { let live_kit_room = if let Some(connection_info) = live_kit_connection_info {
let room = livekit_client_macos::Room::new(); let room = livekit_client_macos::Room::new();
@ -225,15 +225,15 @@ impl Room {
pub(crate) fn create( pub(crate) fn create(
called_user_id: u64, called_user_id: u64,
initial_project: Option<Model<Project>>, initial_project: Option<Entity<Project>>,
client: Arc<Client>, client: Arc<Client>,
user_store: Model<UserStore>, user_store: Entity<UserStore>,
cx: &mut AppContext, cx: &mut App,
) -> Task<Result<Model<Self>>> { ) -> Task<Result<Entity<Self>>> {
cx.spawn(move |mut cx| async move { cx.spawn(move |mut cx| async move {
let response = client.request(proto::CreateRoom {}).await?; let response = client.request(proto::CreateRoom {}).await?;
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?; let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
let room = cx.new_model(|cx| { let room = cx.new(|cx| {
let mut room = Self::new( let mut room = Self::new(
room_proto.id, room_proto.id,
None, None,
@ -275,9 +275,9 @@ impl Room {
pub(crate) async fn join_channel( pub(crate) async fn join_channel(
channel_id: ChannelId, channel_id: ChannelId,
client: Arc<Client>, client: Arc<Client>,
user_store: Model<UserStore>, user_store: Entity<UserStore>,
cx: AsyncAppContext, cx: AsyncAppContext,
) -> Result<Model<Self>> { ) -> Result<Entity<Self>> {
Self::from_join_response( Self::from_join_response(
client client
.request(proto::JoinChannel { .request(proto::JoinChannel {
@ -293,9 +293,9 @@ impl Room {
pub(crate) async fn join( pub(crate) async fn join(
room_id: u64, room_id: u64,
client: Arc<Client>, client: Arc<Client>,
user_store: Model<UserStore>, user_store: Entity<UserStore>,
cx: AsyncAppContext, cx: AsyncAppContext,
) -> Result<Model<Self>> { ) -> Result<Entity<Self>> {
Self::from_join_response( Self::from_join_response(
client.request(proto::JoinRoom { id: room_id }).await?, client.request(proto::JoinRoom { id: room_id }).await?,
client, client,
@ -304,13 +304,13 @@ impl Room {
) )
} }
fn released(&mut self, cx: &mut AppContext) { fn released(&mut self, cx: &mut App) {
if self.status.is_online() { if self.status.is_online() {
self.leave_internal(cx).detach_and_log_err(cx); self.leave_internal(cx).detach_and_log_err(cx);
} }
} }
fn app_will_quit(&mut self, cx: &mut ModelContext<Self>) -> impl Future<Output = ()> { fn app_will_quit(&mut self, cx: &mut Context<Self>) -> impl Future<Output = ()> {
let task = if self.status.is_online() { let task = if self.status.is_online() {
let leave = self.leave_internal(cx); let leave = self.leave_internal(cx);
Some(cx.background_executor().spawn(async move { Some(cx.background_executor().spawn(async move {
@ -327,18 +327,18 @@ impl Room {
} }
} }
pub fn mute_on_join(cx: &AppContext) -> bool { pub fn mute_on_join(cx: &App) -> bool {
CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some() CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some()
} }
fn from_join_response( fn from_join_response(
response: proto::JoinRoomResponse, response: proto::JoinRoomResponse,
client: Arc<Client>, client: Arc<Client>,
user_store: Model<UserStore>, user_store: Entity<UserStore>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<Model<Self>> { ) -> Result<Entity<Self>> {
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?; let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
let room = cx.new_model(|cx| { let room = cx.new(|cx| {
Self::new( Self::new(
room_proto.id, room_proto.id,
response.channel_id.map(ChannelId), response.channel_id.map(ChannelId),
@ -364,12 +364,12 @@ impl Room {
&& self.pending_call_count == 0 && self.pending_call_count == 0
} }
pub(crate) fn leave(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> { pub(crate) fn leave(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
cx.notify(); cx.notify();
self.leave_internal(cx) self.leave_internal(cx)
} }
fn leave_internal(&mut self, cx: &mut AppContext) -> Task<Result<()>> { fn leave_internal(&mut self, cx: &mut App) -> Task<Result<()>> {
if self.status.is_offline() { if self.status.is_offline() {
return Task::ready(Err(anyhow!("room is offline"))); return Task::ready(Err(anyhow!("room is offline")));
} }
@ -386,7 +386,7 @@ impl Room {
}) })
} }
pub(crate) fn clear_state(&mut self, cx: &mut AppContext) { pub(crate) fn clear_state(&mut self, cx: &mut App) {
for project in self.shared_projects.drain() { for project in self.shared_projects.drain() {
if let Some(project) = project.upgrade() { if let Some(project) = project.upgrade() {
project.update(cx, |project, cx| { project.update(cx, |project, cx| {
@ -414,7 +414,7 @@ impl Room {
} }
async fn maintain_connection( async fn maintain_connection(
this: WeakModel<Self>, this: WeakEntity<Self>,
client: Arc<Client>, client: Arc<Client>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
@ -500,7 +500,7 @@ impl Room {
)) ))
} }
fn rejoin(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> { fn rejoin(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
let mut projects = HashMap::default(); let mut projects = HashMap::default();
let mut reshared_projects = Vec::new(); let mut reshared_projects = Vec::new();
let mut rejoined_projects = Vec::new(); let mut rejoined_projects = Vec::new();
@ -626,7 +626,7 @@ impl Room {
&mut self, &mut self,
user_id: u64, user_id: u64,
role: proto::ChannelRole, role: proto::ChannelRole,
cx: &ModelContext<Self>, cx: &Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
let client = self.client.clone(); let client = self.client.clone();
let room_id = self.id; let room_id = self.id;
@ -658,7 +658,7 @@ impl Room {
} }
/// Returns the most 'active' projects, defined as most people in the project /// Returns the most 'active' projects, defined as most people in the project
pub fn most_active_project(&self, cx: &AppContext) -> Option<(u64, u64)> { pub fn most_active_project(&self, cx: &App) -> Option<(u64, u64)> {
let mut project_hosts_and_guest_counts = HashMap::<u64, (Option<u64>, u32)>::default(); let mut project_hosts_and_guest_counts = HashMap::<u64, (Option<u64>, u32)>::default();
for participant in self.remote_participants.values() { for participant in self.remote_participants.values() {
match participant.location { match participant.location {
@ -695,7 +695,7 @@ impl Room {
} }
async fn handle_room_updated( async fn handle_room_updated(
this: Model<Self>, this: Entity<Self>,
envelope: TypedEnvelope<proto::RoomUpdated>, envelope: TypedEnvelope<proto::RoomUpdated>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
@ -706,11 +706,7 @@ impl Room {
this.update(&mut cx, |this, cx| this.apply_room_update(room, cx))? this.update(&mut cx, |this, cx| this.apply_room_update(room, cx))?
} }
fn apply_room_update( fn apply_room_update(&mut self, mut room: proto::Room, cx: &mut Context<Self>) -> Result<()> {
&mut self,
mut room: proto::Room,
cx: &mut ModelContext<Self>,
) -> Result<()> {
// Filter ourselves out from the room's participants. // Filter ourselves out from the room's participants.
let local_participant_ix = room let local_participant_ix = room
.participants .participants
@ -976,11 +972,7 @@ impl Room {
} }
} }
fn live_kit_room_updated( fn live_kit_room_updated(&mut self, update: RoomUpdate, cx: &mut Context<Self>) -> Result<()> {
&mut self,
update: RoomUpdate,
cx: &mut ModelContext<Self>,
) -> Result<()> {
match update { match update {
RoomUpdate::SubscribedToRemoteVideoTrack(track) => { RoomUpdate::SubscribedToRemoteVideoTrack(track) => {
let user_id = track.publisher_id().parse()?; let user_id = track.publisher_id().parse()?;
@ -1132,7 +1124,7 @@ impl Room {
&mut self, &mut self,
called_user_id: u64, called_user_id: u64,
initial_project_id: Option<u64>, initial_project_id: Option<u64>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
if self.status.is_offline() { if self.status.is_offline() {
return Task::ready(Err(anyhow!("room is offline"))); return Task::ready(Err(anyhow!("room is offline")));
@ -1166,8 +1158,8 @@ impl Room {
id: u64, id: u64,
language_registry: Arc<LanguageRegistry>, language_registry: Arc<LanguageRegistry>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<Model<Project>>> { ) -> Task<Result<Entity<Project>>> {
let client = self.client.clone(); let client = self.client.clone();
let user_store = self.user_store.clone(); let user_store = self.user_store.clone();
cx.emit(Event::RemoteProjectJoined { project_id: id }); cx.emit(Event::RemoteProjectJoined { project_id: id });
@ -1191,8 +1183,8 @@ impl Room {
pub fn share_project( pub fn share_project(
&mut self, &mut self,
project: Model<Project>, project: Entity<Project>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<u64>> { ) -> Task<Result<u64>> {
if let Some(project_id) = project.read(cx).remote_id() { if let Some(project_id) = project.read(cx).remote_id() {
return Task::ready(Ok(project_id)); return Task::ready(Ok(project_id));
@ -1229,8 +1221,8 @@ impl Room {
pub(crate) fn unshare_project( pub(crate) fn unshare_project(
&mut self, &mut self,
project: Model<Project>, project: Entity<Project>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Result<()> { ) -> Result<()> {
let project_id = match project.read(cx).remote_id() { let project_id = match project.read(cx).remote_id() {
Some(project_id) => project_id, Some(project_id) => project_id,
@ -1248,8 +1240,8 @@ impl Room {
pub(crate) fn set_location( pub(crate) fn set_location(
&mut self, &mut self,
project: Option<&Model<Project>>, project: Option<&Entity<Project>>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
if self.status.is_offline() { if self.status.is_offline() {
return Task::ready(Err(anyhow!("room is offline"))); return Task::ready(Err(anyhow!("room is offline")));
@ -1340,7 +1332,7 @@ impl Room {
} }
#[track_caller] #[track_caller]
pub fn share_microphone(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> { pub fn share_microphone(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
if self.status.is_offline() { if self.status.is_offline() {
return Task::ready(Err(anyhow!("room is offline"))); return Task::ready(Err(anyhow!("room is offline")));
} }
@ -1416,7 +1408,7 @@ impl Room {
}) })
} }
pub fn share_screen(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> { pub fn share_screen(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
if self.status.is_offline() { if self.status.is_offline() {
return Task::ready(Err(anyhow!("room is offline"))); return Task::ready(Err(anyhow!("room is offline")));
} else if self.is_screen_sharing() { } else if self.is_screen_sharing() {
@ -1497,7 +1489,7 @@ impl Room {
}) })
} }
pub fn toggle_mute(&mut self, cx: &mut ModelContext<Self>) { pub fn toggle_mute(&mut self, cx: &mut Context<Self>) {
if let Some(live_kit) = self.live_kit.as_mut() { if let Some(live_kit) = self.live_kit.as_mut() {
// When unmuting, undeafen if the user was deafened before. // When unmuting, undeafen if the user was deafened before.
let was_deafened = live_kit.deafened; let was_deafened = live_kit.deafened;
@ -1525,7 +1517,7 @@ impl Room {
} }
} }
pub fn toggle_deafen(&mut self, cx: &mut ModelContext<Self>) { pub fn toggle_deafen(&mut self, cx: &mut Context<Self>) {
if let Some(live_kit) = self.live_kit.as_mut() { if let Some(live_kit) = self.live_kit.as_mut() {
// When deafening, mute the microphone if it was not already muted. // When deafening, mute the microphone if it was not already muted.
// When un-deafening, unmute the microphone, unless it was explicitly muted. // When un-deafening, unmute the microphone, unless it was explicitly muted.
@ -1545,7 +1537,7 @@ impl Room {
} }
} }
pub fn unshare_screen(&mut self, cx: &mut ModelContext<Self>) -> Result<()> { pub fn unshare_screen(&mut self, cx: &mut Context<Self>) -> Result<()> {
if self.status.is_offline() { if self.status.is_offline() {
return Err(anyhow!("room is offline")); return Err(anyhow!("room is offline"));
} }
@ -1572,11 +1564,7 @@ impl Room {
} }
} }
fn set_deafened( fn set_deafened(&mut self, deafened: bool, cx: &mut Context<Self>) -> Option<Task<Result<()>>> {
&mut self,
deafened: bool,
cx: &mut ModelContext<Self>,
) -> Option<Task<Result<()>>> {
let live_kit = self.live_kit.as_mut()?; let live_kit = self.live_kit.as_mut()?;
cx.notify(); cx.notify();
@ -1606,11 +1594,7 @@ impl Room {
})) }))
} }
fn set_mute( fn set_mute(&mut self, should_mute: bool, cx: &mut Context<Room>) -> Option<Task<Result<()>>> {
&mut self,
should_mute: bool,
cx: &mut ModelContext<Room>,
) -> Option<Task<Result<()>>> {
let live_kit = self.live_kit.as_mut()?; let live_kit = self.live_kit.as_mut()?;
cx.notify(); cx.notify();
@ -1660,7 +1644,7 @@ struct LiveKitRoom {
} }
impl LiveKitRoom { impl LiveKitRoom {
fn stop_publishing(&mut self, cx: &mut ModelContext<Room>) { fn stop_publishing(&mut self, cx: &mut Context<Room>) {
if let LocalTrack::Published { if let LocalTrack::Published {
track_publication, .. track_publication, ..
} = mem::replace(&mut self.microphone_track, LocalTrack::None) } = mem::replace(&mut self.microphone_track, LocalTrack::None)

View file

@ -3,7 +3,7 @@ mod channel_chat;
mod channel_store; mod channel_store;
use client::{Client, UserStore}; use client::{Client, UserStore};
use gpui::{AppContext, Model}; use gpui::{App, Entity};
use std::sync::Arc; use std::sync::Arc;
pub use channel_buffer::{ChannelBuffer, ChannelBufferEvent, ACKNOWLEDGE_DEBOUNCE_INTERVAL}; pub use channel_buffer::{ChannelBuffer, ChannelBufferEvent, ACKNOWLEDGE_DEBOUNCE_INTERVAL};
@ -16,7 +16,7 @@ pub use channel_store::{Channel, ChannelEvent, ChannelMembership, ChannelStore};
#[cfg(test)] #[cfg(test)]
mod channel_store_tests; mod channel_store_tests;
pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) { pub fn init(client: &Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
channel_store::init(client, user_store, cx); channel_store::init(client, user_store, cx);
channel_buffer::init(&client.clone().into()); channel_buffer::init(&client.clone().into());
channel_chat::init(&client.clone().into()); channel_chat::init(&client.clone().into());

View file

@ -2,7 +2,7 @@ use crate::{Channel, ChannelStore};
use anyhow::Result; use anyhow::Result;
use client::{ChannelId, Client, Collaborator, UserStore, ZED_ALWAYS_ACTIVE}; use client::{ChannelId, Client, Collaborator, UserStore, ZED_ALWAYS_ACTIVE};
use collections::HashMap; use collections::HashMap;
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task}; use gpui::{App, AppContext as _, AsyncAppContext, Context, Entity, EventEmitter, Task};
use language::proto::serialize_version; use language::proto::serialize_version;
use rpc::{ use rpc::{
proto::{self, PeerId}, proto::{self, PeerId},
@ -23,9 +23,9 @@ pub struct ChannelBuffer {
pub channel_id: ChannelId, pub channel_id: ChannelId,
connected: bool, connected: bool,
collaborators: HashMap<PeerId, Collaborator>, collaborators: HashMap<PeerId, Collaborator>,
user_store: Model<UserStore>, user_store: Entity<UserStore>,
channel_store: Model<ChannelStore>, channel_store: Entity<ChannelStore>,
buffer: Model<language::Buffer>, buffer: Entity<language::Buffer>,
buffer_epoch: u64, buffer_epoch: u64,
client: Arc<Client>, client: Arc<Client>,
subscription: Option<client::Subscription>, subscription: Option<client::Subscription>,
@ -45,10 +45,10 @@ impl ChannelBuffer {
pub(crate) async fn new( pub(crate) async fn new(
channel: Arc<Channel>, channel: Arc<Channel>,
client: Arc<Client>, client: Arc<Client>,
user_store: Model<UserStore>, user_store: Entity<UserStore>,
channel_store: Model<ChannelStore>, channel_store: Entity<ChannelStore>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<Model<Self>> { ) -> Result<Entity<Self>> {
let response = client let response = client
.request(proto::JoinChannelBuffer { .request(proto::JoinChannelBuffer {
channel_id: channel.id.0, channel_id: channel.id.0,
@ -62,7 +62,7 @@ impl ChannelBuffer {
.map(language::proto::deserialize_operation) .map(language::proto::deserialize_operation)
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let buffer = cx.new_model(|cx| { let buffer = cx.new(|cx| {
let capability = channel_store.read(cx).channel_capability(channel.id); let capability = channel_store.read(cx).channel_capability(channel.id);
language::Buffer::remote(buffer_id, response.replica_id as u16, capability, base_text) language::Buffer::remote(buffer_id, response.replica_id as u16, capability, base_text)
})?; })?;
@ -70,7 +70,7 @@ impl ChannelBuffer {
let subscription = client.subscribe_to_entity(channel.id.0)?; let subscription = client.subscribe_to_entity(channel.id.0)?;
anyhow::Ok(cx.new_model(|cx| { anyhow::Ok(cx.new(|cx| {
cx.subscribe(&buffer, Self::on_buffer_update).detach(); cx.subscribe(&buffer, Self::on_buffer_update).detach();
cx.on_release(Self::release).detach(); cx.on_release(Self::release).detach();
let mut this = Self { let mut this = Self {
@ -81,7 +81,7 @@ impl ChannelBuffer {
collaborators: Default::default(), collaborators: Default::default(),
acknowledge_task: None, acknowledge_task: None,
channel_id: channel.id, channel_id: channel.id,
subscription: Some(subscription.set_model(&cx.handle(), &mut cx.to_async())), subscription: Some(subscription.set_model(&cx.model(), &mut cx.to_async())),
user_store, user_store,
channel_store, channel_store,
}; };
@ -90,7 +90,7 @@ impl ChannelBuffer {
})?) })?)
} }
fn release(&mut self, _: &mut AppContext) { fn release(&mut self, _: &mut App) {
if self.connected { if self.connected {
if let Some(task) = self.acknowledge_task.take() { if let Some(task) = self.acknowledge_task.take() {
task.detach(); task.detach();
@ -103,18 +103,18 @@ impl ChannelBuffer {
} }
} }
pub fn remote_id(&self, cx: &AppContext) -> BufferId { pub fn remote_id(&self, cx: &App) -> BufferId {
self.buffer.read(cx).remote_id() self.buffer.read(cx).remote_id()
} }
pub fn user_store(&self) -> &Model<UserStore> { pub fn user_store(&self) -> &Entity<UserStore> {
&self.user_store &self.user_store
} }
pub(crate) fn replace_collaborators( pub(crate) fn replace_collaborators(
&mut self, &mut self,
collaborators: Vec<proto::Collaborator>, collaborators: Vec<proto::Collaborator>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
let mut new_collaborators = HashMap::default(); let mut new_collaborators = HashMap::default();
for collaborator in collaborators { for collaborator in collaborators {
@ -136,7 +136,7 @@ impl ChannelBuffer {
} }
async fn handle_update_channel_buffer( async fn handle_update_channel_buffer(
this: Model<Self>, this: Entity<Self>,
update_channel_buffer: TypedEnvelope<proto::UpdateChannelBuffer>, update_channel_buffer: TypedEnvelope<proto::UpdateChannelBuffer>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
@ -157,7 +157,7 @@ impl ChannelBuffer {
} }
async fn handle_update_channel_buffer_collaborators( async fn handle_update_channel_buffer_collaborators(
this: Model<Self>, this: Entity<Self>,
message: TypedEnvelope<proto::UpdateChannelBufferCollaborators>, message: TypedEnvelope<proto::UpdateChannelBufferCollaborators>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
@ -170,9 +170,9 @@ impl ChannelBuffer {
fn on_buffer_update( fn on_buffer_update(
&mut self, &mut self,
_: Model<language::Buffer>, _: Entity<language::Buffer>,
event: &language::BufferEvent, event: &language::BufferEvent,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
match event { match event {
language::BufferEvent::Operation { language::BufferEvent::Operation {
@ -201,7 +201,7 @@ impl ChannelBuffer {
} }
} }
pub fn acknowledge_buffer_version(&mut self, cx: &mut ModelContext<'_, ChannelBuffer>) { pub fn acknowledge_buffer_version(&mut self, cx: &mut Context<'_, ChannelBuffer>) {
let buffer = self.buffer.read(cx); let buffer = self.buffer.read(cx);
let version = buffer.version(); let version = buffer.version();
let buffer_id = buffer.remote_id().into(); let buffer_id = buffer.remote_id().into();
@ -227,7 +227,7 @@ impl ChannelBuffer {
self.buffer_epoch self.buffer_epoch
} }
pub fn buffer(&self) -> Model<language::Buffer> { pub fn buffer(&self) -> Entity<language::Buffer> {
self.buffer.clone() self.buffer.clone()
} }
@ -235,14 +235,14 @@ impl ChannelBuffer {
&self.collaborators &self.collaborators
} }
pub fn channel(&self, cx: &AppContext) -> Option<Arc<Channel>> { pub fn channel(&self, cx: &App) -> Option<Arc<Channel>> {
self.channel_store self.channel_store
.read(cx) .read(cx)
.channel_for_id(self.channel_id) .channel_for_id(self.channel_id)
.cloned() .cloned()
} }
pub(crate) fn disconnect(&mut self, cx: &mut ModelContext<Self>) { pub(crate) fn disconnect(&mut self, cx: &mut Context<Self>) {
log::info!("channel buffer {} disconnected", self.channel_id); log::info!("channel buffer {} disconnected", self.channel_id);
if self.connected { if self.connected {
self.connected = false; self.connected = false;
@ -252,7 +252,7 @@ impl ChannelBuffer {
} }
} }
pub(crate) fn channel_changed(&mut self, cx: &mut ModelContext<Self>) { pub(crate) fn channel_changed(&mut self, cx: &mut Context<Self>) {
cx.emit(ChannelBufferEvent::ChannelChanged); cx.emit(ChannelBufferEvent::ChannelChanged);
cx.notify() cx.notify()
} }
@ -261,7 +261,7 @@ impl ChannelBuffer {
self.connected self.connected
} }
pub fn replica_id(&self, cx: &AppContext) -> u16 { pub fn replica_id(&self, cx: &App) -> u16 {
self.buffer.read(cx).replica_id() self.buffer.read(cx).replica_id()
} }
} }

View file

@ -8,7 +8,7 @@ use client::{
use collections::HashSet; use collections::HashSet;
use futures::lock::Mutex; use futures::lock::Mutex;
use gpui::{ use gpui::{
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel, App, AppContext as _, AsyncAppContext, Context, Entity, EventEmitter, Task, WeakEntity,
}; };
use rand::prelude::*; use rand::prelude::*;
use rpc::AnyProtoClient; use rpc::AnyProtoClient;
@ -24,12 +24,12 @@ pub struct ChannelChat {
pub channel_id: ChannelId, pub channel_id: ChannelId,
messages: SumTree<ChannelMessage>, messages: SumTree<ChannelMessage>,
acknowledged_message_ids: HashSet<u64>, acknowledged_message_ids: HashSet<u64>,
channel_store: Model<ChannelStore>, channel_store: Entity<ChannelStore>,
loaded_all_messages: bool, loaded_all_messages: bool,
last_acknowledged_id: Option<u64>, last_acknowledged_id: Option<u64>,
next_pending_message_id: usize, next_pending_message_id: usize,
first_loaded_message_id: Option<u64>, first_loaded_message_id: Option<u64>,
user_store: Model<UserStore>, user_store: Entity<UserStore>,
rpc: Arc<Client>, rpc: Arc<Client>,
outgoing_messages_lock: Arc<Mutex<()>>, outgoing_messages_lock: Arc<Mutex<()>>,
rng: StdRng, rng: StdRng,
@ -105,11 +105,11 @@ pub fn init(client: &AnyProtoClient) {
impl ChannelChat { impl ChannelChat {
pub async fn new( pub async fn new(
channel: Arc<Channel>, channel: Arc<Channel>,
channel_store: Model<ChannelStore>, channel_store: Entity<ChannelStore>,
user_store: Model<UserStore>, user_store: Entity<UserStore>,
client: Arc<Client>, client: Arc<Client>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<Model<Self>> { ) -> Result<Entity<Self>> {
let channel_id = channel.id; let channel_id = channel.id;
let subscription = client.subscribe_to_entity(channel_id.0).unwrap(); let subscription = client.subscribe_to_entity(channel_id.0).unwrap();
@ -119,7 +119,7 @@ impl ChannelChat {
}) })
.await?; .await?;
let handle = cx.new_model(|cx| { let handle = cx.new(|cx| {
cx.on_release(Self::release).detach(); cx.on_release(Self::release).detach();
Self { Self {
channel_id: channel.id, channel_id: channel.id,
@ -134,7 +134,7 @@ impl ChannelChat {
last_acknowledged_id: None, last_acknowledged_id: None,
rng: StdRng::from_entropy(), rng: StdRng::from_entropy(),
first_loaded_message_id: None, first_loaded_message_id: None,
_subscription: subscription.set_model(&cx.handle(), &mut cx.to_async()), _subscription: subscription.set_model(&cx.model(), &mut cx.to_async()),
} }
})?; })?;
Self::handle_loaded_messages( Self::handle_loaded_messages(
@ -149,7 +149,7 @@ impl ChannelChat {
Ok(handle) Ok(handle)
} }
fn release(&mut self, _: &mut AppContext) { fn release(&mut self, _: &mut App) {
self.rpc self.rpc
.send(proto::LeaveChannelChat { .send(proto::LeaveChannelChat {
channel_id: self.channel_id.0, channel_id: self.channel_id.0,
@ -157,7 +157,7 @@ impl ChannelChat {
.log_err(); .log_err();
} }
pub fn channel(&self, cx: &AppContext) -> Option<Arc<Channel>> { pub fn channel(&self, cx: &App) -> Option<Arc<Channel>> {
self.channel_store self.channel_store
.read(cx) .read(cx)
.channel_for_id(self.channel_id) .channel_for_id(self.channel_id)
@ -171,7 +171,7 @@ impl ChannelChat {
pub fn send_message( pub fn send_message(
&mut self, &mut self,
message: MessageParams, message: MessageParams,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Result<Task<Result<u64>>> { ) -> Result<Task<Result<u64>>> {
if message.text.trim().is_empty() { if message.text.trim().is_empty() {
Err(anyhow!("message body can't be empty"))?; Err(anyhow!("message body can't be empty"))?;
@ -231,7 +231,7 @@ impl ChannelChat {
})) }))
} }
pub fn remove_message(&mut self, id: u64, cx: &mut ModelContext<Self>) -> Task<Result<()>> { pub fn remove_message(&mut self, id: u64, cx: &mut Context<Self>) -> Task<Result<()>> {
let response = self.rpc.request(proto::RemoveChannelMessage { let response = self.rpc.request(proto::RemoveChannelMessage {
channel_id: self.channel_id.0, channel_id: self.channel_id.0,
message_id: id, message_id: id,
@ -249,7 +249,7 @@ impl ChannelChat {
&mut self, &mut self,
id: u64, id: u64,
message: MessageParams, message: MessageParams,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Result<Task<Result<()>>> { ) -> Result<Task<Result<()>>> {
self.message_update( self.message_update(
ChannelMessageId::Saved(id), ChannelMessageId::Saved(id),
@ -274,7 +274,7 @@ impl ChannelChat {
})) }))
} }
pub fn load_more_messages(&mut self, cx: &mut ModelContext<Self>) -> Option<Task<Option<()>>> { pub fn load_more_messages(&mut self, cx: &mut Context<Self>) -> Option<Task<Option<()>>> {
if self.loaded_all_messages { if self.loaded_all_messages {
return None; return None;
} }
@ -323,7 +323,7 @@ impl ChannelChat {
/// ///
/// For now, we always maintain a suffix of the channel's messages. /// For now, we always maintain a suffix of the channel's messages.
pub async fn load_history_since_message( pub async fn load_history_since_message(
chat: Model<Self>, chat: Entity<Self>,
message_id: u64, message_id: u64,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Option<usize> { ) -> Option<usize> {
@ -357,7 +357,7 @@ impl ChannelChat {
} }
} }
pub fn acknowledge_last_message(&mut self, cx: &mut ModelContext<Self>) { pub fn acknowledge_last_message(&mut self, cx: &mut Context<Self>) {
if let ChannelMessageId::Saved(latest_message_id) = self.messages.summary().max_id { if let ChannelMessageId::Saved(latest_message_id) = self.messages.summary().max_id {
if self if self
.last_acknowledged_id .last_acknowledged_id
@ -378,8 +378,8 @@ impl ChannelChat {
} }
async fn handle_loaded_messages( async fn handle_loaded_messages(
this: WeakModel<Self>, this: WeakEntity<Self>,
user_store: Model<UserStore>, user_store: Entity<UserStore>,
rpc: Arc<Client>, rpc: Arc<Client>,
proto_messages: Vec<proto::ChannelMessage>, proto_messages: Vec<proto::ChannelMessage>,
loaded_all_messages: bool, loaded_all_messages: bool,
@ -437,7 +437,7 @@ impl ChannelChat {
Ok(()) Ok(())
} }
pub fn rejoin(&mut self, cx: &mut ModelContext<Self>) { pub fn rejoin(&mut self, cx: &mut Context<Self>) {
let user_store = self.user_store.clone(); let user_store = self.user_store.clone();
let rpc = self.rpc.clone(); let rpc = self.rpc.clone();
let channel_id = self.channel_id; let channel_id = self.channel_id;
@ -527,7 +527,7 @@ impl ChannelChat {
} }
async fn handle_message_sent( async fn handle_message_sent(
this: Model<Self>, this: Entity<Self>,
message: TypedEnvelope<proto::ChannelMessageSent>, message: TypedEnvelope<proto::ChannelMessageSent>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
@ -551,7 +551,7 @@ impl ChannelChat {
} }
async fn handle_message_removed( async fn handle_message_removed(
this: Model<Self>, this: Entity<Self>,
message: TypedEnvelope<proto::RemoveChannelMessage>, message: TypedEnvelope<proto::RemoveChannelMessage>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
@ -562,7 +562,7 @@ impl ChannelChat {
} }
async fn handle_message_updated( async fn handle_message_updated(
this: Model<Self>, this: Entity<Self>,
message: TypedEnvelope<proto::ChannelMessageUpdate>, message: TypedEnvelope<proto::ChannelMessageUpdate>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
@ -586,7 +586,7 @@ impl ChannelChat {
Ok(()) Ok(())
} }
fn insert_messages(&mut self, messages: SumTree<ChannelMessage>, cx: &mut ModelContext<Self>) { fn insert_messages(&mut self, messages: SumTree<ChannelMessage>, cx: &mut Context<Self>) {
if let Some((first_message, last_message)) = messages.first().zip(messages.last()) { if let Some((first_message, last_message)) = messages.first().zip(messages.last()) {
let nonces = messages let nonces = messages
.cursor::<()>(&()) .cursor::<()>(&())
@ -645,7 +645,7 @@ impl ChannelChat {
} }
} }
fn message_removed(&mut self, id: u64, cx: &mut ModelContext<Self>) { fn message_removed(&mut self, id: u64, cx: &mut Context<Self>) {
let mut cursor = self.messages.cursor::<ChannelMessageId>(&()); let mut cursor = self.messages.cursor::<ChannelMessageId>(&());
let mut messages = cursor.slice(&ChannelMessageId::Saved(id), Bias::Left, &()); let mut messages = cursor.slice(&ChannelMessageId::Saved(id), Bias::Left, &());
if let Some(item) = cursor.item() { if let Some(item) = cursor.item() {
@ -683,7 +683,7 @@ impl ChannelChat {
body: String, body: String,
mentions: Vec<(Range<usize>, u64)>, mentions: Vec<(Range<usize>, u64)>,
edited_at: Option<OffsetDateTime>, edited_at: Option<OffsetDateTime>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
let mut cursor = self.messages.cursor::<ChannelMessageId>(&()); let mut cursor = self.messages.cursor::<ChannelMessageId>(&());
let mut messages = cursor.slice(&id, Bias::Left, &()); let mut messages = cursor.slice(&id, Bias::Left, &());
@ -712,7 +712,7 @@ impl ChannelChat {
async fn messages_from_proto( async fn messages_from_proto(
proto_messages: Vec<proto::ChannelMessage>, proto_messages: Vec<proto::ChannelMessage>,
user_store: &Model<UserStore>, user_store: &Entity<UserStore>,
cx: &mut AsyncAppContext, cx: &mut AsyncAppContext,
) -> Result<SumTree<ChannelMessage>> { ) -> Result<SumTree<ChannelMessage>> {
let messages = ChannelMessage::from_proto_vec(proto_messages, user_store, cx).await?; let messages = ChannelMessage::from_proto_vec(proto_messages, user_store, cx).await?;
@ -724,7 +724,7 @@ async fn messages_from_proto(
impl ChannelMessage { impl ChannelMessage {
pub async fn from_proto( pub async fn from_proto(
message: proto::ChannelMessage, message: proto::ChannelMessage,
user_store: &Model<UserStore>, user_store: &Entity<UserStore>,
cx: &mut AsyncAppContext, cx: &mut AsyncAppContext,
) -> Result<Self> { ) -> Result<Self> {
let sender = user_store let sender = user_store
@ -769,7 +769,7 @@ impl ChannelMessage {
pub async fn from_proto_vec( pub async fn from_proto_vec(
proto_messages: Vec<proto::ChannelMessage>, proto_messages: Vec<proto::ChannelMessage>,
user_store: &Model<UserStore>, user_store: &Entity<UserStore>,
cx: &mut AsyncAppContext, cx: &mut AsyncAppContext,
) -> Result<Vec<Self>> { ) -> Result<Vec<Self>> {
let unique_user_ids = proto_messages let unique_user_ids = proto_messages

View file

@ -7,8 +7,8 @@ use client::{ChannelId, Client, ClientSettings, Subscription, User, UserId, User
use collections::{hash_map, HashMap, HashSet}; use collections::{hash_map, HashMap, HashSet};
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt}; use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
use gpui::{ use gpui::{
AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, SharedString, App, AppContext as _, AsyncAppContext, Context, Entity, EventEmitter, Global, SharedString,
Task, WeakModel, Task, WeakEntity,
}; };
use language::Capability; use language::Capability;
use rpc::{ use rpc::{
@ -21,9 +21,8 @@ use util::{maybe, ResultExt};
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) { pub fn init(client: &Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
let channel_store = let channel_store = cx.new(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
cx.new_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
cx.set_global(GlobalChannelStore(channel_store)); cx.set_global(GlobalChannelStore(channel_store));
} }
@ -44,7 +43,7 @@ pub struct ChannelStore {
opened_chats: HashMap<ChannelId, OpenedModelHandle<ChannelChat>>, opened_chats: HashMap<ChannelId, OpenedModelHandle<ChannelChat>>,
client: Arc<Client>, client: Arc<Client>,
did_subscribe: bool, did_subscribe: bool,
user_store: Model<UserStore>, user_store: Entity<UserStore>,
_rpc_subscriptions: [Subscription; 2], _rpc_subscriptions: [Subscription; 2],
_watch_connection_status: Task<Option<()>>, _watch_connection_status: Task<Option<()>>,
disconnect_channel_buffers_task: Option<Task<()>>, disconnect_channel_buffers_task: Option<Task<()>>,
@ -69,7 +68,7 @@ pub struct ChannelState {
} }
impl Channel { impl Channel {
pub fn link(&self, cx: &AppContext) -> String { pub fn link(&self, cx: &App) -> String {
format!( format!(
"{}/channel/{}-{}", "{}/channel/{}-{}",
ClientSettings::get_global(cx).server_url, ClientSettings::get_global(cx).server_url,
@ -78,7 +77,7 @@ impl Channel {
) )
} }
pub fn notes_link(&self, heading: Option<String>, cx: &AppContext) -> String { pub fn notes_link(&self, heading: Option<String>, cx: &App) -> String {
self.link(cx) self.link(cx)
+ "/notes" + "/notes"
+ &heading + &heading
@ -144,24 +143,20 @@ pub enum ChannelEvent {
impl EventEmitter<ChannelEvent> for ChannelStore {} impl EventEmitter<ChannelEvent> for ChannelStore {}
enum OpenedModelHandle<E> { enum OpenedModelHandle<E> {
Open(WeakModel<E>), Open(WeakEntity<E>),
Loading(Shared<Task<Result<Model<E>, Arc<anyhow::Error>>>>), Loading(Shared<Task<Result<Entity<E>, Arc<anyhow::Error>>>>),
} }
struct GlobalChannelStore(Model<ChannelStore>); struct GlobalChannelStore(Entity<ChannelStore>);
impl Global for GlobalChannelStore {} impl Global for GlobalChannelStore {}
impl ChannelStore { impl ChannelStore {
pub fn global(cx: &AppContext) -> Model<Self> { pub fn global(cx: &App) -> Entity<Self> {
cx.global::<GlobalChannelStore>().0.clone() cx.global::<GlobalChannelStore>().0.clone()
} }
pub fn new( pub fn new(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut Context<Self>) -> Self {
client: Arc<Client>,
user_store: Model<UserStore>,
cx: &mut ModelContext<Self>,
) -> Self {
let rpc_subscriptions = [ let rpc_subscriptions = [
client.add_message_handler(cx.weak_model(), Self::handle_update_channels), client.add_message_handler(cx.weak_model(), Self::handle_update_channels),
client.add_message_handler(cx.weak_model(), Self::handle_update_user_channels), client.add_message_handler(cx.weak_model(), Self::handle_update_user_channels),
@ -295,7 +290,7 @@ impl ChannelStore {
self.channel_index.by_id().get(&channel_id) self.channel_index.by_id().get(&channel_id)
} }
pub fn has_open_channel_buffer(&self, channel_id: ChannelId, _cx: &AppContext) -> bool { pub fn has_open_channel_buffer(&self, channel_id: ChannelId, _cx: &App) -> bool {
if let Some(buffer) = self.opened_buffers.get(&channel_id) { if let Some(buffer) = self.opened_buffers.get(&channel_id) {
if let OpenedModelHandle::Open(buffer) = buffer { if let OpenedModelHandle::Open(buffer) = buffer {
return buffer.upgrade().is_some(); return buffer.upgrade().is_some();
@ -307,11 +302,11 @@ impl ChannelStore {
pub fn open_channel_buffer( pub fn open_channel_buffer(
&mut self, &mut self,
channel_id: ChannelId, channel_id: ChannelId,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<Model<ChannelBuffer>>> { ) -> Task<Result<Entity<ChannelBuffer>>> {
let client = self.client.clone(); let client = self.client.clone();
let user_store = self.user_store.clone(); let user_store = self.user_store.clone();
let channel_store = cx.handle(); let channel_store = cx.model();
self.open_channel_resource( self.open_channel_resource(
channel_id, channel_id,
|this| &mut this.opened_buffers, |this| &mut this.opened_buffers,
@ -323,7 +318,7 @@ impl ChannelStore {
pub fn fetch_channel_messages( pub fn fetch_channel_messages(
&self, &self,
message_ids: Vec<u64>, message_ids: Vec<u64>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<Vec<ChannelMessage>>> { ) -> Task<Result<Vec<ChannelMessage>>> {
let request = if message_ids.is_empty() { let request = if message_ids.is_empty() {
None None
@ -384,7 +379,7 @@ impl ChannelStore {
&mut self, &mut self,
channel_id: ChannelId, channel_id: ChannelId,
message_id: u64, message_id: u64,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
self.channel_states self.channel_states
.entry(channel_id) .entry(channel_id)
@ -397,7 +392,7 @@ impl ChannelStore {
&mut self, &mut self,
channel_id: ChannelId, channel_id: ChannelId,
message_id: u64, message_id: u64,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
self.channel_states self.channel_states
.entry(channel_id) .entry(channel_id)
@ -411,7 +406,7 @@ impl ChannelStore {
channel_id: ChannelId, channel_id: ChannelId,
epoch: u64, epoch: u64,
version: &clock::Global, version: &clock::Global,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
self.channel_states self.channel_states
.entry(channel_id) .entry(channel_id)
@ -425,7 +420,7 @@ impl ChannelStore {
channel_id: ChannelId, channel_id: ChannelId,
epoch: u64, epoch: u64,
version: &clock::Global, version: &clock::Global,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
self.channel_states self.channel_states
.entry(channel_id) .entry(channel_id)
@ -437,11 +432,11 @@ impl ChannelStore {
pub fn open_channel_chat( pub fn open_channel_chat(
&mut self, &mut self,
channel_id: ChannelId, channel_id: ChannelId,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<Model<ChannelChat>>> { ) -> Task<Result<Entity<ChannelChat>>> {
let client = self.client.clone(); let client = self.client.clone();
let user_store = self.user_store.clone(); let user_store = self.user_store.clone();
let this = cx.handle(); let this = cx.model();
self.open_channel_resource( self.open_channel_resource(
channel_id, channel_id,
|this| &mut this.opened_chats, |this| &mut this.opened_chats,
@ -460,11 +455,11 @@ impl ChannelStore {
channel_id: ChannelId, channel_id: ChannelId,
get_map: fn(&mut Self) -> &mut HashMap<ChannelId, OpenedModelHandle<T>>, get_map: fn(&mut Self) -> &mut HashMap<ChannelId, OpenedModelHandle<T>>,
load: F, load: F,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<Model<T>>> ) -> Task<Result<Entity<T>>>
where where
F: 'static + FnOnce(Arc<Channel>, AsyncAppContext) -> Fut, F: 'static + FnOnce(Arc<Channel>, AsyncAppContext) -> Fut,
Fut: Future<Output = Result<Model<T>>>, Fut: Future<Output = Result<Entity<T>>>,
T: 'static, T: 'static,
{ {
let task = loop { let task = loop {
@ -572,7 +567,7 @@ impl ChannelStore {
&self, &self,
name: &str, name: &str,
parent_id: Option<ChannelId>, parent_id: Option<ChannelId>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<ChannelId>> { ) -> Task<Result<ChannelId>> {
let client = self.client.clone(); let client = self.client.clone();
let name = name.trim_start_matches('#').to_owned(); let name = name.trim_start_matches('#').to_owned();
@ -614,7 +609,7 @@ impl ChannelStore {
&mut self, &mut self,
channel_id: ChannelId, channel_id: ChannelId,
to: ChannelId, to: ChannelId,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
let client = self.client.clone(); let client = self.client.clone();
cx.spawn(move |_, _| async move { cx.spawn(move |_, _| async move {
@ -633,7 +628,7 @@ impl ChannelStore {
&mut self, &mut self,
channel_id: ChannelId, channel_id: ChannelId,
visibility: ChannelVisibility, visibility: ChannelVisibility,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
let client = self.client.clone(); let client = self.client.clone();
cx.spawn(move |_, _| async move { cx.spawn(move |_, _| async move {
@ -653,7 +648,7 @@ impl ChannelStore {
channel_id: ChannelId, channel_id: ChannelId,
user_id: UserId, user_id: UserId,
role: proto::ChannelRole, role: proto::ChannelRole,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
if !self.outgoing_invites.insert((channel_id, user_id)) { if !self.outgoing_invites.insert((channel_id, user_id)) {
return Task::ready(Err(anyhow!("invite request already in progress"))); return Task::ready(Err(anyhow!("invite request already in progress")));
@ -685,7 +680,7 @@ impl ChannelStore {
&mut self, &mut self,
channel_id: ChannelId, channel_id: ChannelId,
user_id: u64, user_id: u64,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
if !self.outgoing_invites.insert((channel_id, user_id)) { if !self.outgoing_invites.insert((channel_id, user_id)) {
return Task::ready(Err(anyhow!("invite request already in progress"))); return Task::ready(Err(anyhow!("invite request already in progress")));
@ -715,7 +710,7 @@ impl ChannelStore {
channel_id: ChannelId, channel_id: ChannelId,
user_id: UserId, user_id: UserId,
role: proto::ChannelRole, role: proto::ChannelRole,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
if !self.outgoing_invites.insert((channel_id, user_id)) { if !self.outgoing_invites.insert((channel_id, user_id)) {
return Task::ready(Err(anyhow!("member request already in progress"))); return Task::ready(Err(anyhow!("member request already in progress")));
@ -746,7 +741,7 @@ impl ChannelStore {
&mut self, &mut self,
channel_id: ChannelId, channel_id: ChannelId,
new_name: &str, new_name: &str,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
let client = self.client.clone(); let client = self.client.clone();
let name = new_name.to_string(); let name = new_name.to_string();
@ -783,7 +778,7 @@ impl ChannelStore {
&mut self, &mut self,
channel_id: ChannelId, channel_id: ChannelId,
accept: bool, accept: bool,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
let client = self.client.clone(); let client = self.client.clone();
cx.background_executor().spawn(async move { cx.background_executor().spawn(async move {
@ -801,7 +796,7 @@ impl ChannelStore {
channel_id: ChannelId, channel_id: ChannelId,
query: String, query: String,
limit: u16, limit: u16,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<Vec<ChannelMembership>>> { ) -> Task<Result<Vec<ChannelMembership>>> {
let client = self.client.clone(); let client = self.client.clone();
let user_store = self.user_store.downgrade(); let user_store = self.user_store.downgrade();
@ -851,7 +846,7 @@ impl ChannelStore {
} }
async fn handle_update_channels( async fn handle_update_channels(
this: Model<Self>, this: Entity<Self>,
message: TypedEnvelope<proto::UpdateChannels>, message: TypedEnvelope<proto::UpdateChannels>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
@ -864,7 +859,7 @@ impl ChannelStore {
} }
async fn handle_update_user_channels( async fn handle_update_user_channels(
this: Model<Self>, this: Entity<Self>,
message: TypedEnvelope<proto::UpdateUserChannels>, message: TypedEnvelope<proto::UpdateUserChannels>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
@ -896,7 +891,7 @@ impl ChannelStore {
}) })
} }
fn handle_connect(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> { fn handle_connect(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
self.channel_index.clear(); self.channel_index.clear();
self.channel_invitations.clear(); self.channel_invitations.clear();
self.channel_participants.clear(); self.channel_participants.clear();
@ -1011,7 +1006,7 @@ impl ChannelStore {
}) })
} }
fn handle_disconnect(&mut self, wait_for_reconnect: bool, cx: &mut ModelContext<Self>) { fn handle_disconnect(&mut self, wait_for_reconnect: bool, cx: &mut Context<Self>) {
cx.notify(); cx.notify();
self.did_subscribe = false; self.did_subscribe = false;
self.disconnect_channel_buffers_task.get_or_insert_with(|| { self.disconnect_channel_buffers_task.get_or_insert_with(|| {
@ -1039,7 +1034,7 @@ impl ChannelStore {
pub(crate) fn update_channels( pub(crate) fn update_channels(
&mut self, &mut self,
payload: proto::UpdateChannels, payload: proto::UpdateChannels,
cx: &mut ModelContext<ChannelStore>, cx: &mut Context<ChannelStore>,
) -> Option<Task<Result<()>>> { ) -> Option<Task<Result<()>>> {
if !payload.remove_channel_invitations.is_empty() { if !payload.remove_channel_invitations.is_empty() {
self.channel_invitations self.channel_invitations

View file

@ -3,13 +3,13 @@ use crate::channel_chat::ChannelChatEvent;
use super::*; use super::*;
use client::{test::FakeServer, Client, UserStore}; use client::{test::FakeServer, Client, UserStore};
use clock::FakeSystemClock; use clock::FakeSystemClock;
use gpui::{AppContext, Context, Model, SemanticVersion, TestAppContext}; use gpui::{App, AppContext as _, Entity, SemanticVersion, TestAppContext};
use http_client::FakeHttpClient; use http_client::FakeHttpClient;
use rpc::proto::{self}; use rpc::proto::{self};
use settings::SettingsStore; use settings::SettingsStore;
#[gpui::test] #[gpui::test]
fn test_update_channels(cx: &mut AppContext) { fn test_update_channels(cx: &mut App) {
let channel_store = init_test(cx); let channel_store = init_test(cx);
update_channels( update_channels(
@ -77,7 +77,7 @@ fn test_update_channels(cx: &mut AppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_dangling_channel_paths(cx: &mut AppContext) { fn test_dangling_channel_paths(cx: &mut App) {
let channel_store = init_test(cx); let channel_store = init_test(cx);
update_channels( update_channels(
@ -343,7 +343,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
}); });
} }
fn init_test(cx: &mut AppContext) -> Model<ChannelStore> { fn init_test(cx: &mut App) -> Entity<ChannelStore> {
let settings_store = SettingsStore::test(cx); let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store); cx.set_global(settings_store);
release_channel::init(SemanticVersion::default(), cx); release_channel::init(SemanticVersion::default(), cx);
@ -352,7 +352,7 @@ fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
let clock = Arc::new(FakeSystemClock::new()); let clock = Arc::new(FakeSystemClock::new());
let http = FakeHttpClient::with_404_response(); let http = FakeHttpClient::with_404_response();
let client = Client::new(clock, http.clone(), cx); let client = Client::new(clock, http.clone(), cx);
let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx)); let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
client::init(&client, cx); client::init(&client, cx);
crate::init(&client, user_store, cx); crate::init(&client, user_store, cx);
@ -361,9 +361,9 @@ fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
} }
fn update_channels( fn update_channels(
channel_store: &Model<ChannelStore>, channel_store: &Entity<ChannelStore>,
message: proto::UpdateChannels, message: proto::UpdateChannels,
cx: &mut AppContext, cx: &mut App,
) { ) {
let task = channel_store.update(cx, |store, cx| store.update_channels(message, cx)); let task = channel_store.update(cx, |store, cx| store.update_channels(message, cx));
assert!(task.is_none()); assert!(task.is_none());
@ -371,9 +371,9 @@ fn update_channels(
#[track_caller] #[track_caller]
fn assert_channels( fn assert_channels(
channel_store: &Model<ChannelStore>, channel_store: &Entity<ChannelStore>,
expected_channels: &[(usize, String)], expected_channels: &[(usize, String)],
cx: &mut AppContext, cx: &mut App,
) { ) {
let actual = channel_store.update(cx, |store, _| { let actual = channel_store.update(cx, |store, _| {
store store

View file

@ -3,7 +3,7 @@
allow(dead_code) allow(dead_code)
)] )]
use anyhow::{Context, Result}; use anyhow::{Context as _, Result};
use clap::Parser; use clap::Parser;
use cli::{ipc::IpcOneShotServer, CliRequest, CliResponse, IpcHandshake}; use cli::{ipc::IpcOneShotServer, CliRequest, CliResponse, IpcHandshake};
use collections::HashMap; use collections::HashMap;
@ -536,7 +536,7 @@ mod windows {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
mod mac_os { mod mac_os {
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context as _, Result};
use core_foundation::{ use core_foundation::{
array::{CFArray, CFIndex}, array::{CFArray, CFIndex},
string::kCFStringEncodingUTF8, string::kCFStringEncodingUTF8,

View file

@ -19,7 +19,7 @@ use futures::{
channel::oneshot, future::BoxFuture, AsyncReadExt, FutureExt, SinkExt, Stream, StreamExt, channel::oneshot, future::BoxFuture, AsyncReadExt, FutureExt, SinkExt, Stream, StreamExt,
TryFutureExt as _, TryStreamExt, TryFutureExt as _, TryStreamExt,
}; };
use gpui::{actions, AppContext, AsyncAppContext, Global, Model, Task, WeakModel}; use gpui::{actions, App, AsyncAppContext, Entity, Global, Task, WeakEntity};
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl}; use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use parking_lot::RwLock; use parking_lot::RwLock;
use postage::watch; use postage::watch;
@ -104,7 +104,7 @@ impl Settings for ClientSettings {
type FileContent = ClientSettingsContent; type FileContent = ClientSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> { fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
let mut result = sources.json_merge::<Self>()?; let mut result = sources.json_merge::<Self>()?;
if let Some(server_url) = &*ZED_SERVER_URL { if let Some(server_url) = &*ZED_SERVER_URL {
result.server_url.clone_from(server_url) result.server_url.clone_from(server_url)
@ -128,7 +128,7 @@ impl Settings for ProxySettings {
type FileContent = ProxySettingsContent; type FileContent = ProxySettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> { fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
Ok(Self { Ok(Self {
proxy: sources proxy: sources
.user .user
@ -139,13 +139,13 @@ impl Settings for ProxySettings {
} }
} }
pub fn init_settings(cx: &mut AppContext) { pub fn init_settings(cx: &mut App) {
TelemetrySettings::register(cx); TelemetrySettings::register(cx);
ClientSettings::register(cx); ClientSettings::register(cx);
ProxySettings::register(cx); ProxySettings::register(cx);
} }
pub fn init(client: &Arc<Client>, cx: &mut AppContext) { pub fn init(client: &Arc<Client>, cx: &mut App) {
let client = Arc::downgrade(client); let client = Arc::downgrade(client);
cx.on_action({ cx.on_action({
let client = client.clone(); let client = client.clone();
@ -380,7 +380,7 @@ pub struct PendingEntitySubscription<T: 'static> {
} }
impl<T: 'static> PendingEntitySubscription<T> { impl<T: 'static> PendingEntitySubscription<T> {
pub fn set_model(mut self, model: &Model<T>, cx: &AsyncAppContext) -> Subscription { pub fn set_model(mut self, model: &Entity<T>, cx: &AsyncAppContext) -> Subscription {
self.consumed = true; self.consumed = true;
let mut handlers = self.client.handler_set.lock(); let mut handlers = self.client.handler_set.lock();
let id = (TypeId::of::<T>(), self.remote_id); let id = (TypeId::of::<T>(), self.remote_id);
@ -456,7 +456,7 @@ impl settings::Settings for TelemetrySettings {
type FileContent = TelemetrySettingsContent; type FileContent = TelemetrySettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> { fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
Ok(Self { Ok(Self {
diagnostics: sources diagnostics: sources
.user .user
@ -483,7 +483,7 @@ impl Client {
pub fn new( pub fn new(
clock: Arc<dyn SystemClock>, clock: Arc<dyn SystemClock>,
http: Arc<HttpClientWithUrl>, http: Arc<HttpClientWithUrl>,
cx: &mut AppContext, cx: &mut App,
) -> Arc<Self> { ) -> Arc<Self> {
let use_zed_development_auth = match ReleaseChannel::try_global(cx) { let use_zed_development_auth = match ReleaseChannel::try_global(cx) {
Some(ReleaseChannel::Dev) => *ZED_DEVELOPMENT_AUTH, Some(ReleaseChannel::Dev) => *ZED_DEVELOPMENT_AUTH,
@ -518,7 +518,7 @@ impl Client {
}) })
} }
pub fn production(cx: &mut AppContext) -> Arc<Self> { pub fn production(cx: &mut App) -> Arc<Self> {
let clock = Arc::new(clock::RealSystemClock); let clock = Arc::new(clock::RealSystemClock);
let http = Arc::new(HttpClientWithUrl::new_uri( let http = Arc::new(HttpClientWithUrl::new_uri(
cx.http_client(), cx.http_client(),
@ -576,10 +576,10 @@ impl Client {
self self
} }
pub fn global(cx: &AppContext) -> Arc<Self> { pub fn global(cx: &App) -> Arc<Self> {
cx.global::<GlobalClient>().0.clone() cx.global::<GlobalClient>().0.clone()
} }
pub fn set_global(client: Arc<Client>, cx: &mut AppContext) { pub fn set_global(client: Arc<Client>, cx: &mut App) {
cx.set_global(GlobalClient(client)) cx.set_global(GlobalClient(client))
} }
@ -678,13 +678,13 @@ impl Client {
#[track_caller] #[track_caller]
pub fn add_message_handler<M, E, H, F>( pub fn add_message_handler<M, E, H, F>(
self: &Arc<Self>, self: &Arc<Self>,
entity: WeakModel<E>, entity: WeakEntity<E>,
handler: H, handler: H,
) -> Subscription ) -> Subscription
where where
M: EnvelopedMessage, M: EnvelopedMessage,
E: 'static, E: 'static,
H: 'static + Sync + Fn(Model<E>, TypedEnvelope<M>, AsyncAppContext) -> F + Send + Sync, H: 'static + Sync + Fn(Entity<E>, TypedEnvelope<M>, AsyncAppContext) -> F + Send + Sync,
F: 'static + Future<Output = Result<()>>, F: 'static + Future<Output = Result<()>>,
{ {
self.add_message_handler_impl(entity, move |model, message, _, cx| { self.add_message_handler_impl(entity, move |model, message, _, cx| {
@ -694,7 +694,7 @@ impl Client {
fn add_message_handler_impl<M, E, H, F>( fn add_message_handler_impl<M, E, H, F>(
self: &Arc<Self>, self: &Arc<Self>,
entity: WeakModel<E>, entity: WeakEntity<E>,
handler: H, handler: H,
) -> Subscription ) -> Subscription
where where
@ -702,7 +702,7 @@ impl Client {
E: 'static, E: 'static,
H: 'static H: 'static
+ Sync + Sync
+ Fn(Model<E>, TypedEnvelope<M>, AnyProtoClient, AsyncAppContext) -> F + Fn(Entity<E>, TypedEnvelope<M>, AnyProtoClient, AsyncAppContext) -> F
+ Send + Send
+ Sync, + Sync,
F: 'static + Future<Output = Result<()>>, F: 'static + Future<Output = Result<()>>,
@ -739,13 +739,13 @@ impl Client {
pub fn add_request_handler<M, E, H, F>( pub fn add_request_handler<M, E, H, F>(
self: &Arc<Self>, self: &Arc<Self>,
model: WeakModel<E>, model: WeakEntity<E>,
handler: H, handler: H,
) -> Subscription ) -> Subscription
where where
M: RequestMessage, M: RequestMessage,
E: 'static, E: 'static,
H: 'static + Sync + Fn(Model<E>, TypedEnvelope<M>, AsyncAppContext) -> F + Send + Sync, H: 'static + Sync + Fn(Entity<E>, TypedEnvelope<M>, AsyncAppContext) -> F + Send + Sync,
F: 'static + Future<Output = Result<M::Response>>, F: 'static + Future<Output = Result<M::Response>>,
{ {
self.add_message_handler_impl(model, move |handle, envelope, this, cx| { self.add_message_handler_impl(model, move |handle, envelope, this, cx| {
@ -1751,7 +1751,7 @@ pub const ZED_URL_SCHEME: &str = "zed";
/// ///
/// Returns a [`Some`] containing the unprefixed link if the link is a Zed link. /// Returns a [`Some`] containing the unprefixed link if the link is a Zed link.
/// Returns [`None`] otherwise. /// Returns [`None`] otherwise.
pub fn parse_zed_link<'a>(link: &'a str, cx: &AppContext) -> Option<&'a str> { pub fn parse_zed_link<'a>(link: &'a str, cx: &App) -> Option<&'a str> {
let server_url = &ClientSettings::get_global(cx).server_url; let server_url = &ClientSettings::get_global(cx).server_url;
if let Some(stripped) = link if let Some(stripped) = link
.strip_prefix(server_url) .strip_prefix(server_url)
@ -1775,7 +1775,7 @@ mod tests {
use crate::test::FakeServer; use crate::test::FakeServer;
use clock::FakeSystemClock; use clock::FakeSystemClock;
use gpui::{BackgroundExecutor, Context, TestAppContext}; use gpui::{AppContext as _, BackgroundExecutor, TestAppContext};
use http_client::FakeHttpClient; use http_client::FakeHttpClient;
use parking_lot::Mutex; use parking_lot::Mutex;
use proto::TypedEnvelope; use proto::TypedEnvelope;
@ -1961,7 +1961,7 @@ mod tests {
let (done_tx1, done_rx1) = smol::channel::unbounded(); let (done_tx1, done_rx1) = smol::channel::unbounded();
let (done_tx2, done_rx2) = smol::channel::unbounded(); let (done_tx2, done_rx2) = smol::channel::unbounded();
AnyProtoClient::from(client.clone()).add_model_message_handler( AnyProtoClient::from(client.clone()).add_model_message_handler(
move |model: Model<TestModel>, _: TypedEnvelope<proto::JoinProject>, mut cx| { move |model: Entity<TestModel>, _: TypedEnvelope<proto::JoinProject>, mut cx| {
match model.update(&mut cx, |model, _| model.id).unwrap() { match model.update(&mut cx, |model, _| model.id).unwrap() {
1 => done_tx1.try_send(()).unwrap(), 1 => done_tx1.try_send(()).unwrap(),
2 => done_tx2.try_send(()).unwrap(), 2 => done_tx2.try_send(()).unwrap(),
@ -1970,15 +1970,15 @@ mod tests {
async { Ok(()) } async { Ok(()) }
}, },
); );
let model1 = cx.new_model(|_| TestModel { let model1 = cx.new(|_| TestModel {
id: 1, id: 1,
subscription: None, subscription: None,
}); });
let model2 = cx.new_model(|_| TestModel { let model2 = cx.new(|_| TestModel {
id: 2, id: 2,
subscription: None, subscription: None,
}); });
let model3 = cx.new_model(|_| TestModel { let model3 = cx.new(|_| TestModel {
id: 3, id: 3,
subscription: None, subscription: None,
}); });
@ -2018,7 +2018,7 @@ mod tests {
}); });
let server = FakeServer::for_client(user_id, &client, cx).await; let server = FakeServer::for_client(user_id, &client, cx).await;
let model = cx.new_model(|_| TestModel::default()); let model = cx.new(|_| TestModel::default());
let (done_tx1, _done_rx1) = smol::channel::unbounded(); let (done_tx1, _done_rx1) = smol::channel::unbounded();
let (done_tx2, done_rx2) = smol::channel::unbounded(); let (done_tx2, done_rx2) = smol::channel::unbounded();
let subscription1 = client.add_message_handler( let subscription1 = client.add_message_handler(
@ -2053,11 +2053,11 @@ mod tests {
}); });
let server = FakeServer::for_client(user_id, &client, cx).await; let server = FakeServer::for_client(user_id, &client, cx).await;
let model = cx.new_model(|_| TestModel::default()); let model = cx.new(|_| TestModel::default());
let (done_tx, done_rx) = smol::channel::unbounded(); let (done_tx, done_rx) = smol::channel::unbounded();
let subscription = client.add_message_handler( let subscription = client.add_message_handler(
model.clone().downgrade(), model.clone().downgrade(),
move |model: Model<TestModel>, _: TypedEnvelope<proto::Ping>, mut cx| { move |model: Entity<TestModel>, _: TypedEnvelope<proto::Ping>, mut cx| {
model model
.update(&mut cx, |model, _| model.subscription.take()) .update(&mut cx, |model, _| model.subscription.take())
.unwrap(); .unwrap();

View file

@ -6,7 +6,7 @@ use clock::SystemClock;
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use futures::channel::mpsc; use futures::channel::mpsc;
use futures::{Future, StreamExt}; use futures::{Future, StreamExt};
use gpui::{AppContext, BackgroundExecutor, Task}; use gpui::{App, BackgroundExecutor, Task};
use http_client::{self, AsyncBody, HttpClient, HttpClientWithUrl, Method, Request}; use http_client::{self, AsyncBody, HttpClient, HttpClientWithUrl, Method, Request};
use parking_lot::Mutex; use parking_lot::Mutex;
use release_channel::ReleaseChannel; use release_channel::ReleaseChannel;
@ -178,7 +178,7 @@ impl Telemetry {
pub fn new( pub fn new(
clock: Arc<dyn SystemClock>, clock: Arc<dyn SystemClock>,
client: Arc<HttpClientWithUrl>, client: Arc<HttpClientWithUrl>,
cx: &mut AppContext, cx: &mut App,
) -> Arc<Self> { ) -> Arc<Self> {
let release_channel = let release_channel =
ReleaseChannel::try_global(cx).map(|release_channel| release_channel.display_name()); ReleaseChannel::try_global(cx).map(|release_channel| release_channel.display_name());
@ -299,7 +299,7 @@ impl Telemetry {
system_id: Option<String>, system_id: Option<String>,
installation_id: Option<String>, installation_id: Option<String>,
session_id: String, session_id: String,
cx: &AppContext, cx: &App,
) { ) {
let mut state = self.state.lock(); let mut state = self.state.lock();
state.system_id = system_id.map(|id| id.into()); state.system_id = system_id.map(|id| id.into());

View file

@ -2,7 +2,7 @@ use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use chrono::Duration; use chrono::Duration;
use futures::{stream::BoxStream, StreamExt}; use futures::{stream::BoxStream, StreamExt};
use gpui::{BackgroundExecutor, Context, Model, TestAppContext}; use gpui::{AppContext as _, BackgroundExecutor, Entity, TestAppContext};
use parking_lot::Mutex; use parking_lot::Mutex;
use rpc::{ use rpc::{
proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse}, proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse},
@ -203,8 +203,8 @@ impl FakeServer {
&self, &self,
client: Arc<Client>, client: Arc<Client>,
cx: &mut TestAppContext, cx: &mut TestAppContext,
) -> Model<UserStore> { ) -> Entity<UserStore> {
let user_store = cx.new_model(|cx| UserStore::new(client, cx)); let user_store = cx.new(|cx| UserStore::new(client, cx));
assert_eq!( assert_eq!(
self.receive::<proto::GetUsers>() self.receive::<proto::GetUsers>()
.await .await

View file

@ -1,12 +1,11 @@
use super::{proto, Client, Status, TypedEnvelope}; use super::{proto, Client, Status, TypedEnvelope};
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context as _, Result};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use collections::{hash_map::Entry, HashMap, HashSet}; use collections::{hash_map::Entry, HashMap, HashSet};
use feature_flags::FeatureFlagAppExt; use feature_flags::FeatureFlagAppExt;
use futures::{channel::mpsc, Future, StreamExt}; use futures::{channel::mpsc, Future, StreamExt};
use gpui::{ use gpui::{
AppContext, AsyncAppContext, EventEmitter, Model, ModelContext, SharedString, SharedUri, Task, App, AsyncAppContext, Context, Entity, EventEmitter, SharedString, SharedUri, Task, WeakEntity,
WeakModel,
}; };
use postage::{sink::Sink, watch}; use postage::{sink::Sink, watch};
use rpc::proto::{RequestMessage, UsersResponse}; use rpc::proto::{RequestMessage, UsersResponse};
@ -106,7 +105,7 @@ pub struct UserStore {
client: Weak<Client>, client: Weak<Client>,
_maintain_contacts: Task<()>, _maintain_contacts: Task<()>,
_maintain_current_user: Task<Result<()>>, _maintain_current_user: Task<Result<()>>,
weak_self: WeakModel<Self>, weak_self: WeakEntity<Self>,
} }
#[derive(Clone)] #[derive(Clone)]
@ -143,7 +142,7 @@ enum UpdateContacts {
} }
impl UserStore { impl UserStore {
pub fn new(client: Arc<Client>, cx: &ModelContext<Self>) -> Self { pub fn new(client: Arc<Client>, cx: &Context<Self>) -> Self {
let (mut current_user_tx, current_user_rx) = watch::channel(); let (mut current_user_tx, current_user_rx) = watch::channel();
let (update_contacts_tx, mut update_contacts_rx) = mpsc::unbounded(); let (update_contacts_tx, mut update_contacts_rx) = mpsc::unbounded();
let rpc_subscriptions = vec![ let rpc_subscriptions = vec![
@ -274,7 +273,7 @@ impl UserStore {
} }
async fn handle_update_invite_info( async fn handle_update_invite_info(
this: Model<Self>, this: Entity<Self>,
message: TypedEnvelope<proto::UpdateInviteInfo>, message: TypedEnvelope<proto::UpdateInviteInfo>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
@ -289,7 +288,7 @@ impl UserStore {
} }
async fn handle_show_contacts( async fn handle_show_contacts(
this: Model<Self>, this: Entity<Self>,
_: TypedEnvelope<proto::ShowContacts>, _: TypedEnvelope<proto::ShowContacts>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
@ -302,7 +301,7 @@ impl UserStore {
} }
async fn handle_update_contacts( async fn handle_update_contacts(
this: Model<Self>, this: Entity<Self>,
message: TypedEnvelope<proto::UpdateContacts>, message: TypedEnvelope<proto::UpdateContacts>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
@ -315,7 +314,7 @@ impl UserStore {
} }
async fn handle_update_plan( async fn handle_update_plan(
this: Model<Self>, this: Entity<Self>,
message: TypedEnvelope<proto::UpdateUserPlan>, message: TypedEnvelope<proto::UpdateUserPlan>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
@ -326,11 +325,7 @@ impl UserStore {
Ok(()) Ok(())
} }
fn update_contacts( fn update_contacts(&mut self, message: UpdateContacts, cx: &Context<Self>) -> Task<Result<()>> {
&mut self,
message: UpdateContacts,
cx: &ModelContext<Self>,
) -> Task<Result<()>> {
match message { match message {
UpdateContacts::Wait(barrier) => { UpdateContacts::Wait(barrier) => {
drop(barrier); drop(barrier);
@ -504,16 +499,12 @@ impl UserStore {
pub fn request_contact( pub fn request_contact(
&mut self, &mut self,
responder_id: u64, responder_id: u64,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
self.perform_contact_request(responder_id, proto::RequestContact { responder_id }, cx) self.perform_contact_request(responder_id, proto::RequestContact { responder_id }, cx)
} }
pub fn remove_contact( pub fn remove_contact(&mut self, user_id: u64, cx: &mut Context<Self>) -> Task<Result<()>> {
&mut self,
user_id: u64,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
self.perform_contact_request(user_id, proto::RemoveContact { user_id }, cx) self.perform_contact_request(user_id, proto::RemoveContact { user_id }, cx)
} }
@ -527,7 +518,7 @@ impl UserStore {
&mut self, &mut self,
requester_id: u64, requester_id: u64,
accept: bool, accept: bool,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
self.perform_contact_request( self.perform_contact_request(
requester_id, requester_id,
@ -546,7 +537,7 @@ impl UserStore {
pub fn dismiss_contact_request( pub fn dismiss_contact_request(
&self, &self,
requester_id: u64, requester_id: u64,
cx: &ModelContext<Self>, cx: &Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
let client = self.client.upgrade(); let client = self.client.upgrade();
cx.spawn(move |_, _| async move { cx.spawn(move |_, _| async move {
@ -565,7 +556,7 @@ impl UserStore {
&mut self, &mut self,
user_id: u64, user_id: u64,
request: T, request: T,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
let client = self.client.upgrade(); let client = self.client.upgrade();
*self.pending_contact_requests.entry(user_id).or_insert(0) += 1; *self.pending_contact_requests.entry(user_id).or_insert(0) += 1;
@ -615,7 +606,7 @@ impl UserStore {
pub fn get_users( pub fn get_users(
&self, &self,
user_ids: Vec<u64>, user_ids: Vec<u64>,
cx: &ModelContext<Self>, cx: &Context<Self>,
) -> Task<Result<Vec<Arc<User>>>> { ) -> Task<Result<Vec<Arc<User>>>> {
let mut user_ids_to_fetch = user_ids.clone(); let mut user_ids_to_fetch = user_ids.clone();
user_ids_to_fetch.retain(|id| !self.users.contains_key(id)); user_ids_to_fetch.retain(|id| !self.users.contains_key(id));
@ -650,7 +641,7 @@ impl UserStore {
pub fn fuzzy_search_users( pub fn fuzzy_search_users(
&self, &self,
query: String, query: String,
cx: &ModelContext<Self>, cx: &Context<Self>,
) -> Task<Result<Vec<Arc<User>>>> { ) -> Task<Result<Vec<Arc<User>>>> {
self.load_users(proto::FuzzySearchUsers { query }, cx) self.load_users(proto::FuzzySearchUsers { query }, cx)
} }
@ -659,7 +650,7 @@ impl UserStore {
self.users.get(&user_id).cloned() self.users.get(&user_id).cloned()
} }
pub fn get_user_optimistic(&self, user_id: u64, cx: &ModelContext<Self>) -> Option<Arc<User>> { pub fn get_user_optimistic(&self, user_id: u64, cx: &Context<Self>) -> Option<Arc<User>> {
if let Some(user) = self.users.get(&user_id).cloned() { if let Some(user) = self.users.get(&user_id).cloned() {
return Some(user); return Some(user);
} }
@ -668,7 +659,7 @@ impl UserStore {
None None
} }
pub fn get_user(&self, user_id: u64, cx: &ModelContext<Self>) -> Task<Result<Arc<User>>> { pub fn get_user(&self, user_id: u64, cx: &Context<Self>) -> Task<Result<Arc<User>>> {
if let Some(user) = self.users.get(&user_id).cloned() { if let Some(user) = self.users.get(&user_id).cloned() {
return Task::ready(Ok(user)); return Task::ready(Ok(user));
} }
@ -708,7 +699,7 @@ impl UserStore {
.map(|accepted_tos_at| accepted_tos_at.is_some()) .map(|accepted_tos_at| accepted_tos_at.is_some())
} }
pub fn accept_terms_of_service(&self, cx: &ModelContext<Self>) -> Task<Result<()>> { pub fn accept_terms_of_service(&self, cx: &Context<Self>) -> Task<Result<()>> {
if self.current_user().is_none() { if self.current_user().is_none() {
return Task::ready(Err(anyhow!("no current user"))); return Task::ready(Err(anyhow!("no current user")));
}; };
@ -740,7 +731,7 @@ impl UserStore {
fn load_users( fn load_users(
&self, &self,
request: impl RequestMessage<Response = UsersResponse>, request: impl RequestMessage<Response = UsersResponse>,
cx: &ModelContext<Self>, cx: &Context<Self>,
) -> Task<Result<Vec<Arc<User>>>> { ) -> Task<Result<Vec<Arc<User>>>> {
let client = self.client.clone(); let client = self.client.clone();
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
@ -774,7 +765,7 @@ impl UserStore {
pub fn set_participant_indices( pub fn set_participant_indices(
&mut self, &mut self,
participant_indices: HashMap<u64, ParticipantIndex>, participant_indices: HashMap<u64, ParticipantIndex>,
cx: &mut ModelContext<Self>, cx: &mut Context<Self>,
) { ) {
if participant_indices != self.participant_indices { if participant_indices != self.participant_indices {
self.participant_indices = participant_indices; self.participant_indices = participant_indices;
@ -789,7 +780,7 @@ impl UserStore {
pub fn participant_names( pub fn participant_names(
&self, &self,
user_ids: impl Iterator<Item = u64>, user_ids: impl Iterator<Item = u64>,
cx: &AppContext, cx: &App,
) -> HashMap<u64, SharedString> { ) -> HashMap<u64, SharedString> {
let mut ret = HashMap::default(); let mut ret = HashMap::default();
let mut missing_user_ids = Vec::new(); let mut missing_user_ids = Vec::new();
@ -827,7 +818,7 @@ impl User {
impl Contact { impl Contact {
async fn from_proto( async fn from_proto(
contact: proto::Contact, contact: proto::Contact,
user_store: &Model<UserStore>, user_store: &Entity<UserStore>,
cx: &mut AsyncAppContext, cx: &mut AsyncAppContext,
) -> Result<Self> { ) -> Result<Self> {
let user = user_store let user = user_store

View file

@ -4,16 +4,16 @@
//! links appropriate for the environment (e.g., by linking to a local copy of //! links appropriate for the environment (e.g., by linking to a local copy of
//! zed.dev in development). //! zed.dev in development).
use gpui::AppContext; use gpui::App;
use settings::Settings; use settings::Settings;
use crate::ClientSettings; use crate::ClientSettings;
fn server_url(cx: &AppContext) -> &str { fn server_url(cx: &App) -> &str {
&ClientSettings::get_global(cx).server_url &ClientSettings::get_global(cx).server_url
} }
/// Returns the URL to the account page on zed.dev. /// Returns the URL to the account page on zed.dev.
pub fn account_url(cx: &AppContext) -> String { pub fn account_url(cx: &App) -> String {
format!("{server_url}/account", server_url = server_url(cx)) format!("{server_url}/account", server_url = server_url(cx))
} }

View file

@ -3,7 +3,7 @@ use crate::{
rpc::Principal, rpc::Principal,
AppState, Error, Result, AppState, Error, Result,
}; };
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context as _};
use axum::{ use axum::{
http::{self, Request, StatusCode}, http::{self, Request, StatusCode},
middleware::Next, middleware::Next,

View file

@ -6,6 +6,7 @@ use axum::{
routing::get, routing::get,
Extension, Router, Extension, Router,
}; };
use collab::api::billing::sync_llm_usage_with_stripe_periodically; use collab::api::billing::sync_llm_usage_with_stripe_periodically;
use collab::api::CloudflareIpCountryHeader; use collab::api::CloudflareIpCountryHeader;
use collab::llm::{db::LlmDatabase, log_usage_periodically}; use collab::llm::{db::LlmDatabase, log_usage_periodically};

View file

@ -1,6 +1,6 @@
use crate::db::{self, ChannelRole, NewUserParams}; use crate::db::{self, ChannelRole, NewUserParams};
use anyhow::Context; use anyhow::Context as _;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use db::Database; use db::Database;
use serde::{de::DeserializeOwned, Deserialize}; use serde::{de::DeserializeOwned, Deserialize};

View file

@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use crate::{llm, Cents, Result}; use crate::{llm, Cents, Result};
use anyhow::Context; use anyhow::Context as _;
use chrono::{Datelike, Utc}; use chrono::{Datelike, Utc};
use collections::HashMap; use collections::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View file

@ -5,7 +5,7 @@ use std::sync::Arc;
use call::Room; use call::Room;
use client::ChannelId; use client::ChannelId;
use gpui::{Model, TestAppContext}; use gpui::{Entity, TestAppContext};
mod channel_buffer_tests; mod channel_buffer_tests;
mod channel_guest_tests; mod channel_guest_tests;
@ -33,7 +33,7 @@ struct RoomParticipants {
pending: Vec<String>, pending: Vec<String>,
} }
fn room_participants(room: &Model<Room>, cx: &mut TestAppContext) -> RoomParticipants { fn room_participants(room: &Entity<Room>, cx: &mut TestAppContext) -> RoomParticipants {
room.read_with(cx, |room, _| { room.read_with(cx, |room, _| {
let mut remote = room let mut remote = room
.remote_participants() .remote_participants()
@ -51,7 +51,7 @@ fn room_participants(room: &Model<Room>, cx: &mut TestAppContext) -> RoomPartici
}) })
} }
fn channel_id(room: &Model<Room>, cx: &mut TestAppContext) -> Option<ChannelId> { fn channel_id(room: &Entity<Room>, cx: &mut TestAppContext) -> Option<ChannelId> {
cx.read(|cx| room.read(cx).channel_id()) cx.read(|cx| room.read(cx).channel_id())
} }

View file

@ -9,7 +9,7 @@ use collab_ui::channel_view::ChannelView;
use collections::HashMap; use collections::HashMap;
use editor::{Anchor, Editor, ToOffset}; use editor::{Anchor, Editor, ToOffset};
use futures::future; use futures::future;
use gpui::{BackgroundExecutor, Model, TestAppContext, ViewContext}; use gpui::{BackgroundExecutor, Context, Entity, TestAppContext, Window};
use rpc::{proto::PeerId, RECEIVE_TIMEOUT}; use rpc::{proto::PeerId, RECEIVE_TIMEOUT};
use serde_json::json; use serde_json::json;
use std::ops::Range; use std::ops::Range;
@ -161,43 +161,43 @@ async fn test_channel_notes_participant_indices(
// Clients A, B, and C open the channel notes // Clients A, B, and C open the channel notes
let channel_view_a = cx_a let channel_view_a = cx_a
.update(|cx| ChannelView::open(channel_id, None, workspace_a.clone(), cx)) .update(|window, cx| ChannelView::open(channel_id, None, workspace_a.clone(), window, cx))
.await .await
.unwrap(); .unwrap();
let channel_view_b = cx_b let channel_view_b = cx_b
.update(|cx| ChannelView::open(channel_id, None, workspace_b.clone(), cx)) .update(|window, cx| ChannelView::open(channel_id, None, workspace_b.clone(), window, cx))
.await .await
.unwrap(); .unwrap();
let channel_view_c = cx_c let channel_view_c = cx_c
.update(|cx| ChannelView::open(channel_id, None, workspace_c.clone(), cx)) .update(|window, cx| ChannelView::open(channel_id, None, workspace_c.clone(), window, cx))
.await .await
.unwrap(); .unwrap();
// Clients A, B, and C all insert and select some text // Clients A, B, and C all insert and select some text
channel_view_a.update(cx_a, |notes, cx| { channel_view_a.update_in(cx_a, |notes, window, cx| {
notes.editor.update(cx, |editor, cx| { notes.editor.update(cx, |editor, cx| {
editor.insert("a", cx); editor.insert("a", window, cx);
editor.change_selections(None, cx, |selections| { editor.change_selections(None, window, cx, |selections| {
selections.select_ranges(vec![0..1]); selections.select_ranges(vec![0..1]);
}); });
}); });
}); });
executor.run_until_parked(); executor.run_until_parked();
channel_view_b.update(cx_b, |notes, cx| { channel_view_b.update_in(cx_b, |notes, window, cx| {
notes.editor.update(cx, |editor, cx| { notes.editor.update(cx, |editor, cx| {
editor.move_down(&Default::default(), cx); editor.move_down(&Default::default(), window, cx);
editor.insert("b", cx); editor.insert("b", window, cx);
editor.change_selections(None, cx, |selections| { editor.change_selections(None, window, cx, |selections| {
selections.select_ranges(vec![1..2]); selections.select_ranges(vec![1..2]);
}); });
}); });
}); });
executor.run_until_parked(); executor.run_until_parked();
channel_view_c.update(cx_c, |notes, cx| { channel_view_c.update_in(cx_c, |notes, window, cx| {
notes.editor.update(cx, |editor, cx| { notes.editor.update(cx, |editor, cx| {
editor.move_down(&Default::default(), cx); editor.move_down(&Default::default(), window, cx);
editor.insert("c", cx); editor.insert("c", window, cx);
editor.change_selections(None, cx, |selections| { editor.change_selections(None, window, cx, |selections| {
selections.select_ranges(vec![2..3]); selections.select_ranges(vec![2..3]);
}); });
}); });
@ -206,9 +206,9 @@ async fn test_channel_notes_participant_indices(
// Client A sees clients B and C without assigned colors, because they aren't // Client A sees clients B and C without assigned colors, because they aren't
// in a call together. // in a call together.
executor.run_until_parked(); executor.run_until_parked();
channel_view_a.update(cx_a, |notes, cx| { channel_view_a.update_in(cx_a, |notes, window, cx| {
notes.editor.update(cx, |editor, cx| { notes.editor.update(cx, |editor, cx| {
assert_remote_selections(editor, &[(None, 1..2), (None, 2..3)], cx); assert_remote_selections(editor, &[(None, 1..2), (None, 2..3)], window, cx);
}); });
}); });
@ -222,20 +222,22 @@ async fn test_channel_notes_participant_indices(
// Clients A and B see each other with two different assigned colors. Client C // Clients A and B see each other with two different assigned colors. Client C
// still doesn't have a color. // still doesn't have a color.
executor.run_until_parked(); executor.run_until_parked();
channel_view_a.update(cx_a, |notes, cx| { channel_view_a.update_in(cx_a, |notes, window, cx| {
notes.editor.update(cx, |editor, cx| { notes.editor.update(cx, |editor, cx| {
assert_remote_selections( assert_remote_selections(
editor, editor,
&[(Some(ParticipantIndex(1)), 1..2), (None, 2..3)], &[(Some(ParticipantIndex(1)), 1..2), (None, 2..3)],
window,
cx, cx,
); );
}); });
}); });
channel_view_b.update(cx_b, |notes, cx| { channel_view_b.update_in(cx_b, |notes, window, cx| {
notes.editor.update(cx, |editor, cx| { notes.editor.update(cx, |editor, cx| {
assert_remote_selections( assert_remote_selections(
editor, editor,
&[(Some(ParticipantIndex(0)), 0..1), (None, 2..3)], &[(Some(ParticipantIndex(0)), 0..1), (None, 2..3)],
window,
cx, cx,
); );
}); });
@ -252,8 +254,8 @@ async fn test_channel_notes_participant_indices(
// Clients A and B open the same file. // Clients A and B open the same file.
executor.start_waiting(); executor.start_waiting();
let editor_a = workspace_a let editor_a = workspace_a
.update(cx_a, |workspace, cx| { .update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id_a, "file.txt"), None, true, cx) workspace.open_path((worktree_id_a, "file.txt"), None, true, window, cx)
}) })
.await .await
.unwrap() .unwrap()
@ -261,32 +263,32 @@ async fn test_channel_notes_participant_indices(
.unwrap(); .unwrap();
executor.start_waiting(); executor.start_waiting();
let editor_b = workspace_b let editor_b = workspace_b
.update(cx_b, |workspace, cx| { .update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id_a, "file.txt"), None, true, cx) workspace.open_path((worktree_id_a, "file.txt"), None, true, window, cx)
}) })
.await .await
.unwrap() .unwrap()
.downcast::<Editor>() .downcast::<Editor>()
.unwrap(); .unwrap();
editor_a.update(cx_a, |editor, cx| { editor_a.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, cx, |selections| { editor.change_selections(None, window, cx, |selections| {
selections.select_ranges(vec![0..1]); selections.select_ranges(vec![0..1]);
}); });
}); });
editor_b.update(cx_b, |editor, cx| { editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, cx, |selections| { editor.change_selections(None, window, cx, |selections| {
selections.select_ranges(vec![2..3]); selections.select_ranges(vec![2..3]);
}); });
}); });
executor.run_until_parked(); executor.run_until_parked();
// Clients A and B see each other with the same colors as in the channel notes. // Clients A and B see each other with the same colors as in the channel notes.
editor_a.update(cx_a, |editor, cx| { editor_a.update_in(cx_a, |editor, window, cx| {
assert_remote_selections(editor, &[(Some(ParticipantIndex(1)), 2..3)], cx); assert_remote_selections(editor, &[(Some(ParticipantIndex(1)), 2..3)], window, cx);
}); });
editor_b.update(cx_b, |editor, cx| { editor_b.update_in(cx_b, |editor, window, cx| {
assert_remote_selections(editor, &[(Some(ParticipantIndex(0)), 0..1)], cx); assert_remote_selections(editor, &[(Some(ParticipantIndex(0)), 0..1)], window, cx);
}); });
} }
@ -294,9 +296,10 @@ async fn test_channel_notes_participant_indices(
fn assert_remote_selections( fn assert_remote_selections(
editor: &mut Editor, editor: &mut Editor,
expected_selections: &[(Option<ParticipantIndex>, Range<usize>)], expected_selections: &[(Option<ParticipantIndex>, Range<usize>)],
cx: &mut ViewContext<Editor>, window: &mut Window,
cx: &mut Context<Editor>,
) { ) {
let snapshot = editor.snapshot(cx); let snapshot = editor.snapshot(window, cx);
let range = Anchor::min()..Anchor::max(); let range = Anchor::min()..Anchor::max();
let remote_selections = snapshot let remote_selections = snapshot
.remote_selections_in_range(&range, editor.collaboration_hub().unwrap(), cx) .remote_selections_in_range(&range, editor.collaboration_hub().unwrap(), cx)
@ -641,9 +644,9 @@ async fn test_channel_buffer_changes(
}); });
// Closing the buffer should re-enable change tracking // Closing the buffer should re-enable change tracking
cx_b.update(|cx| { cx_b.update(|window, cx| {
workspace_b.update(cx, |workspace, cx| { workspace_b.update(cx, |workspace, cx| {
workspace.close_all_items_and_panes(&Default::default(), cx) workspace.close_all_items_and_panes(&Default::default(), window, cx)
}); });
}); });
deterministic.run_until_parked(); deterministic.run_until_parked();
@ -691,6 +694,6 @@ fn assert_collaborators(collaborators: &HashMap<PeerId, Collaborator>, ids: &[Op
); );
} }
fn buffer_text(channel_buffer: &Model<language::Buffer>, cx: &mut TestAppContext) -> String { fn buffer_text(channel_buffer: &Entity<language::Buffer>, cx: &mut TestAppContext) -> String {
channel_buffer.read_with(cx, |buffer, _| buffer.text()) channel_buffer.read_with(cx, |buffer, _| buffer.text())
} }

Some files were not shown because too many files have changed in this diff Show more