Compare commits

...

2 Commits

Author SHA1 Message Date
Nathan Sobo
a55e496a23 WIP 2024-06-02 13:55:06 -06:00
Nathan Sobo
f89996ac21 Sketch a new approach to views
In the sketch, the View is a model plus a render function, but the
model is not required to declare up-front how it is rendered. In
this world, we would only use a
2024-05-31 18:28:42 -06:00
73 changed files with 1063 additions and 111 deletions

View File

@@ -36,7 +36,7 @@ use futures::future::Shared;
use futures::{FutureExt, StreamExt};
use gpui::{
canvas, div, point, relative, rems, uniform_list, Action, AnyElement, AnyView, AppContext,
AsyncAppContext, AsyncWindowContext, AvailableSpace, ClipboardItem, Context, Empty,
AsyncAppContext, AsyncWindowContext, AvailableSpace, ClipboardItem, StaticContext, Empty,
EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, HighlightStyle,
InteractiveElement, IntoElement, Model, ModelContext, ParentElement, Pixels, Render,
SharedString, StatefulInteractiveElement, Styled, Subscription, Task, TextStyle,

View File

@@ -391,7 +391,7 @@ mod tests {
use super::*;
use futures::stream::{self};
use gpui::{Context, TestAppContext};
use gpui::{StaticContext, TestAppContext};
use indoc::indoc;
use language::{
language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, LanguageMatcher,

View File

@@ -0,0 +1,810 @@
use anyhow::{anyhow, Result};
use chrono::{DateTime, Utc};
use collections::HashMap;
use editor::Editor;
use futures::{
future::{self, BoxFuture, Shared},
FutureExt,
};
use fuzzy::StringMatchCandidate;
use gpui::{
actions, point, size, AppContext, BackgroundExecutor, Bounds, DevicePixels, Empty,
EventEmitter, Global, PromptLevel, ReadGlobal, Subscription, Task, TitlebarOptions, View,
WindowBounds, WindowHandle, WindowOptions,
};
use heed::{types::SerdeBincode, Database, RoTxn};
use language::{language_settings::SoftWrap, Buffer, LanguageRegistry};
use parking_lot::Mutex;
use picker::{Picker, PickerDelegate};
use serde::{Deserialize, Serialize};
use std::{
cmp::Reverse,
future::Future,
path::PathBuf,
sync::{atomic::AtomicBool, Arc},
};
use ui::{
div, prelude::*, IconButtonShape, ListItem, ListItemSpacing, ParentElement, Render,
SharedString, Styled, TitleBar, Tooltip, ViewContext, VisualContext,
};
use util::{paths::PROMPTS_DIR, ResultExt};
use uuid::Uuid;
actions!(
prompt_library,
[NewPrompt, SavePrompt, DeletePrompt, ToggleDefaultPrompt]
);
/// Init starts loading the PromptStore in the background and assigns
/// a shared future to a global.
pub fn init(cx: &mut AppContext) {
let db_path = PROMPTS_DIR.join("prompts-library-db.0.mdb");
let prompt_store_future = PromptStore::new(db_path, cx.background_executor().clone())
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))
.boxed()
.shared();
cx.set_global(GlobalPromptStore(prompt_store_future))
}
/// This function opens a new prompt library window if one doesn't exist already.
/// If one exists, it brings it to the foreground.
///
/// Note that, when opening a new window, this waits for the PromptStore to be
/// initialized. If it was initialized successfully, it returns a window handle
/// to a prompt library.
pub fn open_prompt_library(
language_registry: Arc<LanguageRegistry>,
cx: &mut AppContext,
) -> 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, |_, cx| cx.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 bounds = Bounds::centered(
None,
size(DevicePixels::from(1024), DevicePixels::from(768)),
cx,
);
cx.open_window(
WindowOptions {
titlebar: Some(TitlebarOptions {
title: None,
appears_transparent: true,
traffic_light_position: Some(point(px(9.0), px(9.0))),
}),
window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default()
},
|cx| cx.new_view(|cx| PromptLibrary::new(store, language_registry, cx)),
)
})
})
}
}
pub struct PromptLibrary {
store: Arc<PromptStore>,
language_registry: Arc<LanguageRegistry>,
prompt_editors: HashMap<PromptId, View<Editor>>,
active_prompt_id: Option<PromptId>,
picker: View<Picker<PromptPickerDelegate>>,
pending_load: Task<()>,
_subscriptions: Vec<Subscription>,
}
struct PromptPickerDelegate {
store: Arc<PromptStore>,
selected_index: usize,
matches: Vec<PromptMetadata>,
}
enum PromptPickerEvent {
Confirmed { prompt_id: PromptId },
Deleted { prompt_id: PromptId },
ToggledDefault { prompt_id: PromptId },
}
impl EventEmitter<PromptPickerEvent> for Picker<PromptPickerDelegate> {}
impl PickerDelegate for PromptPickerDelegate {
type ListItem = ListItem;
fn match_count(&self) -> usize {
self.matches.len()
}
fn selected_index(&self) -> usize {
self.selected_index
}
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix;
}
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Search...".into()
}
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
let search = self.store.search(query);
cx.spawn(|this, mut cx| async move {
let matches = search.await;
this.update(&mut cx, |this, cx| {
this.delegate.selected_index = 0;
this.delegate.matches = matches;
cx.notify();
})
.ok();
})
}
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
if let Some(prompt) = self.matches.get(self.selected_index) {
cx.emit(PromptPickerEvent::Confirmed {
prompt_id: prompt.id,
});
}
}
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
fn render_match(
&self,
ix: usize,
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let prompt = self.matches.get(ix)?;
let default = prompt.default;
let prompt_id = prompt.id;
Some(
ListItem::new(ix)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.selected(selected)
.child(Label::new(
prompt.title.clone().unwrap_or("Untitled".into()),
))
.end_slot(if default {
IconButton::new("toggle-default-prompt", IconName::StarFilled)
.shape(IconButtonShape::Square)
.into_any_element()
} else {
Empty.into_any()
})
.end_hover_slot(
h_flex()
.gap_2()
.child(
IconButton::new("delete-prompt", IconName::Trash)
.shape(IconButtonShape::Square)
.tooltip(move |cx| Tooltip::text("Delete Prompt", cx))
.on_click(cx.listener(move |_, _, cx| {
cx.emit(PromptPickerEvent::Deleted { prompt_id })
})),
)
.child(
IconButton::new(
"toggle-default-prompt",
if default {
IconName::StarFilled
} else {
IconName::Star
},
)
.shape(IconButtonShape::Square)
.tooltip(move |cx| {
Tooltip::text(
if default {
"Remove from Default Prompt"
} else {
"Add to Default Prompt"
},
cx,
)
})
.on_click(cx.listener(move |_, _, cx| {
cx.emit(PromptPickerEvent::ToggledDefault { prompt_id })
})),
),
),
)
}
}
impl PromptLibrary {
fn new(
store: Arc<PromptStore>,
language_registry: Arc<LanguageRegistry>,
cx: &mut ViewContext<Self>,
) -> Self {
let delegate = PromptPickerDelegate {
store: store.clone(),
selected_index: 0,
matches: Vec::new(),
};
let picker = cx.new_view(|cx| {
let picker = Picker::uniform_list(delegate, cx)
.modal(false)
.max_height(None);
picker.focus(cx);
picker
});
let mut this = Self {
store: store.clone(),
language_registry,
prompt_editors: HashMap::default(),
active_prompt_id: None,
pending_load: Task::ready(()),
_subscriptions: vec![cx.subscribe(&picker, Self::handle_picker_event)],
picker,
};
if let Some(prompt_id) = store.most_recently_saved() {
this.load_prompt(prompt_id, false, cx);
}
this
}
fn handle_picker_event(
&mut self,
_: View<Picker<PromptPickerDelegate>>,
event: &PromptPickerEvent,
cx: &mut ViewContext<Self>,
) {
match event {
PromptPickerEvent::Confirmed { prompt_id } => {
self.load_prompt(*prompt_id, true, cx);
}
PromptPickerEvent::ToggledDefault { prompt_id } => {
self.toggle_default_for_prompt(*prompt_id, cx);
}
PromptPickerEvent::Deleted { prompt_id } => {
self.delete_prompt(*prompt_id, cx);
}
}
}
pub fn new_prompt(&mut self, cx: &mut ViewContext<Self>) {
let prompt_id = PromptId::new();
let save = self.store.save(prompt_id, None, false, "".into());
self.picker.update(cx, |picker, cx| picker.refresh(cx));
cx.spawn(|this, mut cx| async move {
save.await?;
this.update(&mut cx, |this, cx| this.load_prompt(prompt_id, true, cx))
})
.detach_and_log_err(cx);
}
pub fn save_active_prompt(&mut self, cx: &mut ViewContext<Self>) {
if let Some(active_prompt_id) = self.active_prompt_id {
let prompt_metadata = self.store.metadata(active_prompt_id).unwrap();
let body = self
.prompt_editors
.get_mut(&active_prompt_id)
.unwrap()
.update(cx, |editor, cx| editor.snapshot(cx));
let title = title_from_body(body.buffer_chars_at(0).map(|(c, _)| c));
self.store
.save(
active_prompt_id,
title,
prompt_metadata.default,
body.text(),
)
.detach_and_log_err(cx);
self.picker.update(cx, |picker, cx| picker.refresh(cx));
cx.notify();
}
}
pub fn delete_active_prompt(&mut self, cx: &mut ViewContext<Self>) {
if let Some(active_prompt_id) = self.active_prompt_id {
self.delete_prompt(active_prompt_id, cx);
}
}
pub fn toggle_default_for_active_prompt(&mut self, cx: &mut ViewContext<Self>) {
if let Some(active_prompt_id) = self.active_prompt_id {
self.toggle_default_for_prompt(active_prompt_id, cx);
}
}
pub fn toggle_default_for_prompt(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
if let Some(prompt_metadata) = self.store.metadata(prompt_id) {
self.store
.save_metadata(prompt_id, prompt_metadata.title, !prompt_metadata.default)
.detach_and_log_err(cx);
self.picker.update(cx, |picker, cx| picker.refresh(cx));
cx.notify();
}
}
pub fn load_prompt(&mut self, prompt_id: PromptId, focus: bool, cx: &mut ViewContext<Self>) {
if let Some(prompt_editor) = self.prompt_editors.get(&prompt_id) {
if focus {
prompt_editor.update(cx, |editor, cx| editor.focus(cx));
}
self.active_prompt_id = Some(prompt_id);
} else {
let language_registry = self.language_registry.clone();
let prompt = self.store.load(prompt_id);
self.pending_load = cx.spawn(|this, mut cx| async move {
let prompt = prompt.await;
let markdown = language_registry.language_for_name("Markdown").await;
this.update(&mut cx, |this, cx| match prompt {
Ok(prompt) => {
let buffer = cx.new_model(|cx| {
let mut buffer = Buffer::local(prompt, cx);
buffer.set_language(markdown.log_err(), cx);
buffer.set_language_registry(language_registry);
buffer
});
let editor = cx.new_view(|cx| {
let mut editor = Editor::for_buffer(buffer, None, cx);
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor.set_show_gutter(false, cx);
editor.set_show_wrap_guides(false, cx);
editor.set_show_indent_guides(false, cx);
if focus {
editor.focus(cx);
}
editor
});
this.prompt_editors.insert(prompt_id, editor);
this.active_prompt_id = Some(prompt_id);
cx.notify();
}
Err(error) => {
// TODO: we should show the error in the UI.
log::error!("error while loading prompt: {:?}", error);
}
})
.ok();
});
}
}
pub fn delete_prompt(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
if let Some(metadata) = self.store.metadata(prompt_id) {
let confirmation = cx.prompt(
PromptLevel::Warning,
&format!(
"Are you sure you want to delete {}",
metadata.title.unwrap_or("Untitled".into())
),
None,
&["Delete", "Cancel"],
);
cx.spawn(|this, mut cx| async move {
if confirmation.await.ok() == Some(0) {
this.update(&mut cx, |this, cx| {
if this.active_prompt_id == Some(prompt_id) {
this.active_prompt_id = None;
}
this.prompt_editors.remove(&prompt_id);
this.store.delete(prompt_id).detach_and_log_err(cx);
this.picker.update(cx, |picker, cx| picker.refresh(cx));
cx.notify();
})?;
}
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
}
fn render_prompt_list(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex()
.id("prompt-list")
.bg(cx.theme().colors().surface_background)
.h_full()
.w_1_3()
.overflow_x_hidden()
.child(
h_flex()
.bg(cx.theme().colors().background)
.p(Spacing::Small.rems(cx))
.border_b_1()
.border_color(cx.theme().colors().border)
.h(TitleBar::height(cx))
.w_full()
.flex_none()
.justify_end()
.child(
IconButton::new("new-prompt", IconName::Plus)
.shape(IconButtonShape::Square)
.tooltip(move |cx| Tooltip::for_action("New Prompt", &NewPrompt, cx))
.on_click(|_, cx| {
cx.dispatch_action(Box::new(NewPrompt));
}),
),
)
.child(div().flex_grow().child(self.picker.clone()))
}
fn render_active_prompt(&mut self, cx: &mut ViewContext<PromptLibrary>) -> gpui::Stateful<Div> {
div()
.w_2_3()
.h_full()
.id("prompt-editor")
.border_l_1()
.border_color(cx.theme().colors().border)
.bg(cx.theme().colors().editor_background)
.flex_none()
.min_w_64()
.children(self.active_prompt_id.and_then(|prompt_id| {
let prompt_metadata = self.store.metadata(prompt_id)?;
let editor = self.prompt_editors[&prompt_id].clone();
Some(
v_flex()
.size_full()
.child(
h_flex()
.h(TitleBar::height(cx))
.px(Spacing::Large.rems(cx))
.justify_between()
.child(
Label::new(prompt_metadata.title.unwrap_or("Untitled".into()))
.size(LabelSize::Large),
)
.child(
h_flex()
.gap_4()
.child(
IconButton::new("save-prompt", IconName::Save)
.shape(IconButtonShape::Square)
.tooltip(move |cx| {
Tooltip::for_action(
"Save Prompt",
&SavePrompt,
cx,
)
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(SavePrompt));
}),
)
.child(
IconButton::new(
"toggle-default-prompt",
if prompt_metadata.default {
IconName::StarFilled
} else {
IconName::Star
},
)
.shape(IconButtonShape::Square)
.tooltip(move |cx| {
Tooltip::for_action(
if prompt_metadata.default {
"Remove from Default Prompt"
} else {
"Add to Default Prompt"
},
&ToggleDefaultPrompt,
cx,
)
})
.on_click(
|_, cx| {
cx.dispatch_action(Box::new(
ToggleDefaultPrompt,
));
},
),
)
.child(
IconButton::new("delete-prompt", IconName::Trash)
.shape(IconButtonShape::Square)
.tooltip(move |cx| {
Tooltip::for_action(
"Delete Prompt",
&DeletePrompt,
cx,
)
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(DeletePrompt));
}),
),
),
)
.child(div().flex_grow().p(Spacing::Large.rems(cx)).child(editor)),
)
}))
}
}
impl Render for PromptLibrary {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
h_flex()
.id("prompt-manager")
.key_context("PromptLibrary")
.on_action(cx.listener(|this, &NewPrompt, cx| this.new_prompt(cx)))
.on_action(cx.listener(|this, &SavePrompt, cx| this.save_active_prompt(cx)))
.on_action(cx.listener(|this, &DeletePrompt, cx| this.delete_active_prompt(cx)))
.on_action(cx.listener(|this, &ToggleDefaultPrompt, cx| {
this.toggle_default_for_active_prompt(cx)
}))
.size_full()
.overflow_hidden()
.child(self.render_prompt_list(cx))
.child(self.render_active_prompt(cx))
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct PromptMetadata {
pub id: PromptId,
pub title: Option<SharedString>,
pub default: bool,
pub saved_at: DateTime<Utc>,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PromptId(Uuid);
impl PromptId {
pub fn new() -> PromptId {
PromptId(Uuid::new_v4())
}
}
pub struct PromptStore {
executor: BackgroundExecutor,
env: heed::Env,
bodies: Database<SerdeBincode<PromptId>, SerdeBincode<String>>,
metadata: Database<SerdeBincode<PromptId>, SerdeBincode<PromptMetadata>>,
metadata_cache: Mutex<MetadataCache>,
}
#[derive(Default)]
struct MetadataCache {
metadata: Vec<PromptMetadata>,
metadata_by_id: HashMap<PromptId, PromptMetadata>,
}
impl MetadataCache {
fn from_db(
db: Database<SerdeBincode<PromptId>, SerdeBincode<PromptMetadata>>,
txn: &RoTxn,
) -> Result<Self> {
let mut cache = MetadataCache::default();
for result in db.iter(txn)? {
let (prompt_id, metadata) = result?;
cache.metadata.push(metadata.clone());
cache.metadata_by_id.insert(prompt_id, metadata);
}
cache
.metadata
.sort_unstable_by_key(|metadata| Reverse(metadata.saved_at));
Ok(cache)
}
fn insert(&mut self, metadata: PromptMetadata) {
self.metadata_by_id.insert(metadata.id, metadata.clone());
if let Some(old_metadata) = self.metadata.iter_mut().find(|m| m.id == metadata.id) {
*old_metadata = metadata;
} else {
self.metadata.push(metadata);
}
self.metadata.sort_by_key(|m| Reverse(m.saved_at));
}
fn remove(&mut self, id: PromptId) {
self.metadata.retain(|metadata| metadata.id != id);
self.metadata_by_id.remove(&id);
}
}
impl PromptStore {
pub fn global(cx: &AppContext) -> impl Future<Output = Result<Arc<Self>>> {
let store = GlobalPromptStore::global(cx).0.clone();
async move { store.await.map_err(|err| anyhow!(err)) }
}
pub fn new(db_path: PathBuf, executor: BackgroundExecutor) -> Task<Result<Self>> {
executor.spawn({
let executor = executor.clone();
async move {
std::fs::create_dir_all(&db_path)?;
let db_env = unsafe {
heed::EnvOpenOptions::new()
.map_size(1024 * 1024 * 1024) // 1GB
.max_dbs(2) // bodies and metadata
.open(db_path)?
};
let mut txn = db_env.write_txn()?;
let bodies = db_env.create_database(&mut txn, Some("bodies"))?;
let metadata = db_env.create_database(&mut txn, Some("metadata"))?;
let metadata_cache = MetadataCache::from_db(metadata, &txn)?;
txn.commit()?;
Ok(PromptStore {
executor,
env: db_env,
bodies,
metadata,
metadata_cache: Mutex::new(metadata_cache),
})
}
})
}
pub fn load(&self, id: PromptId) -> Task<Result<String>> {
let env = self.env.clone();
let bodies = self.bodies;
self.executor.spawn(async move {
let txn = env.read_txn()?;
Ok(bodies
.get(&txn, &id)?
.ok_or_else(|| anyhow!("prompt not found"))?)
})
}
pub fn delete(&self, id: PromptId) -> Task<Result<()>> {
self.metadata_cache.lock().remove(id);
let db_connection = self.env.clone();
let bodies = self.bodies;
let metadata = self.metadata;
self.executor.spawn(async move {
let mut txn = db_connection.write_txn()?;
metadata.delete(&mut txn, &id)?;
bodies.delete(&mut txn, &id)?;
txn.commit()?;
Ok(())
})
}
fn metadata(&self, id: PromptId) -> Option<PromptMetadata> {
self.metadata_cache.lock().metadata_by_id.get(&id).cloned()
}
pub fn id_for_title(&self, title: &str) -> Option<PromptId> {
let metadata_cache = self.metadata_cache.lock();
let metadata = metadata_cache
.metadata
.iter()
.find(|metadata| metadata.title.as_ref().map(|title| &***title) == Some(title))?;
Some(metadata.id)
}
pub fn search(&self, query: String) -> Task<Vec<PromptMetadata>> {
let cached_metadata = self.metadata_cache.lock().metadata.clone();
let executor = self.executor.clone();
self.executor.spawn(async move {
if query.is_empty() {
cached_metadata
} else {
let candidates = cached_metadata
.iter()
.enumerate()
.filter_map(|(ix, metadata)| {
Some(StringMatchCandidate::new(
ix,
metadata.title.as_ref()?.to_string(),
))
})
.collect::<Vec<_>>();
let matches = fuzzy::match_strings(
&candidates,
&query,
false,
100,
&AtomicBool::default(),
executor,
)
.await;
matches
.into_iter()
.map(|mat| cached_metadata[mat.candidate_id].clone())
.collect()
}
})
}
fn save(
&self,
id: PromptId,
title: Option<SharedString>,
default: bool,
body: String,
) -> Task<Result<()>> {
let prompt_metadata = PromptMetadata {
id,
title,
default,
saved_at: Utc::now(),
};
self.metadata_cache.lock().insert(prompt_metadata.clone());
let db_connection = self.env.clone();
let bodies = self.bodies;
let metadata = self.metadata;
self.executor.spawn(async move {
let mut txn = db_connection.write_txn()?;
metadata.put(&mut txn, &id, &prompt_metadata)?;
bodies.put(&mut txn, &id, &body)?;
txn.commit()?;
Ok(())
})
}
fn save_metadata(
&self,
id: PromptId,
title: Option<SharedString>,
default: bool,
) -> Task<Result<()>> {
let prompt_metadata = PromptMetadata {
id,
title,
default,
saved_at: Utc::now(),
};
self.metadata_cache.lock().insert(prompt_metadata.clone());
let db_connection = self.env.clone();
let metadata = self.metadata;
self.executor.spawn(async move {
let mut txn = db_connection.write_txn()?;
metadata.put(&mut txn, &id, &prompt_metadata)?;
txn.commit()?;
Ok(())
})
}
fn most_recently_saved(&self) -> Option<PromptId> {
self.metadata_cache
.lock()
.metadata
.first()
.map(|metadata| metadata.id)
}
}
/// Wraps a shared future to a prompt store so it can be assigned as a context global.
pub struct GlobalPromptStore(
Shared<BoxFuture<'static, Result<Arc<PromptStore>, Arc<anyhow::Error>>>>,
);
impl Global for GlobalPromptStore {}
fn title_from_body<'a>(body: impl IntoIterator<Item = char>) -> Option<SharedString> {
let mut chars = body.into_iter().take_while(|c| *c != '\n').peekable();
let mut level = 0;
while let Some('#') = chars.peek() {
level += 1;
chars.next();
}
if level > 0 {
Some(chars.collect::<String>().trim().to_string().into())
} else {
None
}
}

View File

@@ -73,7 +73,7 @@ fn line_similarity(line1: &str, line2: &str) -> f64 {
#[cfg(test)]
mod test {
use super::*;
use gpui::{AppContext, Context as _};
use gpui::{AppContext, StaticContext as _};
use language::Buffer;
use unindent::Unindent as _;
use util::test::marked_text_ranges;

View File

@@ -6,8 +6,8 @@ use db::kvp::KEY_VALUE_STORE;
use db::RELEASE_CHANNEL;
use editor::{Editor, MultiBuffer};
use gpui::{
actions, AppContext, AsyncAppContext, Context as _, Global, Model, ModelContext,
SemanticVersion, SharedString, Task, View, ViewContext, VisualContext, WindowContext,
actions, AppContext, AsyncAppContext, Global, Model, ModelContext, SemanticVersion,
SharedString, StaticContext as _, Task, View, ViewContext, VisualContext, WindowContext,
};
use isahc::AsyncBody;

View File

@@ -9,8 +9,8 @@ use client::{proto, ChannelId, Client, TypedEnvelope, User, UserStore, ZED_ALWAY
use collections::HashSet;
use futures::{channel::oneshot, future::Shared, Future, FutureExt};
use gpui::{
AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Subscription,
Task, WeakModel,
AppContext, AsyncAppContext, EventEmitter, Global, Model, ModelContext, StaticContext,
Subscription, Task, WeakModel,
};
use postage::watch;
use project::Project;

View File

@@ -12,7 +12,7 @@ use collections::{BTreeMap, HashMap, HashSet};
use fs::Fs;
use futures::{FutureExt, StreamExt};
use gpui::{
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel,
AppContext, AsyncAppContext, StaticContext, EventEmitter, Model, ModelContext, Task, WeakModel,
};
use language::LanguageRegistry;
use live_kit_client::{LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RoomUpdate};

View File

@@ -2,7 +2,7 @@ use crate::{Channel, ChannelStore};
use anyhow::Result;
use client::{ChannelId, Client, Collaborator, UserStore, ZED_ALWAYS_ACTIVE};
use collections::HashMap;
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
use gpui::{AppContext, AsyncAppContext, EventEmitter, Model, ModelContext, StaticContext, Task};
use language::proto::serialize_version;
use rpc::{
proto::{self, PeerId},

View File

@@ -8,7 +8,7 @@ use client::{
use collections::HashSet;
use futures::lock::Mutex;
use gpui::{
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel,
AppContext, AsyncAppContext, EventEmitter, Model, ModelContext, StaticContext, Task, WeakModel,
};
use rand::prelude::*;
use std::{

View File

@@ -7,8 +7,8 @@ use client::{ChannelId, Client, ClientSettings, ProjectId, Subscription, User, U
use collections::{hash_map, HashMap, HashSet};
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
use gpui::{
AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, SharedString,
Task, WeakModel,
AppContext, AsyncAppContext, EventEmitter, Global, Model, ModelContext, SharedString,
StaticContext, Task, WeakModel,
};
use language::Capability;
use rpc::{

View File

@@ -3,7 +3,7 @@ use crate::channel_chat::ChannelChatEvent;
use super::*;
use client::{test::FakeServer, Client, UserStore};
use clock::FakeSystemClock;
use gpui::{AppContext, Context, Model, TestAppContext};
use gpui::{AppContext, Model, StaticContext, TestAppContext};
use http::FakeHttpClient;
use rpc::proto::{self};
use settings::SettingsStore;

View File

@@ -1701,7 +1701,7 @@ mod tests {
use crate::test::FakeServer;
use clock::FakeSystemClock;
use gpui::{BackgroundExecutor, Context, TestAppContext};
use gpui::{BackgroundExecutor, StaticContext, TestAppContext};
use http::FakeHttpClient;
use parking_lot::Mutex;
use settings::SettingsStore;

View File

@@ -1,7 +1,7 @@
use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore};
use anyhow::{anyhow, Result};
use futures::{stream::BoxStream, StreamExt};
use gpui::{BackgroundExecutor, Context, Model, TestAppContext};
use gpui::{BackgroundExecutor, StaticContext, Model, TestAppContext};
use parking_lot::Mutex;
use rpc::{
proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse},

View File

@@ -7,7 +7,7 @@ use collab_ui::{
};
use editor::{Editor, ExcerptRange, MultiBuffer};
use gpui::{
point, BackgroundExecutor, BorrowAppContext, Context, Entity, SharedString, TestAppContext,
point, BackgroundExecutor, BorrowAppContext, StaticContext, Entity, SharedString, TestAppContext,
View, VisualContext, VisualTestContext,
};
use language::Capability;

View File

@@ -18,7 +18,7 @@ use collections::{HashMap, HashSet};
use fs::FakeFs;
use futures::{channel::oneshot, StreamExt as _};
use git::GitHostingProviderRegistry;
use gpui::{BackgroundExecutor, Context, Model, Task, TestAppContext, View, VisualTestContext};
use gpui::{BackgroundExecutor, StaticContext, Model, Task, TestAppContext, View, VisualTestContext};
use http::FakeHttpClient;
use language::LanguageRegistry;
use node_runtime::FakeNodeRuntime;

View File

@@ -9,7 +9,7 @@ use collections::{HashMap, HashSet};
use command_palette_hooks::CommandPaletteFilter;
use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
use gpui::{
actions, AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Global, Model,
actions, AppContext, AsyncAppContext, StaticContext, Entity, EntityId, EventEmitter, Global, Model,
ModelContext, Task, WeakModel,
};
use http::github::latest_github_release;

View File

@@ -292,7 +292,7 @@ mod tests {
};
use fs::FakeFs;
use futures::StreamExt;
use gpui::{BackgroundExecutor, Context, TestAppContext, UpdateGlobal};
use gpui::{BackgroundExecutor, StaticContext, TestAppContext, UpdateGlobal};
use indoc::indoc;
use language::{
language_settings::{AllLanguageSettings, AllLanguageSettingsContent},

View File

@@ -1,5 +1,5 @@
use anyhow::Result;
use gpui::{AppContext, AsyncAppContext, Context, Global, Model, ModelContext, SharedString, Task};
use gpui::{AppContext, AsyncAppContext, StaticContext, Global, Model, ModelContext, SharedString, Task};
use rpc::{
proto::{self, DevServerStatus},
TypedEnvelope,

View File

@@ -19,9 +19,9 @@ use futures::{
StreamExt as _,
};
use gpui::{
actions, div, svg, AnyElement, AnyView, AppContext, Context, EventEmitter, FocusHandle,
FocusableView, HighlightStyle, InteractiveElement, IntoElement, Model, ParentElement, Render,
SharedString, Styled, StyledText, Subscription, Task, View, ViewContext, VisualContext,
actions, div, svg, AnyElement, AnyView, AppContext, EventEmitter, FocusHandle, FocusableView,
HighlightStyle, InteractiveElement, IntoElement, Model, ParentElement, Render, SharedString,
StaticContext, Styled, StyledText, Subscription, Task, View, ViewContext, VisualContext,
WeakView, WindowContext,
};
use language::{

View File

@@ -1054,7 +1054,7 @@ pub mod tests {
movement,
test::{editor_test_context::EditorTestContext, marked_display_snapshot},
};
use gpui::{div, font, observe, px, AppContext, BorrowAppContext, Context, Element, Hsla};
use gpui::{div, font, observe, px, AppContext, BorrowAppContext, StaticContext, Element, Hsla};
use language::{
language_settings::{AllLanguageSettings, AllLanguageSettingsContent},
Buffer, Language, LanguageConfig, LanguageMatcher, SelectionGoal,

View File

@@ -3,7 +3,7 @@ use super::{
tab_map::{self, TabEdit, TabPoint, TabSnapshot},
Highlights,
};
use gpui::{AppContext, Context, Font, LineWrapper, Model, ModelContext, Pixels, Task};
use gpui::{AppContext, Font, LineWrapper, Model, ModelContext, Pixels, StaticContext, Task};
use language::{Chunk, Point};
use lazy_static::lazy_static;
use multi_buffer::MultiBufferSnapshot;

View File

@@ -39,6 +39,7 @@ pub mod tasks;
#[cfg(test)]
mod editor_tests;
pub mod sketch;
#[cfg(any(test, feature = "test-support"))]
pub mod test;
use ::git::diff::{DiffHunk, DiffHunkStatus};
@@ -67,11 +68,11 @@ use git::diff_hunk_to_display;
use gpui::{
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem,
Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontId, FontStyle,
DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontId, FontStyle,
FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, Model, MouseButton, PaintQuad,
ParentElement, Pixels, Render, SharedString, Size, StrikethroughStyle, Styled, StyledText,
Subscription, Task, TextStyle, UnderlineStyle, UniformListScrollHandle, View, ViewContext,
ViewInputHandler, VisualContext, WeakView, WhiteSpace, WindowContext,
ParentElement, Pixels, Render, SharedString, Size, StaticContext, StrikethroughStyle, Styled,
StyledText, Subscription, Task, TextStyle, UnderlineStyle, UniformListScrollHandle, View,
ViewContext, ViewInputHandler, VisualContext, WeakView, WhiteSpace, WindowContext,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};

View File

@@ -5569,7 +5569,7 @@ mod tests {
use language::language_settings;
use log::info;
use std::num::NonZeroU32;
use ui::Context;
use ui::StaticContext;
use util::test::sample_text;
#[gpui::test]

View File

@@ -107,7 +107,7 @@ pub fn diff_hunk_to_display(
mod tests {
use crate::Point;
use crate::{editor_tests::init_test, hunk_status};
use gpui::{Context, TestAppContext};
use gpui::{StaticContext, TestAppContext};
use language::Capability::ReadWrite;
use multi_buffer::{ExcerptRange, MultiBuffer, MultiBufferRow};
use project::{FakeFs, Project};

View File

@@ -519,7 +519,7 @@ async fn parse_markdown(text: &str, language_registry: &Arc<LanguageRegistry>) -
#[cfg(test)]
mod tests {
use super::*;
use gpui::Context;
use gpui::StaticContext;
use language::{Point, Rope};
use project::FakeFs;
use rand::prelude::*;

View File

@@ -13,7 +13,8 @@ use multi_buffer::{
use settings::{Settings, SettingsStore};
use text::{BufferId, Point};
use ui::{
div, ActiveTheme, Context as _, IntoElement, ParentElement, Styled, ViewContext, VisualContext,
div, ActiveTheme, IntoElement, ParentElement, StaticContext as _, Styled, ViewContext,
VisualContext,
};
use util::{debug_panic, RangeExt};

View File

@@ -1268,7 +1268,7 @@ pub mod tests {
ExcerptRange,
};
use futures::StreamExt;
use gpui::{Context, TestAppContext, WindowHandle};
use gpui::{StaticContext, TestAppContext, WindowHandle};
use itertools::Itertools;
use language::{
language_settings::AllLanguageSettingsContent, Capability, FakeLspAdapter, Language,

View File

@@ -8,7 +8,7 @@ use collections::HashSet;
use futures::future::try_join_all;
use git::repository::GitFileStatus;
use gpui::{
point, AnyElement, AppContext, AsyncWindowContext, Context, Entity, EntityId, EventEmitter,
point, AnyElement, AppContext, AsyncWindowContext, StaticContext, Entity, EntityId, EventEmitter,
IntoElement, Model, ParentElement, Pixels, SharedString, Styled, Task, View, ViewContext,
VisualContext, WeakView, WindowContext,
};

View File

@@ -579,7 +579,7 @@ mod tests {
test::{editor_test_context::EditorTestContext, marked_display_snapshot},
Buffer, DisplayMap, DisplayRow, ExcerptRange, FoldPlaceholder, InlayId, MultiBuffer,
};
use gpui::{font, Context as _};
use gpui::{font, StaticContext as _};
use language::Capability;
use project::Project;
use settings::SettingsStore;

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use anyhow::Context as _;
use gpui::{Context, View, ViewContext, VisualContext, WindowContext};
use gpui::{StaticContext, View, ViewContext, VisualContext, WindowContext};
use language::Language;
use multi_buffer::MultiBuffer;
use project::lsp_ext_command::ExpandMacro;

View File

@@ -0,0 +1,27 @@
use crate::{Editor, EditorElement, EditorStyle};
use gpui::{sketch::View, Model};
use ui::ViewContext;
pub struct PaneItemProps {}
struct TabProps {}
pub trait RenderEditor {
fn tab_view(&self, props: EditorStyle) -> View<Editor, TabProps>;
fn render(&self, props: EditorStyle) -> EditorElement;
}
impl RenderEditor for Model<Editor> {
fn tab_view(&self, style: EditorStyle) -> View<Editor, TabProps> {
View::new(
self.clone(),
move |_, _: TabProps, cx: &mut ViewContext<Editor>| {
cx.view().model.render(style.clone())
},
)
}
fn render(&self, style: EditorStyle) -> EditorElement {
todo!()
// EditorElement::new(self, style)
}
}

View File

@@ -5,7 +5,7 @@ use crate::{
display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
DisplayPoint, Editor, EditorMode, FoldPlaceholder, MultiBuffer,
};
use gpui::{Context, Font, FontFeatures, FontStyle, FontWeight, Model, Pixels, ViewContext};
use gpui::{Font, FontFeatures, FontStyle, FontWeight, Model, Pixels, StaticContext, ViewContext};
use project::Project;
use util::test::{marked_text_offsets, marked_text_ranges};

View File

@@ -23,7 +23,7 @@ use std::{
},
};
use ui::Context;
use ui::StaticContext;
use util::{
assert_set_eq,
test::{generate_marked_text, marked_text_ranges},

View File

@@ -28,7 +28,7 @@ use futures::{
select_biased, AsyncReadExt as _, Future, FutureExt as _, StreamExt as _,
};
use gpui::{
actions, AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Task,
actions, AppContext, AsyncAppContext, StaticContext, EventEmitter, Global, Model, ModelContext, Task,
WeakModel,
};
use http::{AsyncBody, HttpClient, HttpClientWithUrl};

View File

@@ -10,7 +10,7 @@ use async_compression::futures::bufread::GzipEncoder;
use collections::BTreeMap;
use fs::{FakeFs, Fs, RealFs};
use futures::{io::BufReader, AsyncReadExt, StreamExt};
use gpui::{Context, TestAppContext};
use gpui::{StaticContext, TestAppContext};
use http::{FakeHttpClient, Response};
use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
use node_runtime::FakeNodeRuntime;

View File

@@ -27,7 +27,7 @@ use util::ResultExt;
use crate::{
current_platform, init_app_menus, Action, ActionRegistry, Any, AnyView, AnyWindowHandle,
AppMetadata, AssetCache, AssetSource, BackgroundExecutor, ClipboardItem, Context,
AppMetadata, AssetCache, AssetSource, BackgroundExecutor, ClipboardItem, StaticContext,
DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap,
Keystroke, LayoutId, Menu, MenuItem, PathPromptOptions, Pixels, Platform, PlatformDisplay,
Point, PromptBuilder, PromptHandle, PromptLevel, Render, RenderablePromptHandle, Reservation,
@@ -1263,7 +1263,7 @@ impl AppContext {
}
}
impl Context for AppContext {
impl StaticContext for AppContext {
type Result<T> = T;
/// Build an entity that is owned by the application. The given function will be invoked with

View File

@@ -1,8 +1,8 @@
use crate::{
AnyView, AnyWindowHandle, AppCell, AppContext, BackgroundExecutor, BorrowAppContext, Context,
AnyView, AnyWindowHandle, AppCell, AppContext, BackgroundExecutor, BorrowAppContext,
DismissEvent, FocusableView, ForegroundExecutor, Global, Model, ModelContext, PromptLevel,
Render, Reservation, Result, Task, View, ViewContext, VisualContext, WindowContext,
WindowHandle,
Render, Reservation, Result, StaticContext, Task, View, ViewContext, VisualContext,
WindowContext, WindowHandle,
};
use anyhow::{anyhow, Context as _};
use derive_more::{Deref, DerefMut};
@@ -19,7 +19,7 @@ pub struct AsyncAppContext {
pub(crate) foreground_executor: ForegroundExecutor,
}
impl Context for AsyncAppContext {
impl StaticContext for AsyncAppContext {
type Result<T> = Result<T>;
fn new_model<T: 'static>(
@@ -301,7 +301,7 @@ impl AsyncWindowContext {
}
}
impl Context for AsyncWindowContext {
impl StaticContext for AsyncWindowContext {
type Result<T> = Result<T>;
fn new_model<T>(

View File

@@ -1,4 +1,4 @@
use crate::{seal::Sealed, AppContext, Context, Entity, ModelContext};
use crate::{seal::Sealed, AppContext, StaticContext, Entity, ModelContext};
use anyhow::{anyhow, Result};
use derive_more::{Deref, DerefMut};
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
@@ -398,7 +398,7 @@ impl<T: 'static> Model<T> {
}
/// Read the entity referenced by this model with the given function.
pub fn read_with<R, C: Context>(
pub fn read_with<R, C: StaticContext>(
&self,
cx: &C,
f: impl FnOnce(&T, &AppContext) -> R,
@@ -417,7 +417,7 @@ impl<T: 'static> Model<T> {
update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
) -> C::Result<R>
where
C: Context,
C: StaticContext,
{
cx.update_model(self, update)
}
@@ -595,7 +595,7 @@ impl<T: 'static> WeakModel<T> {
update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
) -> Result<R>
where
C: Context,
C: StaticContext,
Result<C::Result<R>>: crate::Flatten<R>,
{
crate::Flatten::flatten(
@@ -610,7 +610,7 @@ impl<T: 'static> WeakModel<T> {
/// been released.
pub fn read_with<C, R>(&self, cx: &C, read: impl FnOnce(&T, &AppContext) -> R) -> Result<R>
where
C: Context,
C: StaticContext,
Result<C::Result<R>>: crate::Flatten<R>,
{
crate::Flatten::flatten(

View File

@@ -1,6 +1,6 @@
use crate::{
AnyView, AnyWindowHandle, AppContext, AsyncAppContext, Context, Effect, Entity, EntityId,
EventEmitter, Model, Reservation, Subscription, Task, View, WeakModel, WindowContext,
AnyView, AnyWindowHandle, AppContext, AsyncAppContext, Effect, Entity, EntityId, EventEmitter,
Model, Reservation, StaticContext, Subscription, Task, View, WeakModel, WindowContext,
WindowHandle,
};
use anyhow::Result;
@@ -220,7 +220,7 @@ impl<'a, T> ModelContext<'a, T> {
}
}
impl<'a, T> Context for ModelContext<'a, T> {
impl<'a, T> StaticContext for ModelContext<'a, T> {
type Result<U> = U;
fn new_model<U: 'static>(

View File

@@ -1,6 +1,6 @@
use crate::{
Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, AvailableSpace,
BackgroundExecutor, BorrowAppContext, Bounds, ClipboardItem, Context, DrawPhase, Drawable,
BackgroundExecutor, BorrowAppContext, Bounds, ClipboardItem, StaticContext, DrawPhase, Drawable,
Element, Empty, Entity, EventEmitter, ForegroundExecutor, Global, InputEvent, Keystroke, Model,
ModelContext, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent,
MouseUpEvent, Pixels, Platform, Point, Render, Result, Size, Task, TestDispatcher,
@@ -29,7 +29,7 @@ pub struct TestAppContext {
on_quit: Rc<RefCell<Vec<Box<dyn FnOnce() + 'static>>>>,
}
impl Context for TestAppContext {
impl StaticContext for TestAppContext {
type Result<T> = T;
fn new_model<T: 'static>(
@@ -844,8 +844,8 @@ impl VisualTestContext {
}
}
impl Context for VisualTestContext {
type Result<T> = <TestAppContext as Context>::Result<T>;
impl StaticContext for VisualTestContext {
type Result<T> = <TestAppContext as StaticContext>::Result<T>;
fn new_model<T: 'static>(
&mut self,

View File

@@ -87,6 +87,8 @@ pub mod prelude;
mod scene;
mod shared_string;
mod shared_uri;
/// todo!
pub mod sketch;
mod style;
mod styled;
mod subscription;
@@ -156,7 +158,7 @@ use taffy::TaffyLayoutEngine;
/// The context trait, allows the different contexts in GPUI to be used
/// interchangeably for certain operations.
pub trait Context {
pub trait StaticContext {
/// The result type for this context, used for async contexts that
/// can't hold a direct reference to the application context.
type Result<T>;
@@ -226,7 +228,7 @@ impl<T: 'static> Reservation<T> {
/// This trait is used for the different visual contexts in GPUI that
/// require a window to be present.
pub trait VisualContext: Context {
pub trait VisualContext: StaticContext {
/// Construct a new view in the window referenced by this context.
fn new_view<V>(
&mut self,

View File

@@ -3,7 +3,7 @@
//! application to avoid having to import each trait individually.
pub use crate::{
util::FluentBuilder, BorrowAppContext, BorrowWindow, Context, Element, FocusableElement,
util::FluentBuilder, BorrowAppContext, BorrowWindow, Element, FocusableElement,
InteractiveElement, IntoElement, ParentElement, Refineable, Render, RenderOnce,
StatefulInteractiveElement, Styled, VisualContext,
StatefulInteractiveElement, StaticContext, Styled, VisualContext,
};

107
crates/gpui/src/sketch.rs Normal file
View File

@@ -0,0 +1,107 @@
use crate::{AnyElement, AnyModel, IntoElement, ViewContext, WindowContext};
use std::{any::Any, marker::PhantomData, sync::Arc};
pub struct Model<M> {
state_type: PhantomData<M>,
}
pub trait Context {
type ModelContext<'a, M>;
fn update<M, F, R>(&mut self, model: &Model<M>, update: F) -> R
where
M: 'static,
F: for<'a> FnOnce(&mut M, &mut Self::ModelContext<'a, M>) -> R;
}
impl Context for WindowContext<'_> {
type ModelContext<'a, M> = ViewContext<'a, M>;
fn update<M, F, R>(&mut self, model: &Model<M>, update: F) -> R
where
M: 'static,
F: for<'a> FnOnce(&mut M, &mut Self::ModelContext<'a, M>) -> R,
{
todo!()
}
}
impl<M> Model<M> {
pub fn update<C, F, R>(&self, cx: &mut C, update: F) -> R
where
C: Context,
F: for<'a> FnOnce(&mut M, &mut C::ModelContext<'a, M>) -> R,
{
todo!()
}
}
/// A view is the combination of a model with a compatible render function for that model.
pub struct View<M, P> {
/// A handle to the state we will render
pub model: Model<M>,
/// A recipe for displaying the state based on properties
pub component: Arc<dyn StatefulComponent<M, P>>,
}
impl<M: 'static, P: 'static> View<M, P> {
/// Creates a new `View` with the specified model and render function.
pub fn new<F, E>(model: Model<M>, render: F) -> Self
where
F: 'static + Fn(&mut M, P, &mut ViewContext<M>) -> E,
E: IntoElement,
{
View {
model,
component: Arc::new(
move |model: &mut M, props: P, cx: &mut ViewContext<'_, M>| {
render(model, props, cx).into_any_element()
},
),
}
}
pub fn render(&self, props: P, cx: &mut WindowContext) -> AnyElement {
self.model
.update(cx, |model, cx| self.component.render(model, props, cx))
}
}
/// A mapping from properties P to an element tree.
pub trait Component<P>: 'static {
/// Render the properties
fn render(&self, props: P, cx: &mut WindowContext) -> AnyElement;
}
/// A mapping from a stateful model M and properties P to an element tree.
pub trait StatefulComponent<M, P>: 'static {
/// Render the model with the given properties
fn render(&self, model: &mut M, props: P, cx: &mut ViewContext<M>) -> AnyElement;
}
impl<P, F> Component<P> for F
where
F: Fn(P, &mut WindowContext) -> AnyElement + 'static,
{
fn render(&self, props: P, cx: &mut WindowContext) -> AnyElement {
(self)(props, cx)
}
}
impl<M, P, F> StatefulComponent<M, P> for F
where
F: for<'a, 'b, 'c> Fn(&'a mut M, P, &'b mut ViewContext<'c, M>) -> AnyElement + 'static,
{
fn render(&self, model: &mut M, props: P, cx: &mut ViewContext<M>) -> AnyElement {
(self)(model, props, cx)
}
}
/// A dynamically typed view. It can be rendered with props P or downcast back to a typed view.
pub struct AnyView<P> {
model: AnyModel,
/// An upcasted render function that takes the dynamic reference.
render: Arc<dyn Fn(&AnyModel, P, &mut WindowContext) -> AnyElement>,
/// The original render function to enable downcasting to a View.
typed_render: Arc<dyn Any>,
}

View File

@@ -1,7 +1,7 @@
use crate::{
hash, point, prelude::*, px, size, transparent_black, Action, AnyDrag, AnyElement, AnyTooltip,
AnyView, AppContext, Arena, Asset, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow,
Context, Corners, CursorStyle, DevicePixels, DispatchActionListener, DispatchNodeId,
StaticContext, Corners, CursorStyle, DevicePixels, DispatchActionListener, DispatchNodeId,
DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten,
FontId, Global, GlobalElementId, GlyphId, Hsla, ImageData, InputHandler, IsZero, KeyBinding,
KeyContext, KeyDownEvent, KeyEvent, KeyMatch, KeymatchResult, Keystroke, KeystrokeEvent,
@@ -3536,7 +3536,7 @@ impl WindowContext<'_> {
}
}
impl Context for WindowContext<'_> {
impl StaticContext for WindowContext<'_> {
type Result<T> = T;
fn new_model<T>(&mut self, build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T) -> Model<T>
@@ -4198,7 +4198,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
}
}
impl<V> Context for ViewContext<'_, V> {
impl<V> StaticContext for ViewContext<'_, V> {
type Result<U> = U;
fn new_model<T: 'static>(
@@ -4348,7 +4348,7 @@ impl<V: 'static + Render> WindowHandle<V> {
/// This will fail if the window is closed or if the root view's type does not match `V`.
pub fn root<C>(&self, cx: &mut C) -> Result<View<V>>
where
C: Context,
C: StaticContext,
{
Flatten::flatten(cx.update_window(self.any_handle, |root_view, _| {
root_view
@@ -4366,7 +4366,7 @@ impl<V: 'static + Render> WindowHandle<V> {
update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
) -> Result<R>
where
C: Context,
C: StaticContext,
{
cx.update_window(self.any_handle, |root_view, cx| {
let view = root_view
@@ -4400,7 +4400,7 @@ impl<V: 'static + Render> WindowHandle<V> {
/// This will fail if the window is closed or if the root view's type does not match `V`.
pub fn read_with<C, R>(&self, cx: &C, read_with: impl FnOnce(&V, &AppContext) -> R) -> Result<R>
where
C: Context,
C: StaticContext,
{
cx.read_window(self, |root_view, cx| read_with(root_view.read(cx), cx))
}
@@ -4410,7 +4410,7 @@ impl<V: 'static + Render> WindowHandle<V> {
/// This will fail if the window is closed or if the root view's type does not match `V`.
pub fn root_view<C>(&self, cx: &C) -> Result<View<V>>
where
C: Context,
C: StaticContext,
{
cx.read_window(self, |root_view, _cx| root_view.clone())
}
@@ -4491,7 +4491,7 @@ impl AnyWindowHandle {
update: impl FnOnce(AnyView, &mut WindowContext<'_>) -> R,
) -> Result<R>
where
C: Context,
C: StaticContext,
{
cx.update_window(self, update)
}
@@ -4501,7 +4501,7 @@ impl AnyWindowHandle {
/// This will fail if the window has been closed.
pub fn read<T, C, R>(self, cx: &C, read: impl FnOnce(View<T>, &AppContext) -> R) -> Result<R>
where
C: Context,
C: StaticContext,
T: 'static,
{
let view = self

View File

@@ -3,7 +3,9 @@ use client::DevServerProjectId;
use client::{user::UserStore, Client, ClientSettings};
use fs::Fs;
use futures::Future;
use gpui::{AppContext, AsyncAppContext, Context, Global, Model, ModelContext, Task, WeakModel};
use gpui::{
AppContext, AsyncAppContext, Global, Model, ModelContext, StaticContext, Task, WeakModel,
};
use language::LanguageRegistry;
use node_runtime::NodeRuntime;
use postage::stream::Stream;

View File

@@ -1,5 +1,5 @@
use gpui::{
canvas, div, fill, img, opaque_grey, point, size, AnyElement, AppContext, Bounds, Context,
canvas, div, fill, img, opaque_grey, point, size, AnyElement, AppContext, Bounds, StaticContext,
EventEmitter, FocusHandle, FocusableView, Img, InteractiveElement, IntoElement, Model,
ObjectFit, ParentElement, Render, Styled, Task, View, ViewContext, VisualContext, WeakView,
WindowContext,

View File

@@ -7,7 +7,7 @@ use clock::ReplicaId;
use collections::BTreeMap;
use futures::FutureExt as _;
use gpui::{AppContext, BorrowAppContext, Model};
use gpui::{Context, TestAppContext};
use gpui::{StaticContext, TestAppContext};
use indoc::indoc;
use proto::deserialize_operation;
use rand::prelude::*;

View File

@@ -3,7 +3,7 @@ use copilot::Copilot;
use editor::{actions::MoveToEnd, Editor, EditorEvent};
use futures::{channel::mpsc, StreamExt};
use gpui::{
actions, div, AnchorCorner, AnyElement, AppContext, Context, EventEmitter, FocusHandle,
actions, div, AnchorCorner, AnyElement, AppContext, StaticContext, EventEmitter, FocusHandle,
FocusableView, IntoElement, Model, ModelContext, ParentElement, Render, Styled, Subscription,
View, ViewContext, VisualContext, WeakModel, WindowContext,
};

View File

@@ -4,7 +4,7 @@ use crate::lsp_log::LogMenuItem;
use super::*;
use futures::StreamExt;
use gpui::{Context, TestAppContext, VisualTestContext};
use gpui::{StaticContext, TestAppContext, VisualTestContext};
use language::{
tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageServerName,
};

View File

@@ -319,7 +319,7 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServ
#[cfg(test)]
mod tests {
use gpui::{BorrowAppContext, Context, TestAppContext};
use gpui::{BorrowAppContext, StaticContext, TestAppContext};
use language::{language_settings::AllLanguageSettings, AutoindentMode, Buffer};
use settings::SettingsStore;
use std::num::NonZeroU32;

View File

@@ -135,7 +135,7 @@ async fn get_cached_server_binary(
#[cfg(test)]
mod tests {
use gpui::{Context, TestAppContext};
use gpui::{StaticContext, TestAppContext};
use unindent::Unindent;
#[gpui::test]

View File

@@ -201,7 +201,7 @@ pub(super) fn python_task_context() -> ContextProviderWithTasks {
#[cfg(test)]
mod tests {
use gpui::{BorrowAppContext, Context, ModelContext, TestAppContext};
use gpui::{BorrowAppContext, ModelContext, StaticContext, TestAppContext};
use language::{language_settings::AllLanguageSettings, AutoindentMode, Buffer};
use settings::SettingsStore;
use std::num::NonZeroU32;

View File

@@ -504,7 +504,7 @@ mod tests {
use super::*;
use crate::language;
use gpui::{BorrowAppContext, Context, Hsla, TestAppContext};
use gpui::{BorrowAppContext, Hsla, StaticContext, TestAppContext};
use language::language_settings::AllLanguageSettings;
use settings::SettingsStore;
use theme::SyntaxTheme;

View File

@@ -435,7 +435,7 @@ async fn get_cached_eslint_server_binary(
#[cfg(test)]
mod tests {
use gpui::{Context, TestAppContext};
use gpui::{StaticContext, TestAppContext};
use unindent::Unindent;
#[gpui::test]

View File

@@ -42,7 +42,7 @@ use theme::SyntaxTheme;
use util::post_inc;
#[cfg(any(test, feature = "test-support"))]
use gpui::Context;
use gpui::StaticContext;
const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize];
@@ -4649,7 +4649,7 @@ where
mod tests {
use super::*;
use futures::StreamExt;
use gpui::{AppContext, Context, TestAppContext};
use gpui::{AppContext, StaticContext, TestAppContext};
use language::{Buffer, Rope};
use parking_lot::RwLock;
use rand::prelude::*;

View File

@@ -4,7 +4,7 @@ use client::{ChannelId, Client, UserStore};
use collections::HashMap;
use db::smol::stream::StreamExt;
use gpui::{
AppContext, AsyncAppContext, Context as _, EventEmitter, Global, Model, ModelContext, Task,
AppContext, AsyncAppContext, StaticContext as _, EventEmitter, Global, Model, ModelContext, Task,
};
use rpc::{proto, Notification, TypedEnvelope};
use std::{ops::Range, sync::Arc};

View File

@@ -3,7 +3,7 @@ use anyhow::Result;
use client::Client;
use collections::{HashMap, HashSet};
use futures::{FutureExt, StreamExt};
use gpui::{AppContext, AsyncAppContext, Context, Global, Model, ModelContext, Task, WeakModel};
use gpui::{AppContext, AsyncAppContext, StaticContext, Global, Model, ModelContext, Task, WeakModel};
use postage::stream::Stream;
use rpc::proto;
use std::{sync::Arc, time::Duration};

View File

@@ -35,8 +35,9 @@ use fuzzy::CharBag;
use git::{blame::Blame, repository::GitRepository};
use globset::{Glob, GlobSet, GlobSetBuilder};
use gpui::{
AnyModel, AppContext, AsyncAppContext, BackgroundExecutor, BorrowAppContext, Context, Entity,
EventEmitter, Model, ModelContext, PromptLevel, SharedString, Task, WeakModel, WindowContext,
AnyModel, AppContext, AsyncAppContext, BackgroundExecutor, BorrowAppContext, Entity,
EventEmitter, Model, ModelContext, PromptLevel, SharedString, StaticContext, Task, WeakModel,
WindowContext,
};
use itertools::Itertools;
use language::{

View File

@@ -13,7 +13,7 @@ use futures::{
channel::mpsc::{unbounded, UnboundedSender},
StreamExt,
};
use gpui::{AppContext, Context, Model, ModelContext, Task};
use gpui::{AppContext, Model, ModelContext, StaticContext, Task};
use itertools::Itertools;
use language::{ContextProvider, Language, Location};
use task::{

View File

@@ -1,7 +1,8 @@
use crate::Project;
use collections::HashMap;
use gpui::{
AnyWindowHandle, AppContext, Context, Entity, Model, ModelContext, SharedString, WeakModel,
AnyWindowHandle, AppContext, Entity, Model, ModelContext, SharedString, StaticContext,
WeakModel,
};
use itertools::Itertools;
use settings::{Settings, SettingsLocation};

View File

@@ -1091,7 +1091,7 @@ mod tests {
use super::*;
use editor::{display_map::DisplayRow, DisplayPoint, Editor};
use gpui::{Context, Hsla, TestAppContext, VisualTestContext};
use gpui::{Hsla, StaticContext, TestAppContext, VisualTestContext};
use language::Buffer;
use project::Project;
use smol::stream::StreamExt as _;

View File

@@ -12,9 +12,9 @@ use editor::{
Anchor, Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer, MAX_TAB_TITLE_LEN,
};
use gpui::{
actions, div, Action, AnyElement, AnyView, AppContext, Context as _, Element, EntityId,
EventEmitter, FocusHandle, FocusableView, FontStyle, Global, Hsla, InteractiveElement,
IntoElement, Model, ModelContext, ParentElement, Point, Render, SharedString, Styled,
actions, div, Action, AnyElement, AnyView, AppContext, Element, EntityId, EventEmitter,
FocusHandle, FocusableView, FontStyle, Global, Hsla, InteractiveElement, IntoElement, Model,
ModelContext, ParentElement, Point, Render, SharedString, StaticContext as _, Styled,
Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, VisualContext, WeakModel,
WeakView, WhiteSpace, WindowContext,
};

View File

@@ -10,8 +10,8 @@ use fs::Fs;
use futures::{future::Shared, stream::StreamExt, FutureExt};
use futures_batch::ChunksTimeoutStreamExt;
use gpui::{
AppContext, AsyncAppContext, BorrowAppContext, Context, Entity, EntityId, EventEmitter, Global,
Model, ModelContext, Subscription, Task, WeakModel,
AppContext, AsyncAppContext, BorrowAppContext, Entity, EntityId, EventEmitter, Global, Model,
ModelContext, StaticContext, Subscription, Task, WeakModel,
};
use heed::types::{SerdeBincode, Str};
use language::LanguageRegistry;

View File

@@ -78,7 +78,7 @@ fn released(entity_id: EntityId, cx: &mut AppContext) {
mod test {
use crate::{test::VimTestContext, Vim};
use editor::Editor;
use gpui::{Context, Entity, VisualTestContext};
use gpui::{Entity, StaticContext, VisualTestContext};
use language::Buffer;
// regression test for blur called with a different active editor

View File

@@ -118,7 +118,7 @@ mod test {
state::Mode,
test::{NeovimBackedTestContext, VimTestContext},
};
use gpui::{point, px, size, Context};
use gpui::{point, px, size, StaticContext};
use indoc::indoc;
use language::Point;

View File

@@ -1,4 +1,4 @@
use gpui::{px, size, Context, UpdateGlobal};
use gpui::{px, size, StaticContext, UpdateGlobal};
use indoc::indoc;
use settings::SettingsStore;
use std::{

View File

@@ -1,7 +1,7 @@
use std::ops::{Deref, DerefMut};
use editor::test::editor_lsp_test_context::EditorLspTestContext;
use gpui::{Context, View, VisualContext};
use gpui::{StaticContext, View, VisualContext};
use search::{project_search::ProjectSearchBar, BufferSearchBar};
use crate::{state::Operator, *};

View File

@@ -869,8 +869,8 @@ pub mod test {
use super::{Item, ItemEvent, TabContentParams};
use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
use gpui::{
AnyElement, AppContext, Context as _, EntityId, EventEmitter, FocusableView,
InteractiveElement, IntoElement, Model, Render, SharedString, Task, View, ViewContext,
AnyElement, AppContext, EntityId, EventEmitter, FocusableView, InteractiveElement,
IntoElement, Model, Render, SharedString, StaticContext as _, Task, View, ViewContext,
VisualContext, WeakView,
};
use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};

View File

@@ -80,7 +80,7 @@ use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
pub use ui;
use ui::{
div, h_flex, Context as _, Div, Element, FluentBuilder, InteractiveElement as _, IntoElement,
div, h_flex, StaticContext as _, Div, Element, FluentBuilder, InteractiveElement as _, IntoElement,
Label, ParentElement as _, Pixels, SharedString, Styled as _, ViewContext, VisualContext as _,
WindowContext,
};
@@ -453,7 +453,7 @@ impl AppState {
pub fn test(cx: &mut AppContext) -> Arc<Self> {
use node_runtime::FakeNodeRuntime;
use settings::SettingsStore;
use ui::Context as _;
use ui::StaticContext as _;
if !cx.has_global::<SettingsStore>() {
let settings_store = SettingsStore::test(cx);
@@ -6210,7 +6210,7 @@ mod tests {
}
mod register_project_item_tests {
use ui::Context as _;
use ui::StaticContext as _;
use super::*;

View File

@@ -27,8 +27,8 @@ use git::{
DOT_GIT, GITIGNORE,
};
use gpui::{
AppContext, AsyncAppContext, BackgroundExecutor, Context, EventEmitter, Model, ModelContext,
Task,
AppContext, AsyncAppContext, BackgroundExecutor, EventEmitter, Model, ModelContext,
StaticContext, Task,
};
use ignore::IgnoreStack;
use itertools::Itertools;

View File

@@ -18,7 +18,7 @@ use fs::RealFs;
use futures::{future, StreamExt};
use git::GitHostingProviderRegistry;
use gpui::{
App, AppContext, AsyncAppContext, Context, Global, Task, UpdateGlobal as _, VisualContext,
App, AppContext, AsyncAppContext, StaticContext, Global, Task, UpdateGlobal as _, VisualContext,
};
use image_viewer;
use language::LanguageRegistry;

View File

@@ -10,7 +10,7 @@ use client::ZED_URL_SCHEME;
use collections::VecDeque;
use editor::{scroll::Autoscroll, Editor, MultiBuffer};
use gpui::{
actions, point, px, AppContext, AsyncAppContext, Context, FocusableView, MenuItem, PromptLevel,
actions, point, px, AppContext, AsyncAppContext, StaticContext, FocusableView, MenuItem, PromptLevel,
ReadGlobal, TitlebarOptions, View, ViewContext, VisualContext, WindowKind, WindowOptions,
};
pub use open_listener::*;

View File

@@ -4,7 +4,7 @@ use client::telemetry::Telemetry;
use collections::HashMap;
use copilot::{Copilot, CopilotCompletionProvider};
use editor::{Editor, EditorMode};
use gpui::{AnyWindowHandle, AppContext, Context, ViewContext, WeakView};
use gpui::{AnyWindowHandle, AppContext, StaticContext, ViewContext, WeakView};
use language::language_settings::all_language_settings;
use settings::SettingsStore;
use supermaven::{Supermaven, SupermavenCompletionProvider};