Detailed changes
@@ -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());
-
- workspace
- .client()
- .telemetry()
- .log_edit_event("inline assist", is_via_ssh);
- })
- .log_err();
+ 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);
+ });
}
let prompt = self.editor.read(cx).text(cx);
if self
@@ -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());
-
- workspace
- .client()
- .telemetry()
- .log_edit_event("inline assist", is_via_ssh);
- })
- .log_err();
+ 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);
+ });
}
let prompt = self.editor.read(cx).text(cx);
if self
@@ -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
@@ -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)
+ });
+ });
}
}
}
@@ -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,
@@ -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);
}
}
}
@@ -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(
@@ -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
@@ -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.
@@ -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::*;
@@ -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;
}
}
@@ -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"))
@@ -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"
@@ -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()
+}
@@ -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()
+}
@@ -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,
+ }
+}
@@ -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,
+ }
+}
@@ -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);
}
},
);
@@ -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 {
@@ -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,
+ );
+ })
});
}
@@ -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(¬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::<GlobalAppNotifications>()
- .insert(id.clone(), build_notification.clone());
-
- let mut notify_errors = Vec::new();
+) {
+ // 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(¬ification, {
+ let id = id.clone();
+ move |_, _, _: &DismissEvent, cx| {
+ dismiss_app_notification(&id, cx);
+ }
+ })
+ .detach();
+ notification.into()
+ }
+ });
- 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),
+ // Store the notification so that new workspaces also receive it.
+ cx.global_mut::<GlobalAppNotifications>()
+ .insert(id.clone(), build_notification.clone());
+
+ 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
}
}
@@ -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()
}
@@ -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();
})
@@ -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;
};