Fix window double borrows (#23739)

Fix bugs caused by the window context PR, where the window could be on
the stack and is then requested from the App.
This PR also adds derive macros for `AppContext` and `VisualContext` so
that it's easy to define further contexts in API code, such as
`editor::BlockContext`.

Release Notes:

- N/A
This commit is contained in:
Mikayla Maki 2025-01-27 13:56:29 -08:00 committed by GitHub
parent 29bfb56739
commit a7c549b85b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 465 additions and 297 deletions

View file

@ -1878,19 +1878,17 @@ impl PromptEditor {
) {
match event {
EditorEvent::Edited { .. } => {
if let Some(workspace) = window.window_handle().downcast::<Workspace>() {
workspace
.update(cx, |workspace, _, cx| {
let is_via_ssh = workspace
.project()
.update(cx, |project, _| project.is_via_ssh());
if let Some(workspace) = window.root::<Workspace>().flatten() {
workspace.update(cx, |workspace, cx| {
let is_via_ssh = workspace
.project()
.update(cx, |project, _| project.is_via_ssh());
workspace
.client()
.telemetry()
.log_edit_event("inline assist", is_via_ssh);
})
.log_err();
workspace
.client()
.telemetry()
.log_edit_event("inline assist", is_via_ssh);
});
}
let prompt = self.editor.read(cx).text(cx);
if self

View file

@ -304,19 +304,17 @@ impl<T: 'static> PromptEditor<T> {
) {
match event {
EditorEvent::Edited { .. } => {
if let Some(workspace) = window.window_handle().downcast::<Workspace>() {
workspace
.update(cx, |workspace, _, cx| {
let is_via_ssh = workspace
.project()
.update(cx, |project, _| project.is_via_ssh());
if let Some(workspace) = window.root::<Workspace>().flatten() {
workspace.update(cx, |workspace, cx| {
let is_via_ssh = workspace
.project()
.update(cx, |project, _| project.is_via_ssh());
workspace
.client()
.telemetry()
.log_edit_event("inline assist", is_via_ssh);
})
.log_err();
workspace
.client()
.telemetry()
.log_edit_event("inline assist", is_via_ssh);
});
}
let prompt = self.editor.read(cx).text(cx);
if self

View file

@ -23,11 +23,11 @@ use fs::Fs;
use futures::FutureExt;
use gpui::{
actions, div, img, impl_internal_actions, percentage, point, prelude::*, pulsating_between,
size, Animation, AnimationExt, AnyElement, AnyView, AnyWindowHandle, App, AsyncWindowContext,
ClipboardEntry, ClipboardItem, CursorStyle, Empty, Entity, EventEmitter, FocusHandle,
Focusable, FontWeight, Global, InteractiveElement, IntoElement, ParentElement, Pixels, Render,
RenderImage, SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task,
Transformation, WeakEntity,
size, Animation, AnimationExt, AnyElement, AnyView, App, AsyncWindowContext, ClipboardEntry,
ClipboardItem, CursorStyle, Empty, Entity, EventEmitter, FocusHandle, Focusable, FontWeight,
Global, InteractiveElement, IntoElement, ParentElement, Pixels, Render, RenderImage,
SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task, Transformation,
WeakEntity,
};
use indexed_docs::IndexedDocsStore;
use language::{language_settings::SoftWrap, BufferSnapshot, LspAdapterDelegate, ToOffset};
@ -978,21 +978,20 @@ impl ContextEditor {
.unwrap();
let render_block: RenderBlock = Arc::new({
let this = this.clone();
let window_handle = window.window_handle();
let patch_range = range.clone();
move |cx: &mut BlockContext<'_, '_>| {
let max_width = cx.max_width;
let gutter_width = cx.gutter_dimensions.full_width();
let block_id = cx.block_id;
let selected = cx.selected;
this.update(&mut **cx, |this, cx| {
this.update_in(cx, |this, window, cx| {
this.render_patch_block(
patch_range.clone(),
max_width,
gutter_width,
block_id,
selected,
window_handle,
window,
cx,
)
})
@ -2198,15 +2197,12 @@ impl ContextEditor {
gutter_width: Pixels,
id: BlockId,
selected: bool,
window_handle: AnyWindowHandle,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<AnyElement> {
let snapshot = window_handle
.update(cx, |_, window, cx| {
self.editor
.update(cx, |editor, cx| editor.snapshot(window, cx))
})
.ok()?;
let snapshot = self
.editor
.update(cx, |editor, cx| editor.snapshot(window, cx));
let (excerpt_id, _buffer_id, _) = snapshot.buffer_snapshot.as_singleton().unwrap();
let excerpt_id = *excerpt_id;
let anchor = snapshot

View file

@ -18,16 +18,12 @@ pub fn initiate_sign_in(window: &mut Window, cx: &mut App) {
return;
};
let status = copilot.read(cx).status();
let Some(workspace) = window.window_handle().downcast::<Workspace>() else {
let Some(workspace) = window.root::<Workspace>().flatten() else {
return;
};
match status {
Status::Starting { task } => {
let Some(workspace) = window.window_handle().downcast::<Workspace>() else {
return;
};
let Ok(workspace) = workspace.update(cx, |workspace, _window, cx| {
workspace.update(cx, |workspace, cx| {
workspace.show_toast(
Toast::new(
NotificationId::unique::<CopilotStartingToast>(),
@ -35,11 +31,9 @@ pub fn initiate_sign_in(window: &mut Window, cx: &mut App) {
),
cx,
);
workspace.weak_handle()
}) else {
return;
};
});
let workspace = workspace.downgrade();
cx.spawn(|mut cx| async move {
task.await;
if let Some(copilot) = cx.update(|cx| Copilot::global(cx)).ok().flatten() {
@ -69,13 +63,11 @@ pub fn initiate_sign_in(window: &mut Window, cx: &mut App) {
}
_ => {
copilot.update(cx, |this, cx| this.sign_in(cx)).detach();
workspace
.update(cx, |this, window, cx| {
this.toggle_modal(window, cx, |_, cx| {
CopilotCodeVerification::new(&copilot, cx)
});
})
.ok();
workspace.update(cx, |this, cx| {
this.toggle_modal(window, cx, |_, cx| {
CopilotCodeVerification::new(&copilot, cx)
});
});
}
}
}

View file

@ -225,8 +225,11 @@ pub enum BlockStyle {
Sticky,
}
#[derive(gpui::AppContext, gpui::VisualContext)]
pub struct BlockContext<'a, 'b> {
#[window]
pub window: &'a mut Window,
#[app]
pub app: &'b mut App,
pub anchor_x: Pixels,
pub max_width: Pixels,

View file

@ -4147,9 +4147,9 @@ impl EditorElement {
// In singleton buffers, we select corresponding lines on the line number click, so use | -like cursor.
// In multi buffers, we open file at the line number clicked, so use a pointing hand cursor.
if is_singleton {
window.set_cursor_style(CursorStyle::IBeam, hitbox);
window.set_cursor_style(CursorStyle::IBeam, &hitbox);
} else {
window.set_cursor_style(CursorStyle::PointingHand, hitbox);
window.set_cursor_style(CursorStyle::PointingHand, &hitbox);
}
}
}

View file

@ -5,8 +5,8 @@ use editor::{
actions::Tab, scroll::Autoscroll, Anchor, Editor, MultiBufferSnapshot, ToOffset, ToPoint,
};
use gpui::{
div, prelude::*, AnyWindowHandle, App, DismissEvent, Entity, EventEmitter, FocusHandle,
Focusable, Render, SharedString, Styled, Subscription,
div, prelude::*, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render,
SharedString, Styled, Subscription,
};
use language::Buffer;
use settings::Settings;
@ -133,19 +133,15 @@ impl GoToLine {
}
}
fn release(&mut self, window: AnyWindowHandle, cx: &mut App) {
window
.update(cx, |_, window, cx| {
let scroll_position = self.prev_scroll_position.take();
self.active_editor.update(cx, |editor, cx| {
editor.clear_row_highlights::<GoToLineRowHighlights>();
if let Some(scroll_position) = scroll_position {
editor.set_scroll_position(scroll_position, window, cx);
}
cx.notify();
})
})
.ok();
fn release(&mut self, window: &mut Window, cx: &mut App) {
let scroll_position = self.prev_scroll_position.take();
self.active_editor.update(cx, |editor, cx| {
editor.clear_row_highlights::<GoToLineRowHighlights>();
if let Some(scroll_position) = scroll_position {
editor.set_scroll_position(scroll_position, window, cx);
}
cx.notify();
})
}
fn on_line_editor_event(

View file

@ -1236,15 +1236,9 @@ impl App {
T: 'static,
{
let window_handle = window.handle;
let (subscription, activate) = self.release_listeners.insert(
handle.entity_id(),
Box::new(move |entity, cx| {
let entity = entity.downcast_mut().expect("invalid entity type");
let _ = window_handle.update(cx, |_, window, cx| on_release(entity, window, cx));
}),
);
activate();
subscription
self.observe_release(&handle, move |entity, cx| {
let _ = window_handle.update(cx, |_, window, cx| on_release(entity, window, cx));
})
}
/// Register a callback to be invoked when a keystroke is received by the application

View file

@ -323,46 +323,32 @@ impl<'a, T: 'static> Context<'a, T> {
pub fn on_release_in(
&mut self,
window: &Window,
on_release: impl FnOnce(&mut T, AnyWindowHandle, &mut App) + 'static,
on_release: impl FnOnce(&mut T, &mut Window, &mut App) + 'static,
) -> Subscription {
let window_handle = window.handle;
let (subscription, activate) = self.release_listeners.insert(
self.entity_id(),
Box::new(move |this, cx| {
let this = this.downcast_mut().expect("invalid entity type");
on_release(this, window_handle, cx)
}),
);
activate();
subscription
let entity = self.entity();
self.app.observe_release_in(&entity, window, on_release)
}
/// Register a callback to be invoked when the given Model or View is released.
pub fn observe_release_in<V2>(
pub fn observe_release_in<T2>(
&self,
observed: &Entity<V2>,
observed: &Entity<T2>,
window: &Window,
mut on_release: impl FnMut(&mut T, &mut V2, &mut Window, &mut Context<'_, T>) + 'static,
mut on_release: impl FnMut(&mut T, &mut T2, &mut Window, &mut Context<'_, T>) + 'static,
) -> Subscription
where
T: 'static,
V2: 'static,
T2: 'static,
{
let observer = self.weak_entity();
let window_handle = window.handle;
let (subscription, activate) = self.release_listeners.insert(
observed.entity_id(),
Box::new(move |observed, cx| {
let observed = observed
.downcast_mut()
.expect("invalid observed entity type");
let _ = window_handle.update(cx, |_, window, cx| {
observer.update(cx, |this, cx| on_release(this, observed, window, cx))
});
}),
);
activate();
subscription
self.app
.observe_release_in(observed, window, move |observed, window, cx| {
observer
.update(cx, |observer, cx| {
on_release(observer, observed, window, cx)
})
.ok();
})
}
/// Register a callback to be invoked when the window is resized.

View file

@ -129,7 +129,7 @@ pub use elements::*;
pub use executor::*;
pub use geometry::*;
pub use global::*;
pub use gpui_macros::{register_action, test, IntoElement, Render};
pub use gpui_macros::{register_action, test, AppContext, IntoElement, Render, VisualContext};
pub use http_client;
pub use input::*;
pub use interactive::*;

View file

@ -1271,7 +1271,7 @@ impl ClipboardItem {
for entry in self.entries.iter() {
if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
answer.push_str(text);
answer.push_str(&text);
any_entries = true;
}
}

View file

@ -3,8 +3,8 @@ use crate::{
AnyView, App, AppContext, Arena, Asset, AsyncWindowContext, AvailableSpace, Background, Bounds,
BoxShadow, Context, Corners, CursorStyle, Decorations, DevicePixels, DispatchActionListener,
DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
FileDropEvent, Flatten, FontId, Global, GlobalElementId, GlyphId, GpuSpecs, Hsla, InputHandler,
IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent, Keystroke, KeystrokeEvent, LayoutId,
FileDropEvent, FontId, Global, GlobalElementId, GlyphId, GpuSpecs, Hsla, InputHandler, IsZero,
KeyBinding, KeyContext, KeyDownEvent, KeyEvent, Keystroke, KeystrokeEvent, LayoutId,
LineLayoutIndex, Modifiers, ModifiersChangedEvent, MonochromeSprite, MouseButton, MouseEvent,
MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render,
@ -677,6 +677,9 @@ pub(crate) struct ElementStateBox {
fn default_bounds(display_id: Option<DisplayId>, cx: &mut App) -> Bounds<Pixels> {
const DEFAULT_WINDOW_OFFSET: Point<Pixels> = point(px(0.), px(35.));
// TODO, BUG: if you open a window with the currently active window
// on the stack, this will erroneously select the 'unwrap_or_else'
// code path
cx.active_window()
.and_then(|w| w.update(cx, |_, window, _| window.bounds()).ok())
.map(|mut bounds| {
@ -3775,11 +3778,12 @@ impl<V: 'static + Render> WindowHandle<V> {
/// Get the root view out of this window.
///
/// This will fail if the window is closed or if the root view's type does not match `V`.
#[cfg(any(test, feature = "test-support"))]
pub fn root<C>(&self, cx: &mut C) -> Result<Entity<V>>
where
C: AppContext,
{
Flatten::flatten(cx.update_window(self.any_handle, |root_view, _, _| {
crate::Flatten::flatten(cx.update_window(self.any_handle, |root_view, _, _| {
root_view
.downcast::<V>()
.map_err(|_| anyhow!("the type of the window's root view has changed"))

View file

@ -11,7 +11,7 @@ workspace = true
[lib]
path = "src/gpui_macros.rs"
proc-macro = true
doctest = false
doctest = true
[dependencies]
proc-macro2 = "1.0.66"

View file

@ -0,0 +1,88 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
use crate::get_simple_attribute_field;
pub fn derive_app_context(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let Some(app_variable) = get_simple_attribute_field(&ast, "app") else {
return quote! {
compile_error!("Derive must have an #[app] attribute to detect the &mut App field");
}
.into();
};
let type_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = ast.generics.split_for_impl();
let gen = quote! {
impl #impl_generics gpui::AppContext for #type_name #type_generics
#where_clause
{
type Result<T> = T;
fn new<T: 'static>(
&mut self,
build_model: impl FnOnce(&mut gpui::Context<'_, T>) -> T,
) -> Self::Result<gpui::Entity<T>> {
self.#app_variable.new(build_model)
}
fn reserve_entity<T: 'static>(&mut self) -> Self::Result<gpui::Reservation<T>> {
self.#app_variable.reserve_entity()
}
fn insert_entity<T: 'static>(
&mut self,
reservation: gpui::Reservation<T>,
build_model: impl FnOnce(&mut gpui::Context<'_, T>) -> T,
) -> Self::Result<gpui::Entity<T>> {
self.#app_variable.insert_entity(reservation, build_model)
}
fn update_entity<T, R>(
&mut self,
handle: &gpui::Entity<T>,
update: impl FnOnce(&mut T, &mut gpui::Context<'_, T>) -> R,
) -> Self::Result<R>
where
T: 'static,
{
self.#app_variable.update_entity(handle, update)
}
fn read_entity<T, R>(
&self,
handle: &gpui::Entity<T>,
read: impl FnOnce(&T, &gpui::App) -> R,
) -> Self::Result<R>
where
T: 'static,
{
self.#app_variable.read_entity(handle, read)
}
fn update_window<T, F>(&mut self, window: gpui::AnyWindowHandle, f: F) -> gpui::Result<T>
where
F: FnOnce(gpui::AnyView, &mut gpui::Window, &mut gpui::App) -> T,
{
self.#app_variable.update_window(window, f)
}
fn read_window<T, R>(
&self,
window: &gpui::WindowHandle<T>,
read: impl FnOnce(gpui::Entity<T>, &gpui::App) -> R,
) -> gpui::Result<R>
where
T: 'static,
{
self.#app_variable.read_window(window, read)
}
}
};
gen.into()
}

View file

@ -0,0 +1,71 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
use super::get_simple_attribute_field;
pub fn derive_visual_context(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let Some(window_variable) = get_simple_attribute_field(&ast, "window") else {
return quote! {
compile_error!("Derive must have a #[window] attribute to detect the &mut Window field");
}
.into();
};
let Some(app_variable) = get_simple_attribute_field(&ast, "app") else {
return quote! {
compile_error!("Derive must have a #[app] attribute to detect the &mut App field");
}
.into();
};
let type_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = ast.generics.split_for_impl();
let gen = quote! {
impl #impl_generics gpui::VisualContext for #type_name #type_generics
#where_clause
{
fn window_handle(&self) -> gpui::AnyWindowHandle {
self.#window_variable.window_handle()
}
fn update_window_entity<T: 'static, R>(
&mut self,
model: &gpui::Entity<T>,
update: impl FnOnce(&mut T, &mut gpui::Window, &mut gpui::Context<T>) -> R,
) -> Self::Result<R> {
gpui::AppContext::update_entity(self.#app_variable, model, |entity, cx| update(entity, self.#window_variable, cx))
}
fn new_window_entity<T: 'static>(
&mut self,
build_model: impl FnOnce(&mut gpui::Window, &mut gpui::Context<'_, T>) -> T,
) -> Self::Result<gpui::Entity<T>> {
gpui::AppContext::new(self.#app_variable, |cx| build_model(self.#window_variable, cx))
}
fn replace_root_view<V>(
&mut self,
build_view: impl FnOnce(&mut gpui::Window, &mut gpui::Context<V>) -> V,
) -> Self::Result<gpui::Entity<V>>
where
V: 'static + gpui::Render,
{
self.#window_variable.replace_root(self.#app_variable, build_view)
}
fn focus<V>(&mut self, model: &gpui::Entity<V>) -> Self::Result<()>
where
V: gpui::Focusable,
{
let focus_handle = gpui::Focusable::focus_handle(model, self.#app_variable);
self.#window_variable.focus(&focus_handle)
}
}
};
gen.into()
}

View file

@ -1,11 +1,14 @@
mod derive_app_context;
mod derive_into_element;
mod derive_path_static_str;
mod derive_render;
mod derive_visual_context;
mod register_action;
mod styles;
mod test;
use proc_macro::TokenStream;
use syn::{DeriveInput, Ident};
/// register_action! can be used to register an action with the GPUI runtime.
/// You should typically use `gpui::actions!` or `gpui::impl_actions!` instead,
@ -34,6 +37,57 @@ pub fn derive_path_static_str(input: TokenStream) -> TokenStream {
derive_path_static_str::derive_path_static_str(input)
}
/// #[derive(AppContext)] is used to create a context out of anything that holds a `&mut App`
/// Note that a `#[app]` attribute is required to identify the variable holding the &mut App.
///
/// Failure to add the attribute causes a compile error:
///
/// ```compile_fail
/// # #[macro_use] extern crate gpui_macros;
/// # #[macro_use] extern crate gpui;
/// #[derive(AppContext)]
/// struct MyContext<'a> {
/// app: &'a mut gpui::App
/// }
/// ```
#[proc_macro_derive(AppContext, attributes(app))]
pub fn derive_app_context(input: TokenStream) -> TokenStream {
derive_app_context::derive_app_context(input)
}
/// #[derive(VisualContext)] is used to create a visual context out of anything that holds a `&mut Window` and
/// implements `AppContext`
/// Note that a `#[app]` and a `#[window]` attribute are required to identify the variables holding the &mut App,
/// and &mut Window respectively.
///
/// Failure to add both attributes causes a compile error:
///
/// ```compile_fail
/// # #[macro_use] extern crate gpui_macros;
/// # #[macro_use] extern crate gpui;
/// #[derive(VisualContext)]
/// struct MyContext<'a, 'b> {
/// #[app]
/// app: &'a mut gpui::App,
/// window: &'b mut gpui::Window
/// }
/// ```
///
/// ```compile_fail
/// # #[macro_use] extern crate gpui_macros;
/// # #[macro_use] extern crate gpui;
/// #[derive(VisualContext)]
/// struct MyContext<'a, 'b> {
/// app: &'a mut gpui::App,
/// #[window]
/// window: &'b mut gpui::Window
/// }
/// ```
#[proc_macro_derive(VisualContext, attributes(window, app))]
pub fn derive_visual_context(input: TokenStream) -> TokenStream {
derive_visual_context::derive_visual_context(input)
}
/// Used by GPUI to generate the style helpers.
#[proc_macro]
#[doc(hidden)]
@ -115,3 +169,15 @@ pub fn box_shadow_style_methods(input: TokenStream) -> TokenStream {
pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
test::test(args, function)
}
pub(crate) fn get_simple_attribute_field(ast: &DeriveInput, name: &'static str) -> Option<Ident> {
match &ast.data {
syn::Data::Struct(data_struct) => data_struct
.fields
.iter()
.find(|field| field.attrs.iter().any(|attr| attr.path.is_ident(name)))
.map(|field| field.ident.clone().unwrap()),
syn::Data::Enum(_) => None,
syn::Data::Union(_) => None,
}
}

View file

@ -0,0 +1,13 @@
#[test]
fn test_derive_context() {
use gpui::{App, Window};
use gpui_macros::{AppContext, VisualContext};
#[derive(AppContext, VisualContext)]
struct _MyCustomContext<'a, 'b> {
#[app]
app: &'a mut App,
#[window]
window: &'b mut Window,
}
}

View file

@ -91,17 +91,16 @@ impl Render for InlineCompletionButton {
IconButton::new("copilot-error", icon)
.icon_size(IconSize::Small)
.on_click(cx.listener(move |_, _, window, cx| {
if let Some(workspace) =
window.window_handle().downcast::<Workspace>()
{
workspace
.update(cx, |workspace, _, cx| {
workspace.show_toast(
Toast::new(
NotificationId::unique::<CopilotErrorToast>(),
format!("Copilot can't be started: {}", e),
)
.on_click("Reinstall Copilot", |_, cx| {
if let Some(workspace) = window.root::<Workspace>().flatten() {
workspace.update(cx, |workspace, cx| {
workspace.show_toast(
Toast::new(
NotificationId::unique::<CopilotErrorToast>(),
format!("Copilot can't be started: {}", e),
)
.on_click(
"Reinstall Copilot",
|_, cx| {
if let Some(copilot) = Copilot::global(cx) {
copilot
.update(cx, |copilot, cx| {
@ -109,11 +108,11 @@ impl Render for InlineCompletionButton {
})
.detach();
}
}),
cx,
);
})
.ok();
},
),
cx,
);
});
}
}))
.tooltip(|window, cx| {
@ -398,19 +397,17 @@ impl InlineCompletionButton {
),
None,
move |window, cx| {
if let Some(workspace) = window.window_handle().downcast::<Workspace>() {
if let Ok(workspace) = workspace.root(cx) {
let workspace = workspace.downgrade();
window
.spawn(cx, |cx| {
configure_disabled_globs(
workspace,
path_enabled.then_some(path.clone()),
cx,
)
})
.detach_and_log_err(cx);
}
if let Some(workspace) = window.root().flatten() {
let workspace = workspace.downgrade();
window
.spawn(cx, |cx| {
configure_disabled_globs(
workspace,
path_enabled.then_some(path.clone()),
cx,
)
})
.detach_and_log_err(cx);
}
},
);

View file

@ -81,49 +81,62 @@ pub fn open_prompt_library(
make_completion_provider: Arc<dyn Fn() -> Box<dyn CompletionProvider>>,
cx: &mut App,
) -> Task<Result<WindowHandle<PromptLibrary>>> {
let existing_window = cx
.windows()
.into_iter()
.find_map(|window| window.downcast::<PromptLibrary>());
if let Some(existing_window) = existing_window {
existing_window
.update(cx, |_, window, _| window.activate_window())
.ok();
Task::ready(Ok(existing_window))
} else {
let store = PromptStore::global(cx);
cx.spawn(|cx| async move {
let store = store.await?;
cx.update(|cx| {
let app_id = ReleaseChannel::global(cx).app_id();
let bounds = Bounds::centered(None, size(px(1024.0), px(768.0)), cx);
cx.open_window(
WindowOptions {
titlebar: Some(TitlebarOptions {
title: Some("Prompt Library".into()),
appears_transparent: cfg!(target_os = "macos"),
traffic_light_position: Some(point(px(9.0), px(9.0))),
}),
app_id: Some(app_id.to_owned()),
window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default()
},
|window, cx| {
cx.new(|cx| {
PromptLibrary::new(
store,
language_registry,
inline_assist_delegate,
make_completion_provider,
window,
cx,
)
})
},
)
})?
})
}
let store = PromptStore::global(cx);
cx.spawn(|cx| async move {
// We query windows in spawn so that all windows have been returned to GPUI
let existing_window = cx
.update(|cx| {
let existing_window = cx
.windows()
.into_iter()
.find_map(|window| window.downcast::<PromptLibrary>());
if let Some(existing_window) = existing_window {
existing_window
.update(cx, |_, window, _| window.activate_window())
.ok();
Some(existing_window)
} else {
None
}
})
.ok()
.flatten();
if let Some(existing_window) = existing_window {
return Ok(existing_window);
}
let store = store.await?;
cx.update(|cx| {
let app_id = ReleaseChannel::global(cx).app_id();
let bounds = Bounds::centered(None, size(px(1024.0), px(768.0)), cx);
cx.open_window(
WindowOptions {
titlebar: Some(TitlebarOptions {
title: Some("Prompt Library".into()),
appears_transparent: cfg!(target_os = "macos"),
traffic_light_position: Some(point(px(9.0), px(9.0))),
}),
app_id: Some(app_id.to_owned()),
window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default()
},
|window, cx| {
cx.new(|cx| {
PromptLibrary::new(
store,
language_registry,
inline_assist_delegate,
make_completion_provider,
window,
cx,
)
})
},
)
})?
})
}
pub struct PromptLibrary {

View file

@ -126,16 +126,15 @@ pub(crate) fn show_no_more_matches(window: &mut Window, cx: &mut App) {
window.defer(cx, |window, cx| {
struct NotifType();
let notification_id = NotificationId::unique::<NotifType>();
let Some(workspace) = window.window_handle().downcast::<Workspace>() else {
let Some(workspace) = window.root::<Workspace>().flatten() else {
return;
};
workspace
.update(cx, |workspace, _, cx| {
workspace.show_toast(
Toast::new(notification_id.clone(), "No more matches").autohide(),
cx,
);
})
.ok();
workspace.update(cx, |workspace, cx| {
workspace.show_toast(
Toast::new(notification_id.clone(), "No more matches").autohide(),
cx,
);
})
});
}

View file

@ -1,6 +1,4 @@
use crate::{Toast, Workspace};
use anyhow::Context as _;
use anyhow::{anyhow, Result};
use gpui::{
svg, AnyView, App, AppContext as _, AsyncWindowContext, ClipboardItem, Context, DismissEvent,
Entity, EventEmitter, Global, PromptLevel, Render, ScrollHandle, Task,
@ -535,72 +533,61 @@ pub fn show_app_notification<V: Notification + 'static>(
id: NotificationId,
cx: &mut App,
build_notification: impl Fn(&mut Context<Workspace>) -> Entity<V> + 'static,
) -> Result<()> {
// Handle dismiss events by removing the notification from all workspaces.
let build_notification: Rc<dyn Fn(&mut Context<Workspace>) -> AnyView> = Rc::new({
let id = id.clone();
move |cx| {
let notification = build_notification(cx);
cx.subscribe(&notification, {
let id = id.clone();
move |_, _, _: &DismissEvent, cx| {
dismiss_app_notification(&id, cx);
}
})
.detach();
notification.into()
}
});
) {
// Defer notification creation so that windows on the stack can be returned to GPUI
cx.defer(move |cx| {
// Handle dismiss events by removing the notification from all workspaces.
let build_notification: Rc<dyn Fn(&mut Context<Workspace>) -> AnyView> = Rc::new({
let id = id.clone();
move |cx| {
let notification = build_notification(cx);
cx.subscribe(&notification, {
let id = id.clone();
move |_, _, _: &DismissEvent, cx| {
dismiss_app_notification(&id, cx);
}
})
.detach();
notification.into()
}
});
// Store the notification so that new workspaces also receive it.
cx.global_mut::<GlobalAppNotifications>()
.insert(id.clone(), build_notification.clone());
// Store the notification so that new workspaces also receive it.
cx.global_mut::<GlobalAppNotifications>()
.insert(id.clone(), build_notification.clone());
let mut notify_errors = Vec::new();
for window in cx.windows() {
if let Some(workspace_window) = window.downcast::<Workspace>() {
let notify_result = workspace_window.update(cx, |workspace, _window, cx| {
workspace.show_notification_without_handling_dismiss_events(&id, cx, |cx| {
build_notification(cx)
});
});
match notify_result {
Ok(()) => {}
Err(notify_err) => notify_errors.push(notify_err),
for window in cx.windows() {
if let Some(workspace_window) = window.downcast::<Workspace>() {
workspace_window
.update(cx, |workspace, _window, cx| {
workspace.show_notification_without_handling_dismiss_events(
&id,
cx,
|cx| build_notification(cx),
);
})
.ok(); // Doesn't matter if the windows are dropped
}
}
}
if notify_errors.is_empty() {
Ok(())
} else {
Err(anyhow!(
"No workspaces were able to show notification. Errors:\n\n{}",
notify_errors
.iter()
.map(|e| e.to_string())
.collect::<Vec<_>>()
.join("\n\n")
))
}
});
}
pub fn dismiss_app_notification(id: &NotificationId, cx: &mut App) {
cx.global_mut::<GlobalAppNotifications>().remove(id);
for window in cx.windows() {
if let Some(workspace_window) = window.downcast::<Workspace>() {
let id = id.clone();
// This spawn is necessary in order to dismiss the notification on which the click
// occurred, because in that case we're already in the middle of an update.
cx.spawn(move |mut cx| async move {
workspace_window.update(&mut cx, |workspace, _window, cx| {
workspace.dismiss_notification(&id, cx)
})
})
.detach_and_log_err(cx);
let id = id.clone();
// Defer notification dismissal so that windows on the stack can be returned to GPUI
cx.defer(move |cx| {
cx.global_mut::<GlobalAppNotifications>().remove(&id);
for window in cx.windows() {
if let Some(workspace_window) = window.downcast::<Workspace>() {
let id = id.clone();
workspace_window
.update(cx, |workspace, _window, cx| {
workspace.dismiss_notification(&id, cx)
})
.ok();
}
}
}
});
}
pub trait NotifyResultExt {
@ -662,9 +649,8 @@ where
move |_cx| ErrorMessagePrompt::new(message)
})
}
})
.with_context(|| format!("Error while showing error notification: {message}"))
.log_err();
});
None
}
}

View file

@ -5607,36 +5607,6 @@ impl std::fmt::Debug for OpenPaths {
}
}
pub fn activate_workspace_for_project(
cx: &mut App,
predicate: impl Fn(&Project, &App) -> bool + Send + 'static,
) -> Option<WindowHandle<Workspace>> {
for window in cx.windows() {
let Some(workspace) = window.downcast::<Workspace>() else {
continue;
};
let predicate = workspace
.update(cx, |workspace, window, cx| {
let project = workspace.project.read(cx);
if predicate(project, cx) {
window.activate_window();
true
} else {
false
}
})
.log_err()
.unwrap_or(false);
if predicate {
return Some(workspace);
}
}
None
}
pub async fn last_opened_workspace_location() -> Option<SerializedWorkspaceLocation> {
DB.last_workspace().await.log_err().flatten()
}

View file

@ -1200,8 +1200,7 @@ fn show_keymap_file_json_error(
cx.emit(DismissEvent);
})
})
})
.log_err();
});
}
fn show_keymap_file_load_error(
@ -1245,7 +1244,6 @@ fn show_keymap_file_load_error(
})
})
})
.log_err();
})
.ok();
})

View file

@ -9,7 +9,6 @@ use gpui::{AnyWindowHandle, App, AppContext as _, Context, Entity, WeakEntity, W
use language::language_settings::{all_language_settings, InlineCompletionProvider};
use settings::SettingsStore;
use supermaven::{Supermaven, SupermavenCompletionProvider};
use workspace::Workspace;
use zed_predict_tos::ZedPredictTos;
pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
@ -115,8 +114,9 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
return;
};
let Some(workspace) =
window.downcast::<Workspace>().and_then(|w| w.root(cx).ok())
let Some(Some(workspace)) = window
.update(cx, |_, window, _| window.root().flatten())
.ok()
else {
return;
};