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,9 +1878,8 @@ impl PromptEditor {
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
EditorEvent::Edited { .. } => {
|
EditorEvent::Edited { .. } => {
|
||||||
if let Some(workspace) = window.window_handle().downcast::<Workspace>() {
|
if let Some(workspace) = window.root::<Workspace>().flatten() {
|
||||||
workspace
|
workspace.update(cx, |workspace, cx| {
|
||||||
.update(cx, |workspace, _, cx| {
|
|
||||||
let is_via_ssh = workspace
|
let is_via_ssh = workspace
|
||||||
.project()
|
.project()
|
||||||
.update(cx, |project, _| project.is_via_ssh());
|
.update(cx, |project, _| project.is_via_ssh());
|
||||||
|
@ -1889,8 +1888,7 @@ impl PromptEditor {
|
||||||
.client()
|
.client()
|
||||||
.telemetry()
|
.telemetry()
|
||||||
.log_edit_event("inline assist", is_via_ssh);
|
.log_edit_event("inline assist", is_via_ssh);
|
||||||
})
|
});
|
||||||
.log_err();
|
|
||||||
}
|
}
|
||||||
let prompt = self.editor.read(cx).text(cx);
|
let prompt = self.editor.read(cx).text(cx);
|
||||||
if self
|
if self
|
||||||
|
|
|
@ -304,9 +304,8 @@ impl<T: 'static> PromptEditor<T> {
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
EditorEvent::Edited { .. } => {
|
EditorEvent::Edited { .. } => {
|
||||||
if let Some(workspace) = window.window_handle().downcast::<Workspace>() {
|
if let Some(workspace) = window.root::<Workspace>().flatten() {
|
||||||
workspace
|
workspace.update(cx, |workspace, cx| {
|
||||||
.update(cx, |workspace, _, cx| {
|
|
||||||
let is_via_ssh = workspace
|
let is_via_ssh = workspace
|
||||||
.project()
|
.project()
|
||||||
.update(cx, |project, _| project.is_via_ssh());
|
.update(cx, |project, _| project.is_via_ssh());
|
||||||
|
@ -315,8 +314,7 @@ impl<T: 'static> PromptEditor<T> {
|
||||||
.client()
|
.client()
|
||||||
.telemetry()
|
.telemetry()
|
||||||
.log_edit_event("inline assist", is_via_ssh);
|
.log_edit_event("inline assist", is_via_ssh);
|
||||||
})
|
});
|
||||||
.log_err();
|
|
||||||
}
|
}
|
||||||
let prompt = self.editor.read(cx).text(cx);
|
let prompt = self.editor.read(cx).text(cx);
|
||||||
if self
|
if self
|
||||||
|
|
|
@ -23,11 +23,11 @@ use fs::Fs;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, img, impl_internal_actions, percentage, point, prelude::*, pulsating_between,
|
actions, div, img, impl_internal_actions, percentage, point, prelude::*, pulsating_between,
|
||||||
size, Animation, AnimationExt, AnyElement, AnyView, AnyWindowHandle, App, AsyncWindowContext,
|
size, Animation, AnimationExt, AnyElement, AnyView, App, AsyncWindowContext, ClipboardEntry,
|
||||||
ClipboardEntry, ClipboardItem, CursorStyle, Empty, Entity, EventEmitter, FocusHandle,
|
ClipboardItem, CursorStyle, Empty, Entity, EventEmitter, FocusHandle, Focusable, FontWeight,
|
||||||
Focusable, FontWeight, Global, InteractiveElement, IntoElement, ParentElement, Pixels, Render,
|
Global, InteractiveElement, IntoElement, ParentElement, Pixels, Render, RenderImage,
|
||||||
RenderImage, SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task,
|
SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task, Transformation,
|
||||||
Transformation, WeakEntity,
|
WeakEntity,
|
||||||
};
|
};
|
||||||
use indexed_docs::IndexedDocsStore;
|
use indexed_docs::IndexedDocsStore;
|
||||||
use language::{language_settings::SoftWrap, BufferSnapshot, LspAdapterDelegate, ToOffset};
|
use language::{language_settings::SoftWrap, BufferSnapshot, LspAdapterDelegate, ToOffset};
|
||||||
|
@ -978,21 +978,20 @@ impl ContextEditor {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let render_block: RenderBlock = Arc::new({
|
let render_block: RenderBlock = Arc::new({
|
||||||
let this = this.clone();
|
let this = this.clone();
|
||||||
let window_handle = window.window_handle();
|
|
||||||
let patch_range = range.clone();
|
let patch_range = range.clone();
|
||||||
move |cx: &mut BlockContext<'_, '_>| {
|
move |cx: &mut BlockContext<'_, '_>| {
|
||||||
let max_width = cx.max_width;
|
let max_width = cx.max_width;
|
||||||
let gutter_width = cx.gutter_dimensions.full_width();
|
let gutter_width = cx.gutter_dimensions.full_width();
|
||||||
let block_id = cx.block_id;
|
let block_id = cx.block_id;
|
||||||
let selected = cx.selected;
|
let selected = cx.selected;
|
||||||
this.update(&mut **cx, |this, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
this.render_patch_block(
|
this.render_patch_block(
|
||||||
patch_range.clone(),
|
patch_range.clone(),
|
||||||
max_width,
|
max_width,
|
||||||
gutter_width,
|
gutter_width,
|
||||||
block_id,
|
block_id,
|
||||||
selected,
|
selected,
|
||||||
window_handle,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -2198,15 +2197,12 @@ impl ContextEditor {
|
||||||
gutter_width: Pixels,
|
gutter_width: Pixels,
|
||||||
id: BlockId,
|
id: BlockId,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
window_handle: AnyWindowHandle,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Option<AnyElement> {
|
) -> Option<AnyElement> {
|
||||||
let snapshot = window_handle
|
let snapshot = self
|
||||||
.update(cx, |_, window, cx| {
|
.editor
|
||||||
self.editor
|
.update(cx, |editor, cx| editor.snapshot(window, cx));
|
||||||
.update(cx, |editor, cx| editor.snapshot(window, cx))
|
|
||||||
})
|
|
||||||
.ok()?;
|
|
||||||
let (excerpt_id, _buffer_id, _) = snapshot.buffer_snapshot.as_singleton().unwrap();
|
let (excerpt_id, _buffer_id, _) = snapshot.buffer_snapshot.as_singleton().unwrap();
|
||||||
let excerpt_id = *excerpt_id;
|
let excerpt_id = *excerpt_id;
|
||||||
let anchor = snapshot
|
let anchor = snapshot
|
||||||
|
|
|
@ -18,16 +18,12 @@ pub fn initiate_sign_in(window: &mut Window, cx: &mut App) {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let status = copilot.read(cx).status();
|
let status = copilot.read(cx).status();
|
||||||
let Some(workspace) = window.window_handle().downcast::<Workspace>() else {
|
let Some(workspace) = window.root::<Workspace>().flatten() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
match status {
|
match status {
|
||||||
Status::Starting { task } => {
|
Status::Starting { task } => {
|
||||||
let Some(workspace) = window.window_handle().downcast::<Workspace>() else {
|
workspace.update(cx, |workspace, cx| {
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok(workspace) = workspace.update(cx, |workspace, _window, cx| {
|
|
||||||
workspace.show_toast(
|
workspace.show_toast(
|
||||||
Toast::new(
|
Toast::new(
|
||||||
NotificationId::unique::<CopilotStartingToast>(),
|
NotificationId::unique::<CopilotStartingToast>(),
|
||||||
|
@ -35,11 +31,9 @@ pub fn initiate_sign_in(window: &mut Window, cx: &mut App) {
|
||||||
),
|
),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
workspace.weak_handle()
|
});
|
||||||
}) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
let workspace = workspace.downgrade();
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
task.await;
|
task.await;
|
||||||
if let Some(copilot) = cx.update(|cx| Copilot::global(cx)).ok().flatten() {
|
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();
|
copilot.update(cx, |this, cx| this.sign_in(cx)).detach();
|
||||||
workspace
|
workspace.update(cx, |this, cx| {
|
||||||
.update(cx, |this, window, cx| {
|
|
||||||
this.toggle_modal(window, cx, |_, cx| {
|
this.toggle_modal(window, cx, |_, cx| {
|
||||||
CopilotCodeVerification::new(&copilot, cx)
|
CopilotCodeVerification::new(&copilot, cx)
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,8 +225,11 @@ pub enum BlockStyle {
|
||||||
Sticky,
|
Sticky,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(gpui::AppContext, gpui::VisualContext)]
|
||||||
pub struct BlockContext<'a, 'b> {
|
pub struct BlockContext<'a, 'b> {
|
||||||
|
#[window]
|
||||||
pub window: &'a mut Window,
|
pub window: &'a mut Window,
|
||||||
|
#[app]
|
||||||
pub app: &'b mut App,
|
pub app: &'b mut App,
|
||||||
pub anchor_x: Pixels,
|
pub anchor_x: Pixels,
|
||||||
pub max_width: 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 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.
|
// In multi buffers, we open file at the line number clicked, so use a pointing hand cursor.
|
||||||
if is_singleton {
|
if is_singleton {
|
||||||
window.set_cursor_style(CursorStyle::IBeam, hitbox);
|
window.set_cursor_style(CursorStyle::IBeam, &hitbox);
|
||||||
} else {
|
} 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,
|
actions::Tab, scroll::Autoscroll, Anchor, Editor, MultiBufferSnapshot, ToOffset, ToPoint,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, prelude::*, AnyWindowHandle, App, DismissEvent, Entity, EventEmitter, FocusHandle,
|
div, prelude::*, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render,
|
||||||
Focusable, Render, SharedString, Styled, Subscription,
|
SharedString, Styled, Subscription,
|
||||||
};
|
};
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
|
@ -133,9 +133,7 @@ impl GoToLine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn release(&mut self, window: AnyWindowHandle, cx: &mut App) {
|
fn release(&mut self, window: &mut Window, cx: &mut App) {
|
||||||
window
|
|
||||||
.update(cx, |_, window, cx| {
|
|
||||||
let scroll_position = self.prev_scroll_position.take();
|
let scroll_position = self.prev_scroll_position.take();
|
||||||
self.active_editor.update(cx, |editor, cx| {
|
self.active_editor.update(cx, |editor, cx| {
|
||||||
editor.clear_row_highlights::<GoToLineRowHighlights>();
|
editor.clear_row_highlights::<GoToLineRowHighlights>();
|
||||||
|
@ -144,8 +142,6 @@ impl GoToLine {
|
||||||
}
|
}
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_line_editor_event(
|
fn on_line_editor_event(
|
||||||
|
|
|
@ -1236,15 +1236,9 @@ impl App {
|
||||||
T: 'static,
|
T: 'static,
|
||||||
{
|
{
|
||||||
let window_handle = window.handle;
|
let window_handle = window.handle;
|
||||||
let (subscription, activate) = self.release_listeners.insert(
|
self.observe_release(&handle, move |entity, cx| {
|
||||||
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));
|
let _ = window_handle.update(cx, |_, window, cx| on_release(entity, window, cx));
|
||||||
}),
|
})
|
||||||
);
|
|
||||||
activate();
|
|
||||||
subscription
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a callback to be invoked when a keystroke is received by the application
|
/// 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(
|
pub fn on_release_in(
|
||||||
&mut self,
|
&mut self,
|
||||||
window: &Window,
|
window: &Window,
|
||||||
on_release: impl FnOnce(&mut T, AnyWindowHandle, &mut App) + 'static,
|
on_release: impl FnOnce(&mut T, &mut Window, &mut App) + 'static,
|
||||||
) -> Subscription {
|
) -> Subscription {
|
||||||
let window_handle = window.handle;
|
let entity = self.entity();
|
||||||
let (subscription, activate) = self.release_listeners.insert(
|
self.app.observe_release_in(&entity, window, on_release)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a callback to be invoked when the given Model or View is released.
|
/// 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,
|
&self,
|
||||||
observed: &Entity<V2>,
|
observed: &Entity<T2>,
|
||||||
window: &Window,
|
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
|
) -> Subscription
|
||||||
where
|
where
|
||||||
T: 'static,
|
T: 'static,
|
||||||
V2: 'static,
|
T2: 'static,
|
||||||
{
|
{
|
||||||
let observer = self.weak_entity();
|
let observer = self.weak_entity();
|
||||||
let window_handle = window.handle;
|
self.app
|
||||||
let (subscription, activate) = self.release_listeners.insert(
|
.observe_release_in(observed, window, move |observed, window, cx| {
|
||||||
observed.entity_id(),
|
observer
|
||||||
Box::new(move |observed, cx| {
|
.update(cx, |observer, cx| {
|
||||||
let observed = observed
|
on_release(observer, observed, window, cx)
|
||||||
.downcast_mut()
|
})
|
||||||
.expect("invalid observed entity type");
|
.ok();
|
||||||
let _ = window_handle.update(cx, |_, window, cx| {
|
})
|
||||||
observer.update(cx, |this, cx| on_release(this, observed, window, cx))
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
activate();
|
|
||||||
subscription
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a callback to be invoked when the window is resized.
|
/// Register a callback to be invoked when the window is resized.
|
||||||
|
|
|
@ -129,7 +129,7 @@ pub use elements::*;
|
||||||
pub use executor::*;
|
pub use executor::*;
|
||||||
pub use geometry::*;
|
pub use geometry::*;
|
||||||
pub use global::*;
|
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 http_client;
|
||||||
pub use input::*;
|
pub use input::*;
|
||||||
pub use interactive::*;
|
pub use interactive::*;
|
||||||
|
|
|
@ -1271,7 +1271,7 @@ impl ClipboardItem {
|
||||||
|
|
||||||
for entry in self.entries.iter() {
|
for entry in self.entries.iter() {
|
||||||
if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
|
if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
|
||||||
answer.push_str(text);
|
answer.push_str(&text);
|
||||||
any_entries = true;
|
any_entries = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ use crate::{
|
||||||
AnyView, App, AppContext, Arena, Asset, AsyncWindowContext, AvailableSpace, Background, Bounds,
|
AnyView, App, AppContext, Arena, Asset, AsyncWindowContext, AvailableSpace, Background, Bounds,
|
||||||
BoxShadow, Context, Corners, CursorStyle, Decorations, DevicePixels, DispatchActionListener,
|
BoxShadow, Context, Corners, CursorStyle, Decorations, DevicePixels, DispatchActionListener,
|
||||||
DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
|
DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
|
||||||
FileDropEvent, Flatten, FontId, Global, GlobalElementId, GlyphId, GpuSpecs, Hsla, InputHandler,
|
FileDropEvent, FontId, Global, GlobalElementId, GlyphId, GpuSpecs, Hsla, InputHandler, IsZero,
|
||||||
IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent, Keystroke, KeystrokeEvent, LayoutId,
|
KeyBinding, KeyContext, KeyDownEvent, KeyEvent, Keystroke, KeystrokeEvent, LayoutId,
|
||||||
LineLayoutIndex, Modifiers, ModifiersChangedEvent, MonochromeSprite, MouseButton, MouseEvent,
|
LineLayoutIndex, Modifiers, ModifiersChangedEvent, MonochromeSprite, MouseButton, MouseEvent,
|
||||||
MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
|
MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
|
||||||
PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render,
|
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> {
|
fn default_bounds(display_id: Option<DisplayId>, cx: &mut App) -> Bounds<Pixels> {
|
||||||
const DEFAULT_WINDOW_OFFSET: Point<Pixels> = point(px(0.), px(35.));
|
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()
|
cx.active_window()
|
||||||
.and_then(|w| w.update(cx, |_, window, _| window.bounds()).ok())
|
.and_then(|w| w.update(cx, |_, window, _| window.bounds()).ok())
|
||||||
.map(|mut bounds| {
|
.map(|mut bounds| {
|
||||||
|
@ -3775,11 +3778,12 @@ impl<V: 'static + Render> WindowHandle<V> {
|
||||||
/// Get the root view out of this window.
|
/// 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`.
|
/// 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>>
|
pub fn root<C>(&self, cx: &mut C) -> Result<Entity<V>>
|
||||||
where
|
where
|
||||||
C: AppContext,
|
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
|
root_view
|
||||||
.downcast::<V>()
|
.downcast::<V>()
|
||||||
.map_err(|_| anyhow!("the type of the window's root view has changed"))
|
.map_err(|_| anyhow!("the type of the window's root view has changed"))
|
||||||
|
|
|
@ -11,7 +11,7 @@ workspace = true
|
||||||
[lib]
|
[lib]
|
||||||
path = "src/gpui_macros.rs"
|
path = "src/gpui_macros.rs"
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
doctest = false
|
doctest = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
proc-macro2 = "1.0.66"
|
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_into_element;
|
||||||
mod derive_path_static_str;
|
mod derive_path_static_str;
|
||||||
mod derive_render;
|
mod derive_render;
|
||||||
|
mod derive_visual_context;
|
||||||
mod register_action;
|
mod register_action;
|
||||||
mod styles;
|
mod styles;
|
||||||
mod test;
|
mod test;
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
|
use syn::{DeriveInput, Ident};
|
||||||
|
|
||||||
/// register_action! can be used to register an action with the GPUI runtime.
|
/// register_action! can be used to register an action with the GPUI runtime.
|
||||||
/// You should typically use `gpui::actions!` or `gpui::impl_actions!` instead,
|
/// 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_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.
|
/// Used by GPUI to generate the style helpers.
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
@ -115,3 +169,15 @@ pub fn box_shadow_style_methods(input: TokenStream) -> TokenStream {
|
||||||
pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
|
pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
|
||||||
test::test(args, function)
|
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)
|
IconButton::new("copilot-error", icon)
|
||||||
.icon_size(IconSize::Small)
|
.icon_size(IconSize::Small)
|
||||||
.on_click(cx.listener(move |_, _, window, cx| {
|
.on_click(cx.listener(move |_, _, window, cx| {
|
||||||
if let Some(workspace) =
|
if let Some(workspace) = window.root::<Workspace>().flatten() {
|
||||||
window.window_handle().downcast::<Workspace>()
|
workspace.update(cx, |workspace, cx| {
|
||||||
{
|
|
||||||
workspace
|
|
||||||
.update(cx, |workspace, _, cx| {
|
|
||||||
workspace.show_toast(
|
workspace.show_toast(
|
||||||
Toast::new(
|
Toast::new(
|
||||||
NotificationId::unique::<CopilotErrorToast>(),
|
NotificationId::unique::<CopilotErrorToast>(),
|
||||||
format!("Copilot can't be started: {}", e),
|
format!("Copilot can't be started: {}", e),
|
||||||
)
|
)
|
||||||
.on_click("Reinstall Copilot", |_, cx| {
|
.on_click(
|
||||||
|
"Reinstall Copilot",
|
||||||
|
|_, cx| {
|
||||||
if let Some(copilot) = Copilot::global(cx) {
|
if let Some(copilot) = Copilot::global(cx) {
|
||||||
copilot
|
copilot
|
||||||
.update(cx, |copilot, cx| {
|
.update(cx, |copilot, cx| {
|
||||||
|
@ -109,11 +108,11 @@ impl Render for InlineCompletionButton {
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
})
|
});
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.tooltip(|window, cx| {
|
.tooltip(|window, cx| {
|
||||||
|
@ -398,8 +397,7 @@ impl InlineCompletionButton {
|
||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
move |window, cx| {
|
move |window, cx| {
|
||||||
if let Some(workspace) = window.window_handle().downcast::<Workspace>() {
|
if let Some(workspace) = window.root().flatten() {
|
||||||
if let Ok(workspace) = workspace.root(cx) {
|
|
||||||
let workspace = workspace.downgrade();
|
let workspace = workspace.downgrade();
|
||||||
window
|
window
|
||||||
.spawn(cx, |cx| {
|
.spawn(cx, |cx| {
|
||||||
|
@ -411,7 +409,6 @@ impl InlineCompletionButton {
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,11 @@ pub fn open_prompt_library(
|
||||||
make_completion_provider: Arc<dyn Fn() -> Box<dyn CompletionProvider>>,
|
make_completion_provider: Arc<dyn Fn() -> Box<dyn CompletionProvider>>,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<WindowHandle<PromptLibrary>>> {
|
) -> Task<Result<WindowHandle<PromptLibrary>>> {
|
||||||
|
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
|
let existing_window = cx
|
||||||
.windows()
|
.windows()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -89,10 +94,19 @@ pub fn open_prompt_library(
|
||||||
existing_window
|
existing_window
|
||||||
.update(cx, |_, window, _| window.activate_window())
|
.update(cx, |_, window, _| window.activate_window())
|
||||||
.ok();
|
.ok();
|
||||||
Task::ready(Ok(existing_window))
|
|
||||||
|
Some(existing_window)
|
||||||
} else {
|
} else {
|
||||||
let store = PromptStore::global(cx);
|
None
|
||||||
cx.spawn(|cx| async move {
|
}
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
.flatten();
|
||||||
|
|
||||||
|
if let Some(existing_window) = existing_window {
|
||||||
|
return Ok(existing_window);
|
||||||
|
}
|
||||||
|
|
||||||
let store = store.await?;
|
let store = store.await?;
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
let app_id = ReleaseChannel::global(cx).app_id();
|
let app_id = ReleaseChannel::global(cx).app_id();
|
||||||
|
@ -124,7 +138,6 @@ pub fn open_prompt_library(
|
||||||
})?
|
})?
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PromptLibrary {
|
pub struct PromptLibrary {
|
||||||
store: Arc<PromptStore>,
|
store: Arc<PromptStore>,
|
||||||
|
|
|
@ -126,16 +126,15 @@ pub(crate) fn show_no_more_matches(window: &mut Window, cx: &mut App) {
|
||||||
window.defer(cx, |window, cx| {
|
window.defer(cx, |window, cx| {
|
||||||
struct NotifType();
|
struct NotifType();
|
||||||
let notification_id = NotificationId::unique::<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;
|
return;
|
||||||
};
|
};
|
||||||
workspace
|
workspace.update(cx, |workspace, cx| {
|
||||||
.update(cx, |workspace, _, cx| {
|
|
||||||
workspace.show_toast(
|
workspace.show_toast(
|
||||||
Toast::new(notification_id.clone(), "No more matches").autohide(),
|
Toast::new(notification_id.clone(), "No more matches").autohide(),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.ok();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
use crate::{Toast, Workspace};
|
use crate::{Toast, Workspace};
|
||||||
use anyhow::Context as _;
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
svg, AnyView, App, AppContext as _, AsyncWindowContext, ClipboardItem, Context, DismissEvent,
|
svg, AnyView, App, AppContext as _, AsyncWindowContext, ClipboardItem, Context, DismissEvent,
|
||||||
Entity, EventEmitter, Global, PromptLevel, Render, ScrollHandle, Task,
|
Entity, EventEmitter, Global, PromptLevel, Render, ScrollHandle, Task,
|
||||||
|
@ -535,7 +533,9 @@ pub fn show_app_notification<V: Notification + 'static>(
|
||||||
id: NotificationId,
|
id: NotificationId,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
build_notification: impl Fn(&mut Context<Workspace>) -> Entity<V> + 'static,
|
build_notification: impl Fn(&mut Context<Workspace>) -> Entity<V> + 'static,
|
||||||
) -> Result<()> {
|
) {
|
||||||
|
// 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.
|
// Handle dismiss events by removing the notification from all workspaces.
|
||||||
let build_notification: Rc<dyn Fn(&mut Context<Workspace>) -> AnyView> = Rc::new({
|
let build_notification: Rc<dyn Fn(&mut Context<Workspace>) -> AnyView> = Rc::new({
|
||||||
let id = id.clone();
|
let id = id.clone();
|
||||||
|
@ -556,51 +556,38 @@ pub fn show_app_notification<V: Notification + 'static>(
|
||||||
cx.global_mut::<GlobalAppNotifications>()
|
cx.global_mut::<GlobalAppNotifications>()
|
||||||
.insert(id.clone(), build_notification.clone());
|
.insert(id.clone(), build_notification.clone());
|
||||||
|
|
||||||
let mut notify_errors = Vec::new();
|
|
||||||
|
|
||||||
for window in cx.windows() {
|
for window in cx.windows() {
|
||||||
if let Some(workspace_window) = window.downcast::<Workspace>() {
|
if let Some(workspace_window) = window.downcast::<Workspace>() {
|
||||||
let notify_result = workspace_window.update(cx, |workspace, _window, cx| {
|
workspace_window
|
||||||
workspace.show_notification_without_handling_dismiss_events(&id, cx, |cx| {
|
.update(cx, |workspace, _window, cx| {
|
||||||
build_notification(cx)
|
workspace.show_notification_without_handling_dismiss_events(
|
||||||
|
&id,
|
||||||
|
cx,
|
||||||
|
|cx| build_notification(cx),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.ok(); // Doesn't matter if the windows are dropped
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
match notify_result {
|
|
||||||
Ok(()) => {}
|
|
||||||
Err(notify_err) => notify_errors.push(notify_err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
pub fn dismiss_app_notification(id: &NotificationId, cx: &mut App) {
|
||||||
cx.global_mut::<GlobalAppNotifications>().remove(id);
|
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() {
|
for window in cx.windows() {
|
||||||
if let Some(workspace_window) = window.downcast::<Workspace>() {
|
if let Some(workspace_window) = window.downcast::<Workspace>() {
|
||||||
let id = id.clone();
|
let id = id.clone();
|
||||||
// This spawn is necessary in order to dismiss the notification on which the click
|
workspace_window
|
||||||
// occurred, because in that case we're already in the middle of an update.
|
.update(cx, |workspace, _window, cx| {
|
||||||
cx.spawn(move |mut cx| async move {
|
|
||||||
workspace_window.update(&mut cx, |workspace, _window, cx| {
|
|
||||||
workspace.dismiss_notification(&id, cx)
|
workspace.dismiss_notification(&id, cx)
|
||||||
})
|
})
|
||||||
})
|
.ok();
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait NotifyResultExt {
|
pub trait NotifyResultExt {
|
||||||
|
@ -662,9 +649,8 @@ where
|
||||||
move |_cx| ErrorMessagePrompt::new(message)
|
move |_cx| ErrorMessagePrompt::new(message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.with_context(|| format!("Error while showing error notification: {message}"))
|
|
||||||
.log_err();
|
|
||||||
None
|
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> {
|
pub async fn last_opened_workspace_location() -> Option<SerializedWorkspaceLocation> {
|
||||||
DB.last_workspace().await.log_err().flatten()
|
DB.last_workspace().await.log_err().flatten()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1200,8 +1200,7 @@ fn show_keymap_file_json_error(
|
||||||
cx.emit(DismissEvent);
|
cx.emit(DismissEvent);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
});
|
||||||
.log_err();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_keymap_file_load_error(
|
fn show_keymap_file_load_error(
|
||||||
|
@ -1245,7 +1244,6 @@ fn show_keymap_file_load_error(
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.log_err();
|
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
})
|
})
|
||||||
|
|
|
@ -9,7 +9,6 @@ use gpui::{AnyWindowHandle, App, AppContext as _, Context, Entity, WeakEntity, W
|
||||||
use language::language_settings::{all_language_settings, InlineCompletionProvider};
|
use language::language_settings::{all_language_settings, InlineCompletionProvider};
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
use supermaven::{Supermaven, SupermavenCompletionProvider};
|
use supermaven::{Supermaven, SupermavenCompletionProvider};
|
||||||
use workspace::Workspace;
|
|
||||||
use zed_predict_tos::ZedPredictTos;
|
use zed_predict_tos::ZedPredictTos;
|
||||||
|
|
||||||
pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
|
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;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(workspace) =
|
let Some(Some(workspace)) = window
|
||||||
window.downcast::<Workspace>().and_then(|w| w.root(cx).ok())
|
.update(cx, |_, window, _| window.root().flatten())
|
||||||
|
.ok()
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue