diff --git a/crates/assistant/src/inline_assistant.rs b/crates/assistant/src/inline_assistant.rs index 808054d046..53f142c029 100644 --- a/crates/assistant/src/inline_assistant.rs +++ b/crates/assistant/src/inline_assistant.rs @@ -1878,19 +1878,17 @@ impl PromptEditor { ) { match event { EditorEvent::Edited { .. } => { - if let Some(workspace) = window.window_handle().downcast::() { - workspace - .update(cx, |workspace, _, cx| { - let is_via_ssh = workspace - .project() - .update(cx, |project, _| project.is_via_ssh()); + if let Some(workspace) = window.root::().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 diff --git a/crates/assistant2/src/inline_prompt_editor.rs b/crates/assistant2/src/inline_prompt_editor.rs index dd1c703e2a..a8c4c3d0f9 100644 --- a/crates/assistant2/src/inline_prompt_editor.rs +++ b/crates/assistant2/src/inline_prompt_editor.rs @@ -304,19 +304,17 @@ impl PromptEditor { ) { match event { EditorEvent::Edited { .. } => { - if let Some(workspace) = window.window_handle().downcast::() { - workspace - .update(cx, |workspace, _, cx| { - let is_via_ssh = workspace - .project() - .update(cx, |project, _| project.is_via_ssh()); + if let Some(workspace) = window.root::().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 diff --git a/crates/assistant_context_editor/src/context_editor.rs b/crates/assistant_context_editor/src/context_editor.rs index 5d3e6f20a6..ad8111586a 100644 --- a/crates/assistant_context_editor/src/context_editor.rs +++ b/crates/assistant_context_editor/src/context_editor.rs @@ -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, ) -> Option { - 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 diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index 5d79f00a77..626da984a9 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -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::() else { + let Some(workspace) = window.root::().flatten() else { return; }; match status { Status::Starting { task } => { - let Some(workspace) = window.window_handle().downcast::() else { - return; - }; - - let Ok(workspace) = workspace.update(cx, |workspace, _window, cx| { + workspace.update(cx, |workspace, cx| { workspace.show_toast( Toast::new( NotificationId::unique::(), @@ -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) + }); + }); } } } diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 1470875508..7715471a1f 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -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, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 272243fd6f..6e42d1bafe 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -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); } } } diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index 7defe355be..5143a02283 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -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::(); - 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::(); + if let Some(scroll_position) = scroll_position { + editor.set_scroll_position(scroll_position, window, cx); + } + cx.notify(); + }) } fn on_line_editor_event( diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 5546178eac..c197d2a015 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -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 diff --git a/crates/gpui/src/app/model_context.rs b/crates/gpui/src/app/model_context.rs index daac8c506d..17e161d8c9 100644 --- a/crates/gpui/src/app/model_context.rs +++ b/crates/gpui/src/app/model_context.rs @@ -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( + pub fn observe_release_in( &self, - observed: &Entity, + observed: &Entity, 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. diff --git a/crates/gpui/src/gpui.rs b/crates/gpui/src/gpui.rs index c991c3fdfa..590aa4cd87 100644 --- a/crates/gpui/src/gpui.rs +++ b/crates/gpui/src/gpui.rs @@ -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::*; diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 906d29e9c7..ac397dddcc 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -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; } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index da7483fd51..3cb84102a7 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -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, cx: &mut App) -> Bounds { const DEFAULT_WINDOW_OFFSET: Point = 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 WindowHandle { /// 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(&self, cx: &mut C) -> Result> 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::() .map_err(|_| anyhow!("the type of the window's root view has changed")) diff --git a/crates/gpui_macros/Cargo.toml b/crates/gpui_macros/Cargo.toml index 04c175e69a..c8236245e6 100644 --- a/crates/gpui_macros/Cargo.toml +++ b/crates/gpui_macros/Cargo.toml @@ -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" diff --git a/crates/gpui_macros/src/derive_app_context.rs b/crates/gpui_macros/src/derive_app_context.rs new file mode 100644 index 0000000000..943823ee61 --- /dev/null +++ b/crates/gpui_macros/src/derive_app_context.rs @@ -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; + + fn new( + &mut self, + build_model: impl FnOnce(&mut gpui::Context<'_, T>) -> T, + ) -> Self::Result> { + self.#app_variable.new(build_model) + } + + fn reserve_entity(&mut self) -> Self::Result> { + self.#app_variable.reserve_entity() + } + + fn insert_entity( + &mut self, + reservation: gpui::Reservation, + build_model: impl FnOnce(&mut gpui::Context<'_, T>) -> T, + ) -> Self::Result> { + self.#app_variable.insert_entity(reservation, build_model) + } + + fn update_entity( + &mut self, + handle: &gpui::Entity, + update: impl FnOnce(&mut T, &mut gpui::Context<'_, T>) -> R, + ) -> Self::Result + where + T: 'static, + { + self.#app_variable.update_entity(handle, update) + } + + fn read_entity( + &self, + handle: &gpui::Entity, + read: impl FnOnce(&T, &gpui::App) -> R, + ) -> Self::Result + where + T: 'static, + { + self.#app_variable.read_entity(handle, read) + } + + fn update_window(&mut self, window: gpui::AnyWindowHandle, f: F) -> gpui::Result + where + F: FnOnce(gpui::AnyView, &mut gpui::Window, &mut gpui::App) -> T, + { + self.#app_variable.update_window(window, f) + } + + fn read_window( + &self, + window: &gpui::WindowHandle, + read: impl FnOnce(gpui::Entity, &gpui::App) -> R, + ) -> gpui::Result + where + T: 'static, + { + self.#app_variable.read_window(window, read) + } + } + }; + + gen.into() +} diff --git a/crates/gpui_macros/src/derive_visual_context.rs b/crates/gpui_macros/src/derive_visual_context.rs new file mode 100644 index 0000000000..7a43b35128 --- /dev/null +++ b/crates/gpui_macros/src/derive_visual_context.rs @@ -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( + &mut self, + model: &gpui::Entity, + update: impl FnOnce(&mut T, &mut gpui::Window, &mut gpui::Context) -> R, + ) -> Self::Result { + gpui::AppContext::update_entity(self.#app_variable, model, |entity, cx| update(entity, self.#window_variable, cx)) + } + + fn new_window_entity( + &mut self, + build_model: impl FnOnce(&mut gpui::Window, &mut gpui::Context<'_, T>) -> T, + ) -> Self::Result> { + gpui::AppContext::new(self.#app_variable, |cx| build_model(self.#window_variable, cx)) + } + + fn replace_root_view( + &mut self, + build_view: impl FnOnce(&mut gpui::Window, &mut gpui::Context) -> V, + ) -> Self::Result> + where + V: 'static + gpui::Render, + { + self.#window_variable.replace_root(self.#app_variable, build_view) + } + + fn focus(&mut self, model: &gpui::Entity) -> 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() +} diff --git a/crates/gpui_macros/src/gpui_macros.rs b/crates/gpui_macros/src/gpui_macros.rs index 09cf4027d2..a0af5253ff 100644 --- a/crates/gpui_macros/src/gpui_macros.rs +++ b/crates/gpui_macros/src/gpui_macros.rs @@ -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 { + 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, + } +} diff --git a/crates/gpui_macros/tests/derive_context.rs b/crates/gpui_macros/tests/derive_context.rs new file mode 100644 index 0000000000..6c122eff25 --- /dev/null +++ b/crates/gpui_macros/tests/derive_context.rs @@ -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, + } +} diff --git a/crates/inline_completion_button/src/inline_completion_button.rs b/crates/inline_completion_button/src/inline_completion_button.rs index 5937265250..992d02733f 100644 --- a/crates/inline_completion_button/src/inline_completion_button.rs +++ b/crates/inline_completion_button/src/inline_completion_button.rs @@ -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 - .update(cx, |workspace, _, cx| { - workspace.show_toast( - Toast::new( - NotificationId::unique::(), - format!("Copilot can't be started: {}", e), - ) - .on_click("Reinstall Copilot", |_, cx| { + if let Some(workspace) = window.root::().flatten() { + workspace.update(cx, |workspace, cx| { + workspace.show_toast( + Toast::new( + NotificationId::unique::(), + 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::() { - 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); } }, ); diff --git a/crates/prompt_library/src/prompt_library.rs b/crates/prompt_library/src/prompt_library.rs index aa652eff57..be5e31d6b5 100644 --- a/crates/prompt_library/src/prompt_library.rs +++ b/crates/prompt_library/src/prompt_library.rs @@ -81,49 +81,62 @@ pub fn open_prompt_library( make_completion_provider: Arc Box>, cx: &mut App, ) -> Task>> { - let existing_window = cx - .windows() - .into_iter() - .find_map(|window| window.downcast::()); - 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::()); + 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 { diff --git a/crates/search/src/search.rs b/crates/search/src/search.rs index ec54da3795..28b61b5605 100644 --- a/crates/search/src/search.rs +++ b/crates/search/src/search.rs @@ -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::(); - let Some(workspace) = window.window_handle().downcast::() else { + + let Some(workspace) = window.root::().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, + ); + }) }); } diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index afa302158a..72275f8b54 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -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( id: NotificationId, cx: &mut App, build_notification: impl Fn(&mut Context) -> Entity + 'static, -) -> Result<()> { - // Handle dismiss events by removing the notification from all workspaces. - let build_notification: Rc) -> AnyView> = Rc::new({ - let id = id.clone(); - move |cx| { - let notification = build_notification(cx); - cx.subscribe(¬ification, { - 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) -> AnyView> = Rc::new({ + let id = id.clone(); + move |cx| { + let notification = build_notification(cx); + cx.subscribe(¬ification, { + 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::() - .insert(id.clone(), build_notification.clone()); + // Store the notification so that new workspaces also receive it. + cx.global_mut::() + .insert(id.clone(), build_notification.clone()); - let mut notify_errors = Vec::new(); - - for window in cx.windows() { - if let Some(workspace_window) = window.downcast::() { - 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_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::>() - .join("\n\n") - )) - } + }); } pub fn dismiss_app_notification(id: &NotificationId, cx: &mut App) { - cx.global_mut::().remove(id); - for window in cx.windows() { - if let Some(workspace_window) = window.downcast::() { - 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::().remove(&id); + for window in cx.windows() { + if let Some(workspace_window) = window.downcast::() { + 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 } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 562a6ad5e1..72589725aa 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -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> { - for window in cx.windows() { - let Some(workspace) = window.downcast::() 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 { DB.last_workspace().await.log_err().flatten() } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 80a9306d19..408ca87a22 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -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(); }) diff --git a/crates/zed/src/zed/inline_completion_registry.rs b/crates/zed/src/zed/inline_completion_registry.rs index 10a6f5549f..457cc4ae28 100644 --- a/crates/zed/src/zed/inline_completion_registry.rs +++ b/crates/zed/src/zed/inline_completion_registry.rs @@ -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, user_store: Entity, cx: &mut App) { @@ -115,8 +114,9 @@ pub fn init(client: Arc, user_store: Entity, cx: &mut App) { return; }; - let Some(workspace) = - window.downcast::().and_then(|w| w.root(cx).ok()) + let Some(Some(workspace)) = window + .update(cx, |_, window, _| window.root().flatten()) + .ok() else { return; };