From f00045544f0f15e79a07e3358bf2c6228afbc86f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 2 Oct 2021 19:01:29 -0600 Subject: [PATCH] Introduce a File trait object to buffer This will remove the dependency of buffer on `worktree::File` --- server/src/rpc.rs | 4 +- zed/src/editor.rs | 19 ++-- zed/src/editor/buffer.rs | 81 ++++++++++---- zed/src/workspace.rs | 23 ++-- zed/src/worktree.rs | 227 ++++++++++++++++++++++----------------- 5 files changed, 216 insertions(+), 138 deletions(-) diff --git a/server/src/rpc.rs b/server/src/rpc.rs index 33e22786d7..502cdbcbc6 100644 --- a/server/src/rpc.rs +++ b/server/src/rpc.rs @@ -1383,7 +1383,7 @@ mod tests { .update(&mut cx_b, |worktree, cx| worktree.open_buffer("a.txt", cx)) .await .unwrap(); - let mtime = buffer_b.read_with(&cx_b, |buf, _| buf.file().unwrap().mtime); + let mtime = buffer_b.read_with(&cx_b, |buf, _| buf.file().unwrap().mtime()); buffer_b.update(&mut cx_b, |buf, cx| buf.edit([0..0], "world ", cx)); buffer_b.read_with(&cx_b, |buf, _| { @@ -1398,7 +1398,7 @@ mod tests { .unwrap(); worktree_b .condition(&cx_b, |_, cx| { - buffer_b.read(cx).file().unwrap().mtime != mtime + buffer_b.read(cx).file().unwrap().mtime() != mtime }) .await; buffer_b.read_with(&cx_b, |buf, _| { diff --git a/zed/src/editor.rs b/zed/src/editor.rs index c240ffd334..7beb26cff7 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -10,7 +10,7 @@ use crate::{ time::ReplicaId, util::{post_inc, Bias}, workspace, - worktree::{File, Worktree}, + worktree::Worktree, }; use anyhow::Result; pub use buffer::*; @@ -2554,10 +2554,6 @@ impl View for Editor { impl workspace::Item for Buffer { type View = Editor; - fn file(&self) -> Option<&File> { - self.file() - } - fn build_view( handle: ModelHandle, settings: watch::Receiver, @@ -2592,6 +2588,10 @@ impl workspace::Item for Buffer { cx, ) } + + fn worktree_id_and_path(&self) -> Option<(usize, Arc)> { + self.file().map(|f| (f.worktree_id(), f.path().clone())) + } } impl workspace::ItemView for Editor { @@ -2623,8 +2623,11 @@ impl workspace::ItemView for Editor { } } - fn entry_id(&self, cx: &AppContext) -> Option<(usize, Arc)> { - self.buffer.read(cx).file().map(|file| file.entry_id()) + fn worktree_id_and_path(&self, cx: &AppContext) -> Option<(usize, Arc)> { + self.buffer + .read(cx) + .file() + .map(|file| (file.worktree_id(), file.path().clone())) } fn clone_on_split(&self, cx: &mut ViewContext) -> Option @@ -2678,7 +2681,7 @@ impl workspace::ItemView for Editor { }); buffer.update(&mut cx, |buffer, cx| { - buffer.did_save(version, new_file.mtime, Some(new_file), cx); + buffer.did_save(version, new_file.mtime, Some(Box::new(new_file)), cx); buffer.set_language(language, cx); }); }) diff --git a/zed/src/editor/buffer.rs b/zed/src/editor/buffer.rs index 7f3fdd3653..457c012d22 100644 --- a/zed/src/editor/buffer.rs +++ b/zed/src/editor/buffer.rs @@ -9,11 +9,10 @@ use crate::{ settings::{HighlightId, HighlightMap}, time::{self, ReplicaId}, util::Bias, - worktree::File, }; pub use anchor::*; use anyhow::{anyhow, Result}; -use gpui::{AppContext, Entity, ModelContext, Task}; +use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task}; use lazy_static::lazy_static; use operation_queue::OperationQueue; use parking_lot::Mutex; @@ -23,13 +22,15 @@ use seahash::SeaHasher; pub use selection::*; use similar::{ChangeTag, TextDiff}; use std::{ + any::Any, cell::RefCell, cmp, convert::{TryFrom, TryInto}, + ffi::OsString, hash::BuildHasher, iter::Iterator, ops::{Deref, DerefMut, Range}, - path::Path, + path::{Path, PathBuf}, str, sync::Arc, time::{Duration, Instant, SystemTime, UNIX_EPOCH}, @@ -38,6 +39,46 @@ use sum_tree::{self, FilterCursor, SumTree}; use tree_sitter::{InputEdit, Parser, QueryCursor}; use zrpc::proto; +pub trait File { + fn worktree_id(&self) -> usize; + + fn entry_id(&self) -> Option; + + fn set_entry_id(&mut self, entry_id: Option); + + fn mtime(&self) -> SystemTime; + + fn set_mtime(&mut self, mtime: SystemTime); + + fn path(&self) -> &Arc; + + fn set_path(&mut self, path: Arc); + + fn full_path(&self, cx: &AppContext) -> PathBuf; + + /// 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) -> Option; + + fn is_deleted(&self) -> bool; + + fn save( + &self, + buffer_id: u64, + text: Rope, + version: time::Global, + cx: &mut MutableAppContext, + ) -> Task>; + + fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext); + + fn buffer_removed(&self, buffer_id: u64, cx: &mut MutableAppContext); + + fn boxed_clone(&self) -> Box; + + fn as_any(&self) -> &dyn Any; +} + #[derive(Clone, Default)] struct DeterministicState; @@ -115,7 +156,7 @@ pub struct Buffer { last_edit: time::Local, undo_map: UndoMap, history: History, - file: Option, + file: Option>, language: Option>, sync_parse_timeout: Duration, syntax_tree: Mutex>, @@ -524,7 +565,7 @@ impl Buffer { pub fn from_history( replica_id: ReplicaId, history: History, - file: Option, + file: Option>, language: Option>, cx: &mut ModelContext, ) -> Self { @@ -541,14 +582,14 @@ impl Buffer { fn build( replica_id: ReplicaId, history: History, - file: Option, + file: Option>, remote_id: u64, language: Option>, cx: &mut ModelContext, ) -> Self { let saved_mtime; if let Some(file) = file.as_ref() { - saved_mtime = file.mtime; + saved_mtime = file.mtime(); } else { saved_mtime = UNIX_EPOCH; } @@ -619,7 +660,7 @@ impl Buffer { pub fn from_proto( replica_id: ReplicaId, message: proto::Buffer, - file: Option, + file: Option>, language: Option>, cx: &mut ModelContext, ) -> Result { @@ -678,12 +719,12 @@ impl Buffer { } } - pub fn file(&self) -> Option<&File> { - self.file.as_ref() + pub fn file(&self) -> Option<&dyn File> { + self.file.as_deref() } - pub fn file_mut(&mut self) -> Option<&mut File> { - self.file.as_mut() + pub fn file_mut(&mut self) -> Option<&mut dyn File> { + self.file.as_mut().map(|f| f.deref_mut() as &mut dyn File) } pub fn save( @@ -719,7 +760,7 @@ impl Buffer { &mut self, version: time::Global, mtime: SystemTime, - new_file: Option, + new_file: Option>, cx: &mut ModelContext, ) { self.saved_mtime = mtime; @@ -739,13 +780,13 @@ impl Buffer { ) { let file = self.file.as_mut().unwrap(); let mut changed = false; - if path != file.path { - file.path = path; + if path != *file.path() { + file.set_path(path); changed = true; } - if mtime != file.mtime { - file.mtime = mtime; + if mtime != file.mtime() { + file.set_mtime(mtime); changed = true; if let Some(new_text) = new_text { if self.version == self.saved_version { @@ -1015,7 +1056,7 @@ impl Buffer { && self .file .as_ref() - .map_or(false, |file| file.mtime > self.saved_mtime) + .map_or(false, |file| file.mtime() > self.saved_mtime) } pub fn remote_id(&self) -> u64 { @@ -1930,7 +1971,7 @@ impl Clone for Buffer { history: self.history.clone(), selections: self.selections.clone(), deferred_ops: self.deferred_ops.clone(), - file: self.file.clone(), + file: self.file.as_ref().map(|f| f.boxed_clone()), language: self.language.clone(), syntax_tree: Mutex::new(self.syntax_tree.lock().clone()), parsing_in_background: false, @@ -3415,7 +3456,7 @@ mod tests { assert!(buffer.is_dirty()); assert_eq!(*events.borrow(), &[Event::Edited, Event::Dirtied]); events.borrow_mut().clear(); - buffer.did_save(buffer.version(), buffer.file().unwrap().mtime, None, cx); + buffer.did_save(buffer.version(), buffer.file().unwrap().mtime(), None, cx); }); // after saving, the buffer is not dirty, and emits a saved event. diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs index e2d4673a9e..937db41ec1 100644 --- a/zed/src/workspace.rs +++ b/zed/src/workspace.rs @@ -13,7 +13,7 @@ use crate::{ settings::Settings, user, workspace::sidebar::{Side, Sidebar, SidebarItemId, ToggleSidebarItem, ToggleSidebarItemFocus}, - worktree::{File, Worktree}, + worktree::Worktree, AppState, Authenticate, }; use anyhow::Result; @@ -164,12 +164,12 @@ pub trait Item: Entity + Sized { cx: &mut ViewContext, ) -> Self::View; - fn file(&self) -> Option<&File>; + fn worktree_id_and_path(&self) -> Option<(usize, Arc)>; } pub trait ItemView: View { fn title(&self, cx: &AppContext) -> String; - fn entry_id(&self, cx: &AppContext) -> Option<(usize, Arc)>; + fn worktree_id_and_path(&self, cx: &AppContext) -> Option<(usize, Arc)>; fn clone_on_split(&self, _: &mut ViewContext) -> Option where Self: Sized, @@ -206,7 +206,6 @@ pub trait ItemHandle: Send + Sync { } pub trait WeakItemHandle { - fn file<'a>(&'a self, cx: &'a AppContext) -> Option<&'a File>; fn add_view( &self, window_id: usize, @@ -214,6 +213,7 @@ pub trait WeakItemHandle { cx: &mut MutableAppContext, ) -> Option>; fn alive(&self, cx: &AppContext) -> bool; + fn worktree_id_and_path(&self, cx: &AppContext) -> Option<(usize, Arc)>; } pub trait ItemViewHandle { @@ -246,10 +246,6 @@ impl ItemHandle for ModelHandle { } impl WeakItemHandle for WeakModelHandle { - fn file<'a>(&'a self, cx: &'a AppContext) -> Option<&'a File> { - self.upgrade(cx).and_then(|h| h.read(cx).file()) - } - fn add_view( &self, window_id: usize, @@ -268,6 +264,11 @@ impl WeakItemHandle for WeakModelHandle { fn alive(&self, cx: &AppContext) -> bool { self.upgrade(cx).is_some() } + + fn worktree_id_and_path(&self, cx: &AppContext) -> Option<(usize, Arc)> { + self.upgrade(cx) + .and_then(|h| h.read(cx).worktree_id_and_path()) + } } impl ItemViewHandle for ViewHandle { @@ -276,7 +277,7 @@ impl ItemViewHandle for ViewHandle { } fn entry_id(&self, cx: &AppContext) -> Option<(usize, Arc)> { - self.read(cx).entry_id(cx) + self.read(cx).worktree_id_and_path(cx) } fn boxed_clone(&self) -> Box { @@ -717,9 +718,7 @@ impl Workspace { self.items.retain(|item| { if item.alive(cx.as_ref()) { if view_for_existing_item.is_none() - && item - .file(cx.as_ref()) - .map_or(false, |file| file.entry_id() == entry) + && item.worktree_id_and_path(cx).map_or(false, |e| e == entry) { view_for_existing_item = Some( item.add_view(cx.window_id(), settings.clone(), cx.as_mut()) diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index da228ce2fd..a4bb083c2f 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -2,7 +2,7 @@ mod ignore; use self::ignore::IgnoreStack; use crate::{ - editor::{self, Buffer, History, Operation, Rope}, + editor::{self, buffer, Buffer, History, Operation, Rope}, fs::{self, Fs}, fuzzy::CharBag, language::LanguageRegistry, @@ -25,21 +25,10 @@ use postage::{ }; use serde::Deserialize; use smol::channel::{self, Sender}; -use std::{ - cmp::{self, Ordering}, - collections::HashMap, - convert::{TryFrom, TryInto}, - ffi::{OsStr, OsString}, - fmt, - future::Future, - ops::Deref, - path::{Path, PathBuf}, - sync::{ +use std::{any::Any, cmp::{self, Ordering}, collections::HashMap, convert::{TryFrom, TryInto}, ffi::{OsStr, OsString}, fmt, future::Future, ops::Deref, path::{Path, PathBuf}, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, Arc, - }, - time::{Duration, SystemTime}, -}; + }, time::{Duration, SystemTime}}; use sum_tree::{self, Edit, SeekTarget, SumTree}; use zrpc::{PeerId, TypedEnvelope}; @@ -404,7 +393,7 @@ impl Worktree { open_buffers .find(|buffer| { if let Some(file) = buffer.upgrade(cx).and_then(|buffer| buffer.read(cx).file()) { - file.path.as_ref() == path + file.path().as_ref() == path } else { false } @@ -599,30 +588,30 @@ impl Worktree { let mut file_changed = false; if let Some(entry) = file - .entry_id + .entry_id() .and_then(|entry_id| self.entry_for_id(entry_id)) { - if entry.path != file.path { - file.path = entry.path.clone(); + if entry.path != *file.path() { + file.set_path(entry.path.clone()); file_changed = true; } - if entry.mtime != file.mtime { - file.mtime = entry.mtime; + if entry.mtime != file.mtime() { + file.set_mtime(entry.mtime); file_changed = true; if let Some(worktree) = self.as_local() { if buffer_is_clean { - let abs_path = worktree.absolutize(&file.path); + let abs_path = worktree.absolutize(file.path().as_ref()); refresh_buffer(abs_path, &worktree.fs, cx); } } } - } else if let Some(entry) = self.entry_for_path(&file.path) { - file.entry_id = Some(entry.id); - file.mtime = entry.mtime; + } else if let Some(entry) = self.entry_for_path(file.path().as_ref()) { + file.set_entry_id(Some(entry.id)); + file.set_mtime(entry.mtime); if let Some(worktree) = self.as_local() { if buffer_is_clean { - let abs_path = worktree.absolutize(&file.path); + let abs_path = worktree.absolutize(file.path().as_ref()); refresh_buffer(abs_path, &worktree.fs, cx); } } @@ -631,7 +620,7 @@ impl Worktree { if buffer_is_clean { cx.emit(editor::buffer::Event::Dirtied); } - file.entry_id = None; + file.set_entry_id(None); file_changed = true; } @@ -845,7 +834,7 @@ impl LocalWorktree { self.open_buffers.retain(|_buffer_id, buffer| { if let Some(buffer) = buffer.upgrade(cx.as_ref()) { if let Some(file) = buffer.read(cx.as_ref()).file() { - if file.worktree_id() == handle.id() && file.path.as_ref() == path { + if file.worktree_id() == handle.id() && file.path().as_ref() == path { existing_buffer = Some(buffer); } } @@ -864,12 +853,20 @@ impl LocalWorktree { .update(&mut cx, |this, cx| this.as_local().unwrap().load(&path, cx)) .await?; let language = this.read_with(&cx, |this, cx| { + use buffer::File; + this.languages() .select_language(file.full_path(cx)) .cloned() }); let buffer = cx.add_model(|cx| { - Buffer::from_history(0, History::new(contents.into()), Some(file), language, cx) + Buffer::from_history( + 0, + History::new(contents.into()), + Some(Box::new(file)), + language, + cx, + ) }); this.update(&mut cx, |this, _| { let this = this @@ -1238,7 +1235,7 @@ impl RemoteWorktree { self.open_buffers.retain(|_buffer_id, buffer| { if let Some(buffer) = buffer.upgrade(cx.as_ref()) { if let Some(file) = buffer.read(cx.as_ref()).file() { - if file.worktree_id() == cx.model_id() && file.path.as_ref() == path { + if file.worktree_id() == cx.model_id() && file.path().as_ref() == path { existing_buffer = Some(buffer); } } @@ -1273,6 +1270,8 @@ impl RemoteWorktree { .ok_or_else(|| anyhow!("worktree was closed"))?; let file = File::new(entry.id, this.clone(), entry.path, entry.mtime); let language = this.read_with(&cx, |this, cx| { + use buffer::File; + this.languages() .select_language(file.full_path(cx)) .cloned() @@ -1280,7 +1279,14 @@ impl RemoteWorktree { let remote_buffer = response.buffer.ok_or_else(|| anyhow!("empty buffer"))?; let buffer_id = remote_buffer.id as usize; let buffer = cx.add_model(|cx| { - Buffer::from_proto(replica_id, remote_buffer, Some(file), language, cx).unwrap() + Buffer::from_proto( + replica_id, + remote_buffer, + Some(Box::new(file)), + language, + cx, + ) + .unwrap() }); this.update(&mut cx, |this, cx| { let this = this.as_remote_mut().unwrap(); @@ -1774,67 +1780,45 @@ impl File { } } - pub fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext) { - self.worktree.update(cx, |worktree, cx| { - if let Some((rpc, remote_id)) = match worktree { - Worktree::Local(worktree) => worktree - .remote_id - .borrow() - .map(|id| (worktree.rpc.clone(), id)), - Worktree::Remote(worktree) => Some((worktree.rpc.clone(), worktree.remote_id)), - } { - cx.spawn(|worktree, mut cx| async move { - if let Err(error) = rpc - .request(proto::UpdateBuffer { - worktree_id: remote_id, - buffer_id, - operations: vec![(&operation).into()], - }) - .await - { - worktree.update(&mut cx, |worktree, _| { - log::error!("error sending buffer operation: {}", error); - match worktree { - Worktree::Local(t) => &mut t.queued_operations, - Worktree::Remote(t) => &mut t.queued_operations, - } - .push((buffer_id, operation)); - }); - } - }) - .detach(); - } - }); + // pub fn exists(&self) -> bool { + // !self.is_deleted() + // } + + pub fn worktree_id_and_path(&self) -> (usize, Arc) { + (self.worktree.id(), self.path.clone()) + } +} + +impl buffer::File for File { + fn worktree_id(&self) -> usize { + self.worktree.id() } - pub fn buffer_removed(&self, buffer_id: u64, cx: &mut MutableAppContext) { - self.worktree.update(cx, |worktree, cx| { - if let Worktree::Remote(worktree) = worktree { - let worktree_id = worktree.remote_id; - let rpc = worktree.rpc.clone(); - cx.background() - .spawn(async move { - if let Err(error) = rpc - .send(proto::CloseBuffer { - worktree_id, - buffer_id, - }) - .await - { - log::error!("error closing remote buffer: {}", error); - } - }) - .detach(); - } - }); + fn entry_id(&self) -> Option { + self.entry_id } - /// Returns this file's path relative to the root of its worktree. - pub fn path(&self) -> Arc { - self.path.clone() + fn set_entry_id(&mut self, entry_id: Option) { + self.entry_id = entry_id; } - pub fn full_path(&self, cx: &AppContext) -> PathBuf { + fn mtime(&self) -> SystemTime { + self.mtime + } + + fn set_mtime(&mut self, mtime: SystemTime) { + self.mtime = mtime; + } + + fn path(&self) -> &Arc { + &self.path + } + + fn set_path(&mut self, path: Arc) { + self.path = path; + } + + fn full_path(&self, cx: &AppContext) -> PathBuf { let worktree = self.worktree.read(cx); let mut full_path = PathBuf::new(); full_path.push(worktree.root_name()); @@ -1844,22 +1828,18 @@ impl 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. - pub fn file_name<'a>(&'a self, cx: &'a AppContext) -> Option { + fn file_name<'a>(&'a self, cx: &'a AppContext) -> Option { self.path .file_name() .or_else(|| Some(OsStr::new(self.worktree.read(cx).root_name()))) .map(Into::into) } - pub fn is_deleted(&self) -> bool { + fn is_deleted(&self) -> bool { self.entry_id.is_none() } - pub fn exists(&self) -> bool { - !self.is_deleted() - } - - pub fn save( + fn save( &self, buffer_id: u64, text: Rope, @@ -1906,12 +1886,67 @@ impl File { }) } - pub fn worktree_id(&self) -> usize { - self.worktree.id() + fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext) { + self.worktree.update(cx, |worktree, cx| { + if let Some((rpc, remote_id)) = match worktree { + Worktree::Local(worktree) => worktree + .remote_id + .borrow() + .map(|id| (worktree.rpc.clone(), id)), + Worktree::Remote(worktree) => Some((worktree.rpc.clone(), worktree.remote_id)), + } { + cx.spawn(|worktree, mut cx| async move { + if let Err(error) = rpc + .request(proto::UpdateBuffer { + worktree_id: remote_id, + buffer_id, + operations: vec![(&operation).into()], + }) + .await + { + worktree.update(&mut cx, |worktree, _| { + log::error!("error sending buffer operation: {}", error); + match worktree { + Worktree::Local(t) => &mut t.queued_operations, + Worktree::Remote(t) => &mut t.queued_operations, + } + .push((buffer_id, operation)); + }); + } + }) + .detach(); + } + }); } - pub fn entry_id(&self) -> (usize, Arc) { - (self.worktree.id(), self.path.clone()) + fn buffer_removed(&self, buffer_id: u64, cx: &mut MutableAppContext) { + self.worktree.update(cx, |worktree, cx| { + if let Worktree::Remote(worktree) = worktree { + let worktree_id = worktree.remote_id; + let rpc = worktree.rpc.clone(); + cx.background() + .spawn(async move { + if let Err(error) = rpc + .send(proto::CloseBuffer { + worktree_id, + buffer_id, + }) + .await + { + log::error!("error closing remote buffer: {}", error); + } + }) + .detach(); + } + }); + } + + fn boxed_clone(&self) -> Box { + Box::new(self.clone()) + } + + fn as_any(&self) -> &dyn Any { + self } }