diff --git a/crates/assistant/src/assistant.rs b/crates/assistant/src/assistant.rs index 1873c2179b..df75ab6314 100644 --- a/crates/assistant/src/assistant.rs +++ b/crates/assistant/src/assistant.rs @@ -12,7 +12,7 @@ use assistant_settings::{AnthropicModel, AssistantSettings, OpenAiModel, ZedDotD use client::{proto, Client}; use command_palette_hooks::CommandPaletteFilter; pub(crate) use completion_provider::*; -use gpui::{actions, AppContext, BorrowAppContext, Global, SharedString}; +use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal}; pub(crate) use saved_conversation::*; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; @@ -233,13 +233,13 @@ pub fn init(client: Arc, cx: &mut AppContext) { CommandPaletteFilter::update_global(cx, |filter, _cx| { filter.hide_namespace(Assistant::NAMESPACE); }); - cx.update_global(|assistant: &mut Assistant, cx: &mut AppContext| { + Assistant::update_global(cx, |assistant, cx| { let settings = AssistantSettings::get_global(cx); assistant.set_enabled(settings.enabled, cx); }); cx.observe_global::(|cx| { - cx.update_global(|assistant: &mut Assistant, cx: &mut AppContext| { + Assistant::update_global(cx, |assistant, cx| { let settings = AssistantSettings::get_global(cx); assistant.set_enabled(settings.enabled, cx); diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 215e6d8f23..eb463580b8 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -28,8 +28,8 @@ use gpui::{ AsyncWindowContext, AvailableSpace, ClipboardItem, Context, Entity, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, HighlightStyle, InteractiveElement, IntoElement, Model, ModelContext, ParentElement, Pixels, Render, SharedString, StatefulInteractiveElement, Styled, - Subscription, Task, TextStyle, UniformListScrollHandle, View, ViewContext, VisualContext, - WeakModel, WeakView, WhiteSpace, WindowContext, + Subscription, Task, TextStyle, UniformListScrollHandle, UpdateGlobal, View, ViewContext, + VisualContext, WeakModel, WeakView, WhiteSpace, WindowContext, }; use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, Point, ToOffset as _}; use multi_buffer::MultiBufferRow; @@ -240,7 +240,7 @@ impl AssistantPanel { || prev_settings_version != CompletionProvider::global(cx).settings_version() { self.authentication_prompt = - Some(cx.update_global::(|provider, cx| { + Some(CompletionProvider::update_global(cx, |provider, cx| { provider.authentication_prompt(cx) })); } @@ -1088,7 +1088,7 @@ impl AssistantPanel { } fn authenticate(&mut self, cx: &mut ViewContext) -> Task> { - cx.update_global::(|provider, cx| provider.authenticate(cx)) + CompletionProvider::update_global(cx, |provider, cx| provider.authenticate(cx)) } fn render_signed_in(&mut self, cx: &mut ViewContext) -> impl IntoElement { diff --git a/crates/assistant/src/assistant_settings.rs b/crates/assistant/src/assistant_settings.rs index 7341e9ecbe..31e32a2893 100644 --- a/crates/assistant/src/assistant_settings.rs +++ b/crates/assistant/src/assistant_settings.rs @@ -418,7 +418,7 @@ fn merge(target: &mut T, value: Option) { #[cfg(test)] mod tests { - use gpui::{AppContext, BorrowAppContext}; + use gpui::{AppContext, UpdateGlobal}; use settings::SettingsStore; use super::*; @@ -440,7 +440,7 @@ mod tests { ); // Ensure backward-compatibility. - cx.update_global::(|store, cx| { + SettingsStore::update_global(cx, |store, cx| { store .set_user_settings( r#"{ @@ -460,7 +460,7 @@ mod tests { low_speed_timeout_in_seconds: None, } ); - cx.update_global::(|store, cx| { + SettingsStore::update_global(cx, |store, cx| { store .set_user_settings( r#"{ @@ -482,7 +482,7 @@ mod tests { ); // The new version supports setting a custom model when using zed.dev. - cx.update_global::(|store, cx| { + SettingsStore::update_global(cx, |store, cx| { store .set_user_settings( r#"{ diff --git a/crates/assistant2/src/assistant2.rs b/crates/assistant2/src/assistant2.rs index 3ce5a7a67a..223b68cff7 100644 --- a/crates/assistant2/src/assistant2.rs +++ b/crates/assistant2/src/assistant2.rs @@ -24,7 +24,7 @@ use fs::Fs; use futures::{future::join_all, StreamExt}; use gpui::{ list, AnyElement, AppContext, AsyncWindowContext, ClickEvent, EventEmitter, FocusHandle, - FocusableView, ListAlignment, ListState, Model, Render, Task, View, WeakView, + FocusableView, ListAlignment, ListState, Model, ReadGlobal, Render, Task, View, WeakView, }; use language::{language_settings::SoftWrap, LanguageRegistry}; use markdown::{Markdown, MarkdownStyle}; @@ -288,7 +288,7 @@ impl AssistantChat { workspace: WeakView, cx: &mut ViewContext, ) -> Self { - let model = CompletionProvider::get(cx).default_model(); + let model = CompletionProvider::global(cx).default_model(); let view = cx.view().downgrade(); let list_state = ListState::new( 0, @@ -550,7 +550,7 @@ impl AssistantChat { let messages = messages.await?; let completion = cx.update(|cx| { - CompletionProvider::get(cx).complete( + CompletionProvider::global(cx).complete( model_name, messages, Vec::new(), diff --git a/crates/assistant2/src/completion_provider.rs b/crates/assistant2/src/completion_provider.rs index 07f2f75010..deb87de868 100644 --- a/crates/assistant2/src/completion_provider.rs +++ b/crates/assistant2/src/completion_provider.rs @@ -2,7 +2,7 @@ use anyhow::Result; use assistant_tooling::ToolFunctionDefinition; use client::{proto, Client}; use futures::{future::BoxFuture, stream::BoxStream, FutureExt, StreamExt}; -use gpui::{AppContext, Global}; +use gpui::Global; use std::sync::Arc; pub use open_ai::RequestMessage as CompletionMessage; @@ -11,10 +11,6 @@ pub use open_ai::RequestMessage as CompletionMessage; pub struct CompletionProvider(Arc); impl CompletionProvider { - pub fn get(cx: &AppContext) -> &Self { - cx.global::() - } - pub fn new(backend: impl CompletionProviderBackend) -> Self { Self(Arc::new(backend)) } diff --git a/crates/assistant2/src/ui/composer.rs b/crates/assistant2/src/ui/composer.rs index 52c76d23d8..742ef15c18 100644 --- a/crates/assistant2/src/ui/composer.rs +++ b/crates/assistant2/src/ui/composer.rs @@ -3,7 +3,7 @@ use crate::{ AssistantChat, CompletionProvider, }; use editor::{Editor, EditorElement, EditorStyle}; -use gpui::{AnyElement, FontStyle, FontWeight, TextStyle, View, WeakView, WhiteSpace}; +use gpui::{AnyElement, FontStyle, FontWeight, ReadGlobal, TextStyle, View, WeakView, WhiteSpace}; use settings::Settings; use theme::ThemeSettings; use ui::{popover_menu, prelude::*, ButtonLike, ContextMenu, Divider, TextSize, Tooltip}; @@ -139,7 +139,7 @@ impl RenderOnce for ModelSelector { popover_menu("model-switcher") .menu(move |cx| { ContextMenu::build(cx, |mut menu, cx| { - for model in CompletionProvider::get(cx).available_models() { + for model in CompletionProvider::global(cx).available_models() { menu = menu.custom_entry( { let model = model.clone(); diff --git a/crates/gpui/src/global.rs b/crates/gpui/src/global.rs new file mode 100644 index 0000000000..3197967644 --- /dev/null +++ b/crates/gpui/src/global.rs @@ -0,0 +1,62 @@ +use crate::{AppContext, BorrowAppContext}; + +/// A marker trait for types that can be stored in GPUI's global state. +/// +/// This trait exists to provide type-safe access to globals by ensuring only +/// types that implement [`Global`] can be used with the accessor methods. For +/// example, trying to access a global with a type that does not implement +/// [`Global`] will result in a compile-time error. +/// +/// Implement this on types you want to store in the context as a global. +/// +/// ## Restricting Access to Globals +/// +/// In some situations you may need to store some global state, but want to +/// restrict access to reading it or writing to it. +/// +/// In these cases, Rust's visibility system can be used to restrict access to +/// a global value. For example, you can create a private struct that implements +/// [`Global`] and holds the global state. Then create a newtype struct that wraps +/// the global type and create custom accessor methods to expose the desired subset +/// of operations. +pub trait Global: 'static { + // This trait is intentionally left empty, by virtue of being a marker trait. + // + // Use additional traits with blanket implementations to attach functionality + // to types that implement `Global`. +} + +/// A trait for reading a global value from the context. +pub trait ReadGlobal { + /// Returns the global instance of the implementing type. + /// + /// Panics if a global for that type has not been assigned. + fn global(cx: &AppContext) -> &Self; +} + +impl ReadGlobal for T { + fn global(cx: &AppContext) -> &Self { + cx.global::() + } +} + +/// A trait for updating a global value in the context. +pub trait UpdateGlobal { + /// Updates the global instance of the implementing type using the provided closure. + /// + /// This method provides the closure with mutable access to the context and the global simultaneously. + fn update_global(cx: &mut C, update: F) -> R + where + C: BorrowAppContext, + F: FnOnce(&mut Self, &mut C) -> R; +} + +impl UpdateGlobal for T { + fn update_global(cx: &mut C, update: F) -> R + where + C: BorrowAppContext, + F: FnOnce(&mut Self, &mut C) -> R, + { + cx.update_global(update) + } +} diff --git a/crates/gpui/src/gpui.rs b/crates/gpui/src/gpui.rs index 315ad9257e..ac97382473 100644 --- a/crates/gpui/src/gpui.rs +++ b/crates/gpui/src/gpui.rs @@ -77,6 +77,7 @@ mod element; mod elements; mod executor; mod geometry; +mod global; mod input; mod interactive; mod key_dispatch; @@ -125,6 +126,7 @@ pub use element::*; pub use elements::*; pub use executor::*; pub use geometry::*; +pub use global::*; pub use gpui_macros::{register_action, test, IntoElement, Render}; pub use input::*; pub use interactive::*; @@ -327,15 +329,3 @@ impl Flatten for Result { self } } - -/// A marker trait for types that can be stored in GPUI's global state. -/// -/// This trait exists to provide type-safe access to globals by restricting -/// the scope from which they can be accessed. For instance, the actual type -/// that implements [`Global`] can be private, with public accessor functions -/// that enforce correct usage. -/// -/// Implement this on types you want to store in the context as a global. -pub trait Global: 'static { - // This trait is intentionally left empty, by virtue of being a marker trait. -}