Eliminate GPUI View, ViewContext, and WindowContext types (#22632)

There's still a bit more work to do on this, but this PR is compiling
(with warnings) after eliminating the key types. When the tasks below
are complete, this will be the new narrative for GPUI:

- `Entity<T>` - This replaces `View<T>`/`Model<T>`. It represents a unit
of state, and if `T` implements `Render`, then `Entity<T>` implements
`Element`.
- `&mut App` This replaces `AppContext` and represents the app.
- `&mut Context<T>` This replaces `ModelContext` and derefs to `App`. It
is provided by the framework when updating an entity.
- `&mut Window` Broken out of `&mut WindowContext` which no longer
exists. Every method that once took `&mut WindowContext` now takes `&mut
Window, &mut App` and every method that took `&mut ViewContext<T>` now
takes `&mut Window, &mut Context<T>`

Not pictured here are the two other failed attempts. It's been quite a
month!

Tasks:

- [x] Remove `View`, `ViewContext`, `WindowContext` and thread through
`Window`
- [x] [@cole-miller @mikayla-maki] Redraw window when entities change
- [x] [@cole-miller @mikayla-maki] Get examples and Zed running
- [x] [@cole-miller @mikayla-maki] Fix Zed rendering
- [x] [@mikayla-maki] Fix todo! macros and comments
- [x] Fix a bug where the editor would not be redrawn because of view
caching
- [x] remove publicness window.notify() and replace with
`AppContext::notify`
- [x] remove `observe_new_window_models`, replace with
`observe_new_models` with an optional window
- [x] Fix a bug where the project panel would not be redrawn because of
the wrong refresh() call being used
- [x] Fix the tests
- [x] Fix warnings by eliminating `Window` params or using `_`
- [x] Fix conflicts
- [x] Simplify generic code where possible
- [x] Rename types
- [ ] Update docs

### issues post merge

- [x] Issues switching between normal and insert mode
- [x] Assistant re-rendering failure
- [x] Vim test failures
- [x] Mac build issue



Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Cole Miller <cole@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.dev>
Co-authored-by: Joseph <joseph@zed.dev>
Co-authored-by: max <max@zed.dev>
Co-authored-by: Michael Sloan <michael@zed.dev>
Co-authored-by: Mikayla Maki <mikaylamaki@Mikaylas-MacBook-Pro.local>
Co-authored-by: Mikayla <mikayla.c.maki@gmail.com>
Co-authored-by: joão <joao@zed.dev>
This commit is contained in:
Nathan Sobo 2025-01-25 20:02:45 -07:00 committed by GitHub
parent 21b4a0d50e
commit 6fca1d2b0b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
648 changed files with 36248 additions and 28208 deletions

View file

@ -26,8 +26,7 @@ use git::{
GitHostingProviderRegistry, COOKIES, DOT_GIT, FSMONITOR_DAEMON, GITIGNORE,
};
use gpui::{
AppContext, AsyncAppContext, BackgroundExecutor, Context, EventEmitter, Model, ModelContext,
Task,
App, AppContext as _, AsyncAppContext, BackgroundExecutor, Context, Entity, EventEmitter, Task,
};
use ignore::IgnoreStack;
use language::DiskState;
@ -546,7 +545,7 @@ impl Worktree {
fs: Arc<dyn Fs>,
next_entry_id: Arc<AtomicUsize>,
cx: &mut AsyncAppContext,
) -> Result<Model<Self>> {
) -> Result<Entity<Self>> {
let abs_path = path.into();
let metadata = fs
.metadata(&abs_path)
@ -562,7 +561,7 @@ impl Worktree {
let root_file_handle = fs.open_handle(&abs_path).await.log_err();
cx.new_model(move |cx: &mut ModelContext<Worktree>| {
cx.new(move |cx: &mut Context<Worktree>| {
let mut snapshot = LocalSnapshot {
ignores_by_parent_abs_path: Default::default(),
git_repositories: Default::default(),
@ -636,9 +635,9 @@ impl Worktree {
replica_id: ReplicaId,
worktree: proto::WorktreeMetadata,
client: AnyProtoClient,
cx: &mut AppContext,
) -> Model<Self> {
cx.new_model(|cx: &mut ModelContext<Self>| {
cx: &mut App,
) -> Entity<Self> {
cx.new(|cx: &mut Context<Self>| {
let snapshot = Snapshot::new(
worktree.id,
worktree.root_name,
@ -765,7 +764,7 @@ impl Worktree {
!self.is_local()
}
pub fn settings_location(&self, _: &ModelContext<Self>) -> SettingsLocation<'static> {
pub fn settings_location(&self, _: &Context<Self>) -> SettingsLocation<'static> {
SettingsLocation {
worktree_id: self.id(),
path: Path::new(EMPTY_PATH),
@ -823,17 +822,13 @@ impl Worktree {
}
}
pub fn root_file(&self, cx: &ModelContext<Self>) -> Option<Arc<File>> {
pub fn root_file(&self, cx: &Context<Self>) -> Option<Arc<File>> {
let entry = self.root_entry()?;
Some(File::for_entry(entry.clone(), cx.handle()))
Some(File::for_entry(entry.clone(), cx.model()))
}
pub fn observe_updates<F, Fut>(
&mut self,
project_id: u64,
cx: &ModelContext<Worktree>,
callback: F,
) where
pub fn observe_updates<F, Fut>(&mut self, project_id: u64, cx: &Context<Worktree>, callback: F)
where
F: 'static + Send + Fn(proto::UpdateWorktree) -> Fut,
Fut: 'static + Send + Future<Output = bool>,
{
@ -862,7 +857,7 @@ impl Worktree {
}
}
pub fn load_file(&self, path: &Path, cx: &ModelContext<Worktree>) -> Task<Result<LoadedFile>> {
pub fn load_file(&self, path: &Path, cx: &Context<Worktree>) -> Task<Result<LoadedFile>> {
match self {
Worktree::Local(this) => this.load_file(path, cx),
Worktree::Remote(_) => {
@ -871,7 +866,7 @@ impl Worktree {
}
}
pub fn load_staged_file(&self, path: &Path, cx: &AppContext) -> Task<Result<Option<String>>> {
pub fn load_staged_file(&self, path: &Path, cx: &App) -> Task<Result<Option<String>>> {
match self {
Worktree::Local(this) => {
let path = Arc::from(path);
@ -898,7 +893,7 @@ impl Worktree {
pub fn load_binary_file(
&self,
path: &Path,
cx: &ModelContext<Worktree>,
cx: &Context<Worktree>,
) -> Task<Result<LoadedBinaryFile>> {
match self {
Worktree::Local(this) => this.load_binary_file(path, cx),
@ -913,7 +908,7 @@ impl Worktree {
path: &Path,
text: Rope,
line_ending: LineEnding,
cx: &ModelContext<Worktree>,
cx: &Context<Worktree>,
) -> Task<Result<Arc<File>>> {
match self {
Worktree::Local(this) => this.write_file(path, text, line_ending, cx),
@ -927,7 +922,7 @@ impl Worktree {
&mut self,
path: impl Into<Arc<Path>>,
is_directory: bool,
cx: &ModelContext<Worktree>,
cx: &Context<Worktree>,
) -> Task<Result<CreatedEntry>> {
let path = path.into();
let worktree_id = self.id();
@ -972,7 +967,7 @@ impl Worktree {
&mut self,
entry_id: ProjectEntryId,
trash: bool,
cx: &mut ModelContext<Worktree>,
cx: &mut Context<Worktree>,
) -> Option<Task<Result<()>>> {
let task = match self {
Worktree::Local(this) => this.delete_entry(entry_id, trash, cx),
@ -1007,7 +1002,7 @@ impl Worktree {
&mut self,
entry_id: ProjectEntryId,
new_path: impl Into<Arc<Path>>,
cx: &ModelContext<Self>,
cx: &Context<Self>,
) -> Task<Result<CreatedEntry>> {
let new_path = new_path.into();
match self {
@ -1021,7 +1016,7 @@ impl Worktree {
entry_id: ProjectEntryId,
relative_worktree_source_path: Option<PathBuf>,
new_path: impl Into<Arc<Path>>,
cx: &ModelContext<Self>,
cx: &Context<Self>,
) -> Task<Result<Option<Entry>>> {
let new_path = new_path.into();
match self {
@ -1064,7 +1059,7 @@ impl Worktree {
target_directory: PathBuf,
paths: Vec<Arc<Path>>,
overwrite_existing_files: bool,
cx: &ModelContext<Worktree>,
cx: &Context<Worktree>,
) -> Task<Result<Vec<ProjectEntryId>>> {
match self {
Worktree::Local(this) => {
@ -1079,7 +1074,7 @@ impl Worktree {
pub fn expand_entry(
&mut self,
entry_id: ProjectEntryId,
cx: &ModelContext<Worktree>,
cx: &Context<Worktree>,
) -> Option<Task<Result<()>>> {
match self {
Worktree::Local(this) => this.expand_entry(entry_id, cx),
@ -1103,7 +1098,7 @@ impl Worktree {
}
pub async fn handle_create_entry(
this: Model<Self>,
this: Entity<Self>,
request: proto::CreateProjectEntry,
mut cx: AsyncAppContext,
) -> Result<proto::ProjectEntryResponse> {
@ -1123,7 +1118,7 @@ impl Worktree {
}
pub async fn handle_delete_entry(
this: Model<Self>,
this: Entity<Self>,
request: proto::DeleteProjectEntry,
mut cx: AsyncAppContext,
) -> Result<proto::ProjectEntryResponse> {
@ -1145,7 +1140,7 @@ impl Worktree {
}
pub async fn handle_expand_entry(
this: Model<Self>,
this: Entity<Self>,
request: proto::ExpandProjectEntry,
mut cx: AsyncAppContext,
) -> Result<proto::ExpandProjectEntryResponse> {
@ -1160,7 +1155,7 @@ impl Worktree {
}
pub async fn handle_rename_entry(
this: Model<Self>,
this: Entity<Self>,
request: proto::RenameProjectEntry,
mut cx: AsyncAppContext,
) -> Result<proto::ProjectEntryResponse> {
@ -1184,7 +1179,7 @@ impl Worktree {
}
pub async fn handle_copy_entry(
this: Model<Self>,
this: Entity<Self>,
request: proto::CopyProjectEntry,
mut cx: AsyncAppContext,
) -> Result<proto::ProjectEntryResponse> {
@ -1222,7 +1217,7 @@ impl LocalWorktree {
!self.share_private_files && self.settings.is_path_private(path)
}
fn restart_background_scanners(&mut self, cx: &ModelContext<Worktree>) {
fn restart_background_scanners(&mut self, cx: &Context<Worktree>) {
let (scan_requests_tx, scan_requests_rx) = channel::unbounded();
let (path_prefixes_to_scan_tx, path_prefixes_to_scan_rx) = channel::unbounded();
self.scan_requests_tx = scan_requests_tx;
@ -1244,7 +1239,7 @@ impl LocalWorktree {
&mut self,
scan_requests_rx: channel::Receiver<ScanRequest>,
path_prefixes_to_scan_rx: channel::Receiver<Arc<Path>>,
cx: &ModelContext<Worktree>,
cx: &Context<Worktree>,
) {
let snapshot = self.snapshot();
let share_private_files = self.share_private_files;
@ -1345,7 +1340,7 @@ impl LocalWorktree {
&mut self,
new_snapshot: LocalSnapshot,
entry_changes: UpdatedEntriesSet,
cx: &mut ModelContext<Worktree>,
cx: &mut Context<Worktree>,
) {
let repo_changes = self.changed_repos(&self.snapshot, &new_snapshot);
self.snapshot = new_snapshot;
@ -1501,7 +1496,7 @@ impl LocalWorktree {
fn load_binary_file(
&self,
path: &Path,
cx: &ModelContext<Worktree>,
cx: &Context<Worktree>,
) -> Task<Result<LoadedBinaryFile>> {
let path = Arc::from(path);
let abs_path = self.absolutize(&path);
@ -1546,7 +1541,7 @@ impl LocalWorktree {
})
}
fn load_file(&self, path: &Path, cx: &ModelContext<Worktree>) -> Task<Result<LoadedFile>> {
fn load_file(&self, path: &Path, cx: &Context<Worktree>) -> Task<Result<LoadedFile>> {
let path = Arc::from(path);
let abs_path = self.absolutize(&path);
let fs = self.fs.clone();
@ -1606,7 +1601,7 @@ impl LocalWorktree {
&self,
path: impl Into<Arc<Path>>,
is_dir: bool,
cx: &ModelContext<Worktree>,
cx: &Context<Worktree>,
) -> Task<Result<CreatedEntry>> {
let path = path.into();
let abs_path = match self.absolutize(&path) {
@ -1671,7 +1666,7 @@ impl LocalWorktree {
path: impl Into<Arc<Path>>,
text: Rope,
line_ending: LineEnding,
cx: &ModelContext<Worktree>,
cx: &Context<Worktree>,
) -> Task<Result<Arc<File>>> {
let path = path.into();
let fs = self.fs.clone();
@ -1726,7 +1721,7 @@ impl LocalWorktree {
&self,
entry_id: ProjectEntryId,
trash: bool,
cx: &ModelContext<Worktree>,
cx: &Context<Worktree>,
) -> Option<Task<Result<()>>> {
let entry = self.entry_for_id(entry_id)?.clone();
let abs_path = self.absolutize(&entry.path);
@ -1778,7 +1773,7 @@ impl LocalWorktree {
&self,
entry_id: ProjectEntryId,
new_path: impl Into<Arc<Path>>,
cx: &ModelContext<Worktree>,
cx: &Context<Worktree>,
) -> Task<Result<CreatedEntry>> {
let old_path = match self.entry_for_id(entry_id) {
Some(entry) => entry.path.clone(),
@ -1836,7 +1831,7 @@ impl LocalWorktree {
entry_id: ProjectEntryId,
relative_worktree_source_path: Option<PathBuf>,
new_path: impl Into<Arc<Path>>,
cx: &ModelContext<Worktree>,
cx: &Context<Worktree>,
) -> Task<Result<Option<Entry>>> {
let old_path = match self.entry_for_id(entry_id) {
Some(entry) => entry.path.clone(),
@ -1877,7 +1872,7 @@ impl LocalWorktree {
target_directory: PathBuf,
paths: Vec<Arc<Path>>,
overwrite_existing_files: bool,
cx: &ModelContext<Worktree>,
cx: &Context<Worktree>,
) -> Task<Result<Vec<ProjectEntryId>>> {
let worktree_path = self.abs_path().clone();
let fs = self.fs.clone();
@ -1956,7 +1951,7 @@ impl LocalWorktree {
fn expand_entry(
&self,
entry_id: ProjectEntryId,
cx: &ModelContext<Worktree>,
cx: &Context<Worktree>,
) -> Option<Task<Result<()>>> {
let path = self.entry_for_id(entry_id)?.path.clone();
let mut refresh = self.refresh_entries_for_paths(vec![path]);
@ -1985,7 +1980,7 @@ impl LocalWorktree {
&self,
path: Arc<Path>,
old_path: Option<Arc<Path>>,
cx: &ModelContext<Worktree>,
cx: &Context<Worktree>,
) -> Task<Result<Option<Entry>>> {
if self.settings.is_path_excluded(&path) {
return Task::ready(Ok(None));
@ -2009,7 +2004,7 @@ impl LocalWorktree {
})
}
fn observe_updates<F, Fut>(&mut self, project_id: u64, cx: &ModelContext<Worktree>, callback: F)
fn observe_updates<F, Fut>(&mut self, project_id: u64, cx: &Context<Worktree>, callback: F)
where
F: 'static + Send + Fn(proto::UpdateWorktree) -> Fut,
Fut: Send + Future<Output = bool>,
@ -2064,7 +2059,7 @@ impl LocalWorktree {
});
}
pub fn share_private_files(&mut self, cx: &ModelContext<Worktree>) {
pub fn share_private_files(&mut self, cx: &Context<Worktree>) {
self.share_private_files = true;
self.restart_background_scanners(cx);
}
@ -2093,7 +2088,7 @@ impl RemoteWorktree {
}
}
fn observe_updates<F, Fut>(&mut self, project_id: u64, cx: &ModelContext<Worktree>, callback: F)
fn observe_updates<F, Fut>(&mut self, project_id: u64, cx: &Context<Worktree>, callback: F)
where
F: 'static + Send + Fn(proto::UpdateWorktree) -> Fut,
Fut: 'static + Send + Future<Output = bool>,
@ -2159,7 +2154,7 @@ impl RemoteWorktree {
&mut self,
entry: proto::Entry,
scan_id: usize,
cx: &ModelContext<Worktree>,
cx: &Context<Worktree>,
) -> Task<Result<Entry>> {
let wait_for_snapshot = self.wait_for_snapshot(scan_id);
cx.spawn(|this, mut cx| async move {
@ -2178,7 +2173,7 @@ impl RemoteWorktree {
&self,
entry_id: ProjectEntryId,
trash: bool,
cx: &ModelContext<Worktree>,
cx: &Context<Worktree>,
) -> Option<Task<Result<()>>> {
let response = self.client.request(proto::DeleteProjectEntry {
project_id: self.project_id,
@ -2207,7 +2202,7 @@ impl RemoteWorktree {
&self,
entry_id: ProjectEntryId,
new_path: impl Into<Arc<Path>>,
cx: &ModelContext<Worktree>,
cx: &Context<Worktree>,
) -> Task<Result<CreatedEntry>> {
let new_path = new_path.into();
let response = self.client.request(proto::RenameProjectEntry {
@ -3402,7 +3397,7 @@ impl fmt::Debug for Snapshot {
#[derive(Clone, PartialEq)]
pub struct File {
pub worktree: Model<Worktree>,
pub worktree: Entity<Worktree>,
pub path: Arc<Path>,
pub disk_state: DiskState,
pub entry_id: Option<ProjectEntryId>,
@ -3427,7 +3422,7 @@ impl language::File for File {
&self.path
}
fn full_path(&self, cx: &AppContext) -> PathBuf {
fn full_path(&self, cx: &App) -> PathBuf {
let mut full_path = PathBuf::new();
let worktree = self.worktree.read(cx);
@ -3453,13 +3448,13 @@ impl language::File for File {
/// Returns the last component of this handle's absolute path. If this handle refers to the root
/// of its worktree, then this method will return the name of the worktree itself.
fn file_name<'a>(&'a self, cx: &'a AppContext) -> &'a OsStr {
fn file_name<'a>(&'a self, cx: &'a App) -> &'a OsStr {
self.path
.file_name()
.unwrap_or_else(|| OsStr::new(&self.worktree.read(cx).root_name))
}
fn worktree_id(&self, cx: &AppContext) -> WorktreeId {
fn worktree_id(&self, cx: &App) -> WorktreeId {
self.worktree.read(cx).id()
}
@ -3467,7 +3462,7 @@ impl language::File for File {
self
}
fn to_proto(&self, cx: &AppContext) -> rpc::proto::File {
fn to_proto(&self, cx: &App) -> rpc::proto::File {
rpc::proto::File {
worktree_id: self.worktree.read(cx).id().to_proto(),
entry_id: self.entry_id.map(|id| id.to_proto()),
@ -3483,7 +3478,7 @@ impl language::File for File {
}
impl language::LocalFile for File {
fn abs_path(&self, cx: &AppContext) -> PathBuf {
fn abs_path(&self, cx: &App) -> PathBuf {
let worktree_path = &self.worktree.read(cx).as_local().unwrap().abs_path;
if self.path.as_ref() == Path::new("") {
worktree_path.as_path().to_path_buf()
@ -3492,7 +3487,7 @@ impl language::LocalFile for File {
}
}
fn load(&self, cx: &AppContext) -> Task<Result<String>> {
fn load(&self, cx: &App) -> Task<Result<String>> {
let worktree = self.worktree.read(cx).as_local().unwrap();
let abs_path = worktree.absolutize(&self.path);
let fs = worktree.fs.clone();
@ -3500,7 +3495,7 @@ impl language::LocalFile for File {
.spawn(async move { fs.load(&abs_path?).await })
}
fn load_bytes(&self, cx: &AppContext) -> Task<Result<Vec<u8>>> {
fn load_bytes(&self, cx: &App) -> Task<Result<Vec<u8>>> {
let worktree = self.worktree.read(cx).as_local().unwrap();
let abs_path = worktree.absolutize(&self.path);
let fs = worktree.fs.clone();
@ -3510,7 +3505,7 @@ impl language::LocalFile for File {
}
impl File {
pub fn for_entry(entry: Entry, worktree: Model<Worktree>) -> Arc<Self> {
pub fn for_entry(entry: Entry, worktree: Entity<Worktree>) -> Arc<Self> {
Arc::new(Self {
worktree,
path: entry.path.clone(),
@ -3527,8 +3522,8 @@ impl File {
pub fn from_proto(
proto: rpc::proto::File,
worktree: Model<Worktree>,
cx: &AppContext,
worktree: Entity<Worktree>,
cx: &App,
) -> Result<Self> {
let worktree_id = worktree
.read(cx)
@ -3564,11 +3559,11 @@ impl File {
file.and_then(|f| f.as_any().downcast_ref())
}
pub fn worktree_id(&self, cx: &AppContext) -> WorktreeId {
pub fn worktree_id(&self, cx: &App) -> WorktreeId {
self.worktree.read(cx).id()
}
pub fn project_entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
pub fn project_entry_id(&self, _: &App) -> Option<ProjectEntryId> {
match self.disk_state {
DiskState::Deleted => None,
_ => self.entry_id,
@ -5467,7 +5462,7 @@ pub trait WorktreeModelHandle {
) -> futures::future::LocalBoxFuture<'a, ()>;
}
impl WorktreeModelHandle for Model<Worktree> {
impl WorktreeModelHandle for Entity<Worktree> {
// When the worktree's FS event stream sometimes delivers "redundant" events for FS changes that
// occurred before the worktree was constructed. These events can cause the worktree to perform
// extra directory scans, and emit extra scan-state notifications.

View file

@ -1,7 +1,7 @@
use std::path::Path;
use anyhow::Context;
use gpui::AppContext;
use anyhow::Context as _;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
@ -69,10 +69,7 @@ impl Settings for WorktreeSettings {
type FileContent = WorktreeSettingsContent;
fn load(
sources: SettingsSources<Self::FileContent>,
_: &mut AppContext,
) -> anyhow::Result<Self> {
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> anyhow::Result<Self> {
let result: WorktreeSettingsContent = sources.json_merge()?;
let mut file_scan_exclusions = result.file_scan_exclusions.unwrap_or_default();
let mut private_files = result.private_files.unwrap_or_default();

View file

@ -11,7 +11,7 @@ use git::{
},
GITIGNORE,
};
use gpui::{BorrowAppContext, ModelContext, Task, TestAppContext};
use gpui::{BorrowAppContext, Context, Task, TestAppContext};
use parking_lot::Mutex;
use postage::stream::Stream;
use pretty_assertions::assert_eq;
@ -1882,9 +1882,9 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
// The worktree's `UpdatedEntries` event can be used to follow along with
// all changes to the worktree's snapshot.
fn check_worktree_change_events(tree: &mut Worktree, cx: &mut ModelContext<Worktree>) {
fn check_worktree_change_events(tree: &mut Worktree, cx: &mut Context<Worktree>) {
let mut entries = tree.entries(true, 0).cloned().collect::<Vec<_>>();
cx.subscribe(&cx.handle(), move |tree, _, event, _| {
cx.subscribe(&cx.model(), move |tree, _, event, _| {
if let Event::UpdatedEntries(changes) = event {
for (path, _, change_type) in changes.iter() {
let entry = tree.entry_for_path(path).cloned();
@ -1921,7 +1921,7 @@ fn check_worktree_change_events(tree: &mut Worktree, cx: &mut ModelContext<Workt
fn randomly_mutate_worktree(
worktree: &mut Worktree,
rng: &mut impl Rng,
cx: &mut ModelContext<Worktree>,
cx: &mut Context<Worktree>,
) -> Task<Result<()>> {
log::info!("mutating worktree");
let worktree = worktree.as_local_mut().unwrap();