Introduce a File trait object to buffer

This will remove the dependency of buffer on `worktree::File`
This commit is contained in:
Nathan Sobo 2021-10-02 19:01:29 -06:00
parent 7728467790
commit f00045544f
5 changed files with 216 additions and 138 deletions

View file

@ -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, _| {

View file

@ -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<Self>,
settings: watch::Receiver<Settings>,
@ -2592,6 +2588,10 @@ impl workspace::Item for Buffer {
cx,
)
}
fn worktree_id_and_path(&self) -> Option<(usize, Arc<Path>)> {
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<Path>)> {
self.buffer.read(cx).file().map(|file| file.entry_id())
fn worktree_id_and_path(&self, cx: &AppContext) -> Option<(usize, Arc<Path>)> {
self.buffer
.read(cx)
.file()
.map(|file| (file.worktree_id(), file.path().clone()))
}
fn clone_on_split(&self, cx: &mut ViewContext<Self>) -> Option<Self>
@ -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);
});
})

View file

@ -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<usize>;
fn set_entry_id(&mut self, entry_id: Option<usize>);
fn mtime(&self) -> SystemTime;
fn set_mtime(&mut self, mtime: SystemTime);
fn path(&self) -> &Arc<Path>;
fn set_path(&mut self, path: Arc<Path>);
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<OsString>;
fn is_deleted(&self) -> bool;
fn save(
&self,
buffer_id: u64,
text: Rope,
version: time::Global,
cx: &mut MutableAppContext,
) -> Task<Result<(time::Global, SystemTime)>>;
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<dyn File>;
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>,
file: Option<Box<dyn File>>,
language: Option<Arc<Language>>,
sync_parse_timeout: Duration,
syntax_tree: Mutex<Option<SyntaxTree>>,
@ -524,7 +565,7 @@ impl Buffer {
pub fn from_history(
replica_id: ReplicaId,
history: History,
file: Option<File>,
file: Option<Box<dyn File>>,
language: Option<Arc<Language>>,
cx: &mut ModelContext<Self>,
) -> Self {
@ -541,14 +582,14 @@ impl Buffer {
fn build(
replica_id: ReplicaId,
history: History,
file: Option<File>,
file: Option<Box<dyn File>>,
remote_id: u64,
language: Option<Arc<Language>>,
cx: &mut ModelContext<Self>,
) -> 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>,
file: Option<Box<dyn File>>,
language: Option<Arc<Language>>,
cx: &mut ModelContext<Self>,
) -> Result<Self> {
@ -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<File>,
new_file: Option<Box<dyn File>>,
cx: &mut ModelContext<Self>,
) {
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.

View file

@ -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>,
) -> Self::View;
fn file(&self) -> Option<&File>;
fn worktree_id_and_path(&self) -> Option<(usize, Arc<Path>)>;
}
pub trait ItemView: View {
fn title(&self, cx: &AppContext) -> String;
fn entry_id(&self, cx: &AppContext) -> Option<(usize, Arc<Path>)>;
fn worktree_id_and_path(&self, cx: &AppContext) -> Option<(usize, Arc<Path>)>;
fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
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<Box<dyn ItemViewHandle>>;
fn alive(&self, cx: &AppContext) -> bool;
fn worktree_id_and_path(&self, cx: &AppContext) -> Option<(usize, Arc<Path>)>;
}
pub trait ItemViewHandle {
@ -246,10 +246,6 @@ impl<T: Item> ItemHandle for ModelHandle<T> {
}
impl<T: Item> WeakItemHandle for WeakModelHandle<T> {
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<T: Item> WeakItemHandle for WeakModelHandle<T> {
fn alive(&self, cx: &AppContext) -> bool {
self.upgrade(cx).is_some()
}
fn worktree_id_and_path(&self, cx: &AppContext) -> Option<(usize, Arc<Path>)> {
self.upgrade(cx)
.and_then(|h| h.read(cx).worktree_id_and_path())
}
}
impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
@ -276,7 +277,7 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
}
fn entry_id(&self, cx: &AppContext) -> Option<(usize, Arc<Path>)> {
self.read(cx).entry_id(cx)
self.read(cx).worktree_id_and_path(cx)
}
fn boxed_clone(&self) -> Box<dyn ItemViewHandle> {
@ -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())

View file

@ -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<Path>) {
(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<usize> {
self.entry_id
}
/// Returns this file's path relative to the root of its worktree.
pub fn path(&self) -> Arc<Path> {
self.path.clone()
fn set_entry_id(&mut self, entry_id: Option<usize>) {
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<Path> {
&self.path
}
fn set_path(&mut self, path: Arc<Path>) {
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<OsString> {
fn file_name<'a>(&'a self, cx: &'a AppContext) -> Option<OsString> {
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<Path>) {
(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<dyn buffer::File> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
}