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:
parent
29bfb56739
commit
a7c549b85b
24 changed files with 465 additions and 297 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
88
crates/gpui_macros/src/derive_app_context.rs
Normal file
88
crates/gpui_macros/src/derive_app_context.rs
Normal 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()
|
||||
}
|
71
crates/gpui_macros/src/derive_visual_context.rs
Normal file
71
crates/gpui_macros/src/derive_visual_context.rs
Normal 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()
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
13
crates/gpui_macros/tests/derive_context.rs
Normal file
13
crates/gpui_macros/tests/derive_context.rs
Normal 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,
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
});
|
||||
) {
|
||||
// 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()
|
||||
}
|
||||
});
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue