Replace FileHandle with File entity that subscribes to Worktree update events

Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
Nathan Sobo
2021-06-21 20:40:40 -06:00
co-authored by Max Brunsfeld
parent cabf6b1f58
commit a364839e93
7 changed files with 425 additions and 671 deletions
+25 -22
View File
@@ -7,7 +7,7 @@ use crate::{
settings::{Settings, StyleId},
util::{post_inc, Bias},
workspace,
worktree::FileHandle,
worktree::File,
};
use anyhow::Result;
pub use buffer::*;
@@ -422,7 +422,7 @@ impl Editor {
reversed: false,
goal: SelectionGoal::None,
}],
Some(cx),
cx,
)
});
Self {
@@ -723,7 +723,7 @@ impl Editor {
let mut new_selections = Vec::new();
self.buffer.update(cx, |buffer, cx| {
let edit_ranges = old_selections.iter().map(|(_, range)| range.clone());
buffer.edit(edit_ranges, text.as_str(), Some(cx));
buffer.edit(edit_ranges, text.as_str(), cx);
let text_len = text.len() as isize;
let mut delta = 0_isize;
new_selections = old_selections
@@ -886,7 +886,7 @@ impl Editor {
})
.collect();
self.buffer
.update(cx, |buffer, cx| buffer.edit(edit_ranges, "", Some(cx)))
.update(cx, |buffer, cx| buffer.edit(edit_ranges, "", cx))
.unwrap();
self.update_selections(new_selections, true, cx);
self.end_transaction(cx);
@@ -939,7 +939,7 @@ impl Editor {
self.buffer.update(cx, |buffer, cx| {
for (offset, text) in edits.into_iter().rev() {
buffer.edit(Some(offset..offset), text, Some(cx)).unwrap();
buffer.edit(Some(offset..offset), text, cx).unwrap();
}
});
@@ -1029,7 +1029,7 @@ impl Editor {
self.unfold_ranges(old_folds, cx);
self.buffer.update(cx, |buffer, cx| {
for (range, text) in edits.into_iter().rev() {
buffer.edit(Some(range), text, Some(cx)).unwrap();
buffer.edit(Some(range), text, cx).unwrap();
}
});
self.fold_ranges(new_folds, cx);
@@ -1117,7 +1117,7 @@ impl Editor {
self.unfold_ranges(old_folds, cx);
self.buffer.update(cx, |buffer, cx| {
for (range, text) in edits.into_iter().rev() {
buffer.edit(Some(range), text, Some(cx)).unwrap();
buffer.edit(Some(range), text, cx).unwrap();
}
});
self.fold_ranges(new_folds, cx);
@@ -1227,11 +1227,11 @@ impl Editor {
if selection_start == selection_end && clipboard_selection.is_entire_line {
let line_start = Point::new(selection_start.row, 0);
buffer
.edit(Some(line_start..line_start), to_insert, Some(cx))
.edit(Some(line_start..line_start), to_insert, cx)
.unwrap();
} else {
buffer
.edit(Some(&selection.start..&selection.end), to_insert, Some(cx))
.edit(Some(&selection.start..&selection.end), to_insert, cx)
.unwrap();
};
@@ -1254,11 +1254,11 @@ impl Editor {
}
pub fn undo(&mut self, _: &(), cx: &mut ViewContext<Self>) {
self.buffer.update(cx, |buffer, cx| buffer.undo(Some(cx)));
self.buffer.update(cx, |buffer, cx| buffer.undo(cx));
}
pub fn redo(&mut self, _: &(), cx: &mut ViewContext<Self>) {
self.buffer.update(cx, |buffer, cx| buffer.redo(Some(cx)));
self.buffer.update(cx, |buffer, cx| buffer.redo(cx));
}
pub fn move_left(&mut self, _: &(), cx: &mut ViewContext<Self>) {
@@ -1997,7 +1997,7 @@ impl Editor {
self.buffer.update(cx, |buffer, cx| {
buffer
.update_selection_set(self.selection_set_id, selections, Some(cx))
.update_selection_set(self.selection_set_id, selections, cx)
.unwrap()
});
self.pause_cursor_blinking(cx);
@@ -2012,9 +2012,9 @@ impl Editor {
}
fn start_transaction(&self, cx: &mut ViewContext<Self>) {
self.buffer.update(cx, |buffer, _| {
self.buffer.update(cx, |buffer, cx| {
buffer
.start_transaction(Some(self.selection_set_id))
.start_transaction(Some(self.selection_set_id), cx)
.unwrap()
});
}
@@ -2022,7 +2022,7 @@ impl Editor {
fn end_transaction(&self, cx: &mut ViewContext<Self>) {
self.buffer.update(cx, |buffer, cx| {
buffer
.end_transaction(Some(self.selection_set_id), Some(cx))
.end_transaction(Some(self.selection_set_id), cx)
.unwrap()
});
}
@@ -2444,7 +2444,7 @@ impl View for Editor {
impl workspace::Item for Buffer {
type View = Editor;
fn file(&self) -> Option<&FileHandle> {
fn file(&self) -> Option<&ModelHandle<File>> {
self.file()
}
@@ -2474,7 +2474,7 @@ impl workspace::ItemView for Editor {
.buffer
.read(cx)
.file()
.and_then(|file| file.file_name(cx));
.and_then(|file| file.read(cx).file_name(cx));
if let Some(name) = filename {
name.to_string_lossy().into()
} else {
@@ -2483,7 +2483,10 @@ 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())
self.buffer
.read(cx)
.file()
.map(|file| file.read(cx).entry_id())
}
fn clone_on_split(&self, cx: &mut ViewContext<Self>) -> Option<Self>
@@ -2497,18 +2500,18 @@ impl workspace::ItemView for Editor {
fn save(
&mut self,
new_file: Option<FileHandle>,
new_file: Option<ModelHandle<File>>,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
self.buffer.update(cx, |b, cx| b.save(new_file, cx))
}
fn is_dirty(&self, cx: &AppContext) -> bool {
self.buffer.read(cx).is_dirty()
self.buffer.read(cx).is_dirty(cx)
}
fn has_conflict(&self, cx: &AppContext) -> bool {
self.buffer.read(cx).has_conflict()
self.buffer.read(cx).has_conflict(cx)
}
}
@@ -2819,7 +2822,7 @@ mod tests {
Point::new(1, 1)..Point::new(1, 1),
],
"\t",
Some(cx),
cx,
)
.unwrap();
});
+221 -209
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -475,7 +475,7 @@ mod tests {
Point::new(2, 1)..Point::new(2, 1),
],
"\t",
Some(cx),
cx,
)
})
.unwrap();
+5 -5
View File
@@ -880,7 +880,7 @@ mod tests {
Point::new(2, 3)..Point::new(2, 3),
],
"123",
Some(cx),
cx,
)
.unwrap();
});
@@ -889,7 +889,7 @@ mod tests {
buffer.update(cx, |buffer, cx| {
let start_version = buffer.version.clone();
buffer
.edit(Some(Point::new(2, 6)..Point::new(4, 3)), "456", Some(cx))
.edit(Some(Point::new(2, 6)..Point::new(4, 3)), "456", cx)
.unwrap();
buffer.edits_since(start_version).collect::<Vec<_>>()
});
@@ -932,7 +932,7 @@ mod tests {
// Edit within one of the folds.
buffer.update(cx, |buffer, cx| {
let version = buffer.version();
buffer.edit(vec![0..1], "12345", Some(cx)).unwrap();
buffer.edit(vec![0..1], "12345", cx).unwrap();
buffer.edits_since(version).collect::<Vec<_>>()
});
map.check_invariants(cx.as_ref());
@@ -972,7 +972,7 @@ mod tests {
buffer.update(cx, |buffer, cx| {
buffer
.edit(Some(Point::new(2, 2)..Point::new(3, 1)), "", Some(cx))
.edit(Some(Point::new(2, 2)..Point::new(3, 1)), "", cx)
.unwrap();
});
assert_eq!(map.text(cx.as_ref()), "aa…eeeee");
@@ -1067,7 +1067,7 @@ mod tests {
let edits = buffer.update(cx, |buffer, cx| {
let start_version = buffer.version.clone();
let edit_count = rng.gen_range(1..=5);
buffer.randomly_edit(&mut rng, edit_count, Some(cx));
buffer.randomly_edit(&mut rng, edit_count, cx);
buffer.edits_since(start_version).collect::<Vec<_>>()
});
log::info!("editing {:?}", edits);
+2 -2
View File
@@ -1,4 +1,4 @@
use crate::worktree::{FileHandle, Worktree};
use crate::worktree::{File, Worktree};
use super::util::SurfResultExt as _;
use anyhow::{anyhow, Context, Result};
@@ -32,7 +32,7 @@ pub struct Client {
pub struct ClientState {
connection_id: Option<ConnectionId>,
pub shared_worktrees: HashMap<u64, ModelHandle<Worktree>>,
pub shared_files: HashMap<FileHandle, HashMap<PeerId, usize>>,
pub shared_files: HashMap<File, HashMap<PeerId, usize>>,
}
impl Client {
+69 -75
View File
@@ -7,7 +7,7 @@ use crate::{
rpc,
settings::Settings,
time::ReplicaId,
worktree::{FileHandle, Worktree, WorktreeHandle},
worktree::{File, Worktree, WorktreeHandle},
AppState,
};
use anyhow::{anyhow, Result};
@@ -116,31 +116,31 @@ mod remote {
rpc: &rpc::Client,
cx: &mut AsyncAppContext,
) -> anyhow::Result<()> {
let message = &request.payload;
let peer_id = request
.original_sender_id
.ok_or_else(|| anyhow!("missing original sender id"))?;
// let message = &request.payload;
// let peer_id = request
// .original_sender_id
// .ok_or_else(|| anyhow!("missing original sender id"))?;
let mut state = rpc.state.lock().await;
let worktree = state
.shared_worktrees
.get(&message.worktree_id)
.ok_or_else(|| anyhow!("worktree {} not found", message.worktree_id))?
.clone();
// let mut state = rpc.state.lock().await;
// let worktree = state
// .shared_worktrees
// .get(&message.worktree_id)
// .ok_or_else(|| anyhow!("worktree {} not found", message.worktree_id))?
// .clone();
let file = cx.update(|cx| worktree.file(&message.path, cx)).await?;
let id = file.id() as u64;
let mtime = file.mtime().as_secs();
// let file = worktree.file(&message.path);
// let id = file.id() as u64;
// let mtime = file.mtime().as_secs();
*state
.shared_files
.entry(file)
.or_insert(Default::default())
.entry(peer_id)
.or_insert(0) += 1;
// *state
// .shared_files
// .entry(file)
// .or_insert(Default::default())
// .entry(peer_id)
// .or_insert(0) += 1;
rpc.respond(request.receipt(), proto::OpenFileResponse { id, mtime })
.await?;
// rpc.respond(request.receipt(), proto::OpenFileResponse { id, mtime })
// .await?;
Ok(())
}
@@ -154,19 +154,19 @@ mod remote {
.original_sender_id
.ok_or_else(|| anyhow!("missing original sender id"))?;
let mut state = rpc.state.lock().await;
if let Some((_, ref_counts)) = state
.shared_files
.iter_mut()
.find(|(file, _)| file.id() == message.id)
{
if let Some(count) = ref_counts.get_mut(&peer_id) {
*count -= 1;
if *count == 0 {
ref_counts.remove(&peer_id);
}
}
}
// let mut state = rpc.state.lock().await;
// if let Some((_, ref_counts)) = state
// .shared_files
// .iter_mut()
// .find(|(file, _)| file.id() == message.id)
// {
// if let Some(count) = ref_counts.get_mut(&peer_id) {
// *count -= 1;
// if *count == 0 {
// ref_counts.remove(&peer_id);
// }
// }
// }
Ok(())
}
@@ -176,24 +176,24 @@ mod remote {
rpc: &rpc::Client,
cx: &mut AsyncAppContext,
) -> anyhow::Result<()> {
let message = &request.payload;
let handle = {
let state = rpc.state.lock().await;
let mut files = state.shared_files.keys();
files.find(|file| file.id() == message.id).cloned()
};
let buffer = if let Some(handle) = handle {
let history = cx.read(|cx| handle.load_history(cx)).await?;
Some(proto::Buffer {
content: history.base_text.to_string(),
history: Vec::new(),
})
} else {
None
};
// let message = &request.payload;
// let handle = {
// let state = rpc.state.lock().await;
// let mut files = state.shared_files.keys();
// files.find(|file| file.id() == message.id).cloned()
// };
// let buffer = if let Some(handle) = handle {
// let history = cx.read(|cx| handle.load_history(cx)).await?;
// Some(proto::Buffer {
// content: history.base_text.to_string(),
// history: Vec::new(),
// })
// } else {
// None
// };
rpc.respond(request.receipt(), proto::OpenBufferResponse { buffer })
.await?;
// rpc.respond(request.receipt(), proto::OpenBufferResponse { buffer })
// .await?;
Ok(())
}
@@ -208,7 +208,7 @@ pub trait Item: Entity + Sized {
cx: &mut ViewContext<Self::View>,
) -> Self::View;
fn file(&self) -> Option<&FileHandle>;
fn file(&self) -> Option<&ModelHandle<File>>;
}
pub trait ItemView: View {
@@ -228,7 +228,7 @@ pub trait ItemView: View {
}
fn save(
&mut self,
_: Option<FileHandle>,
_: Option<ModelHandle<File>>,
_: &mut ViewContext<Self>,
) -> Task<anyhow::Result<()>>;
fn should_activate_item_on_event(_: &Self::Event) -> bool {
@@ -245,7 +245,7 @@ pub trait ItemHandle: Send + Sync {
}
pub trait WeakItemHandle: Send + Sync {
fn file<'a>(&'a self, cx: &'a AppContext) -> Option<&'a FileHandle>;
fn file<'a>(&'a self, cx: &'a AppContext) -> Option<&'a ModelHandle<File>>;
fn add_view(
&self,
window_id: usize,
@@ -267,7 +267,7 @@ pub trait ItemViewHandle: Send + Sync {
fn has_conflict(&self, cx: &AppContext) -> bool;
fn save(
&self,
file: Option<FileHandle>,
file: Option<ModelHandle<File>>,
cx: &mut MutableAppContext,
) -> Task<anyhow::Result<()>>;
}
@@ -283,7 +283,7 @@ impl<T: Item> ItemHandle for ModelHandle<T> {
}
impl<T: Item> WeakItemHandle for WeakModelHandle<T> {
fn file<'a>(&'a self, cx: &'a AppContext) -> Option<&'a FileHandle> {
fn file<'a>(&'a self, cx: &'a AppContext) -> Option<&'a ModelHandle<File>> {
self.upgrade(cx).and_then(|h| h.read(cx).file())
}
@@ -345,7 +345,7 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
fn save(
&self,
file: Option<FileHandle>,
file: Option<ModelHandle<File>>,
cx: &mut MutableAppContext,
) -> Task<anyhow::Result<()>> {
self.update(cx, |item, cx| item.save(file, cx))
@@ -484,13 +484,13 @@ impl Workspace {
.map(|(abs_path, file)| {
let is_file = bg.spawn(async move { abs_path.is_file() });
cx.spawn(|this, mut cx| async move {
if let Ok(file) = file.await {
if is_file.await {
return this
.update(&mut cx, |this, cx| this.open_entry(file.entry_id(), cx));
}
if is_file.await {
return this.update(&mut cx, |this, cx| {
this.open_entry(file.read(cx).entry_id(), cx)
});
} else {
None
}
None
})
})
.collect::<Vec<_>>();
@@ -503,11 +503,7 @@ impl Workspace {
}
}
fn file_for_path(
&mut self,
abs_path: &Path,
cx: &mut ViewContext<Self>,
) -> Task<Result<FileHandle>> {
fn file_for_path(&mut self, abs_path: &Path, cx: &mut ViewContext<Self>) -> ModelHandle<File> {
for tree in self.worktrees.iter() {
if let Some(relative_path) = tree
.read(cx)
@@ -592,7 +588,7 @@ impl Workspace {
if view_for_existing_item.is_none()
&& item
.file(cx.as_ref())
.map_or(false, |f| f.entry_id() == entry)
.map_or(false, |file| file.read(cx).entry_id() == entry)
{
view_for_existing_item = Some(
item.add_view(cx.window_id(), settings.clone(), cx.as_mut())
@@ -629,8 +625,7 @@ impl Workspace {
cx.as_mut()
.spawn(|mut cx| async move {
let buffer = async move {
let file = file.await?;
let history = cx.read(|cx| file.load_history(cx));
let history = cx.read(|cx| file.read(cx).load_history(cx));
let history = cx.background_executor().spawn(history).await?;
let buffer = cx.add_model(|cx| {
let language = language_registry.select_language(path);
@@ -696,9 +691,8 @@ impl Workspace {
if let Some(path) = path {
cx.spawn(|mut cx| async move {
let result = async move {
let file = handle
.update(&mut cx, |me, cx| me.file_for_path(&path, cx))
.await?;
let file =
handle.update(&mut cx, |me, cx| me.file_for_path(&path, cx));
cx.update(|cx| item.save(Some(file), cx)).await
}
.await;
+102 -357
View File
@@ -10,7 +10,7 @@ use crate::{
util::Bias,
};
use ::ignore::gitignore::Gitignore;
use anyhow::{anyhow, Context, Result};
use anyhow::{Context, Result};
pub use fuzzy::{match_paths, PathMatch};
use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task};
use lazy_static::lazy_static;
@@ -19,22 +19,18 @@ use postage::{
prelude::{Sink, Stream},
watch,
};
use smol::{channel::Sender, lock::Mutex as AsyncMutex};
use smol::channel::Sender;
use std::{
cmp,
collections::{HashMap, HashSet},
ffi::{OsStr, OsString},
fmt, fs,
future::Future,
hash::Hash,
io::{self, Read, Write},
ops::Deref,
os::unix::fs::MetadataExt,
path::{Path, PathBuf},
sync::{
atomic::{AtomicU64, Ordering::SeqCst},
Arc, Weak,
},
sync::{atomic::AtomicU64, Arc},
time::{Duration, SystemTime, UNIX_EPOCH},
};
@@ -123,7 +119,6 @@ impl Deref for Worktree {
pub struct LocalWorktree {
snapshot: Snapshot,
background_snapshot: Arc<Mutex<Snapshot>>,
handles: Arc<Mutex<HashMap<Arc<Path>, Weak<Mutex<FileHandleState>>>>>,
next_handle_id: AtomicU64,
scan_state: (watch::Sender<ScanState>, watch::Receiver<ScanState>),
_event_stream_handle: fsevent::Handle,
@@ -131,40 +126,6 @@ pub struct LocalWorktree {
rpc: Option<rpc::Client>,
}
#[derive(Clone)]
pub struct FileHandle {
worktree: ModelHandle<Worktree>,
state: Arc<Mutex<FileHandleState>>,
}
#[derive(Clone)]
struct FileHandleState {
path: Arc<Path>,
is_deleted: bool,
mtime: Duration,
worktree_id: usize,
id: u64,
rpc: Option<(ConnectionId, rpc::Client)>,
}
impl Drop for FileHandleState {
fn drop(&mut self) {
if let Some((connection_id, rpc)) = self.rpc.take() {
let id = self.id;
let worktree_id = self.worktree_id as u64;
smol::spawn(async move {
if let Err(error) = rpc
.send(connection_id, proto::CloseFile { worktree_id, id })
.await
{
log::warn!("error closing file {}: {}", id, error);
}
})
.detach();
}
}
}
impl LocalWorktree {
fn new(path: impl Into<Arc<Path>>, cx: &mut ModelContext<Worktree>) -> Self {
let abs_path = path.into();
@@ -182,12 +143,10 @@ impl LocalWorktree {
fsevent::EventStream::new(&[snapshot.abs_path.as_ref()], Duration::from_millis(100));
let background_snapshot = Arc::new(Mutex::new(snapshot.clone()));
let handles = Arc::new(Mutex::new(Default::default()));
let tree = Self {
snapshot,
background_snapshot: background_snapshot.clone(),
handles: handles.clone(),
next_handle_id: Default::default(),
scan_state: watch::channel_with(ScanState::Scanning),
_event_stream_handle: event_stream_handle,
@@ -196,7 +155,7 @@ impl LocalWorktree {
};
std::thread::spawn(move || {
let scanner = BackgroundScanner::new(background_snapshot, handles, scan_state_tx, id);
let scanner = BackgroundScanner::new(background_snapshot, scan_state_tx, id);
scanner.run(event_stream)
});
@@ -297,7 +256,6 @@ impl LocalWorktree {
}
pub fn save(&self, path: &Path, content: Rope, cx: &AppContext) -> Task<Result<()>> {
let handles = self.handles.clone();
let path = path.to_path_buf();
let abs_path = self.absolutize(&path);
cx.background_executor().spawn(async move {
@@ -308,13 +266,6 @@ impl LocalWorktree {
writer.write(chunk.as_bytes())?;
}
writer.flush()?;
if let Some(handle) = handles.lock().get(&*path).and_then(Weak::upgrade) {
let mut handle = handle.lock();
handle.mtime = file.metadata()?.modified()?.duration_since(UNIX_EPOCH)?;
handle.is_deleted = false;
}
Ok(())
})
}
@@ -387,7 +338,6 @@ impl fmt::Debug for LocalWorktree {
pub struct RemoteWorktree {
remote_id: usize,
snapshot: Snapshot,
handles: Arc<Mutex<HashMap<Arc<Path>, Arc<AsyncMutex<Weak<Mutex<FileHandleState>>>>>>>,
rpc: rpc::Client,
connection_id: ConnectionId,
}
@@ -442,7 +392,6 @@ impl RemoteWorktree {
Self {
remote_id,
snapshot,
handles: Default::default(),
rpc,
connection_id,
}
@@ -695,44 +644,74 @@ pub struct Diff {
pub modified: HashSet<Arc<Path>>,
}
impl FileHandle {
pub fn id(&self) -> u64 {
self.state.lock().id
#[derive(Clone, PartialEq)]
pub struct File {
worktree: ModelHandle<Worktree>,
path: Arc<Path>,
}
impl Entity for File {
type Event = ();
}
impl File {
pub fn new(
worktree: ModelHandle<Worktree>,
path: Arc<Path>,
cx: &mut ModelContext<Self>,
) -> Self {
cx.subscribe(&worktree, Self::handle_worktree_update);
Self { worktree, path }
}
fn handle_worktree_update(&mut self, diff: &Diff, cx: &mut ModelContext<Self>) {
if let Some(new_path) = diff.moved.get(&self.path) {
self.path = new_path.clone();
cx.notify();
} else if diff.added.contains(&self.path)
|| diff.removed.contains(&self.path)
|| diff.modified.contains(&self.path)
{
cx.notify();
}
}
/// Returns this file's path relative to the root of its worktree.
pub fn path(&self) -> Arc<Path> {
self.state.lock().path.clone()
self.path.clone()
}
/// 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> {
self.state
.lock()
.path
self.path
.file_name()
.or_else(|| Some(OsStr::new(self.worktree.read(cx).root_name())))
.map(Into::into)
}
pub fn is_deleted(&self) -> bool {
self.state.lock().is_deleted
pub fn is_deleted(&self, cx: &AppContext) -> bool {
let snapshot = self.worktree.read(cx).snapshot();
snapshot.entry_for_path(&self.path).is_none() && !snapshot.path_is_pending(&self.path)
}
pub fn mtime(&self) -> Duration {
self.state.lock().mtime
pub fn exists(&self, cx: &AppContext) -> bool {
!self.is_deleted(cx)
}
pub fn exists(&self) -> bool {
!self.is_deleted()
pub fn mtime(&self, cx: &AppContext) -> Duration {
let snapshot = self.worktree.read(cx).snapshot();
snapshot
.entry_for_path(&self.path)
.map_or(Duration::ZERO, |entry| {
entry.mtime.duration_since(UNIX_EPOCH).unwrap()
})
}
pub fn load_history(&self, cx: &AppContext) -> Task<Result<History>> {
match self.worktree.read(cx) {
Worktree::Local(worktree) => {
let path = self.state.lock().path.to_path_buf();
let abs_path = worktree.absolutize(&path);
let abs_path = worktree.absolutize(&self.path);
cx.background_executor().spawn(async move {
let mut file = fs::File::open(&abs_path)?;
let mut base_text = String::new();
@@ -741,20 +720,21 @@ impl FileHandle {
})
}
Worktree::Remote(worktree) => {
let state = self.state.lock();
let id = state.id;
let worktree_id = worktree.remote_id as u64;
let (connection_id, rpc) = state.rpc.clone().unwrap();
cx.background_executor().spawn(async move {
let response = rpc
.request(connection_id, proto::OpenBuffer { worktree_id, id })
.await?;
let buffer = response
.buffer
.ok_or_else(|| anyhow!("buffer must be present"))?;
let history = History::new(buffer.content.into());
Ok(history)
})
todo!()
// let state = self.state.lock();
// let id = state.id;
// let worktree_id = worktree.remote_id as u64;
// let (connection_id, rpc) = state.rpc.clone().unwrap();
// cx.background_executor().spawn(async move {
// let response = rpc
// .request(connection_id, proto::OpenBuffer { worktree_id, id })
// .await?;
// let buffer = response
// .buffer
// .ok_or_else(|| anyhow!("buffer must be present"))?;
// let history = History::new(buffer.content.into());
// Ok(history)
// })
}
}
}
@@ -771,55 +751,6 @@ impl FileHandle {
pub fn entry_id(&self) -> (usize, Arc<Path>) {
(self.worktree.id(), self.path())
}
pub fn observe_from_model<T: Entity>(
&self,
cx: &mut ModelContext<T>,
mut callback: impl FnMut(&mut T, FileHandle, &mut ModelContext<T>) + 'static,
) {
let mut prev_state = self.state.lock().clone();
let cur_state = Arc::downgrade(&self.state);
cx.observe(&self.worktree, move |observer, worktree, cx| {
if let Some(cur_state) = cur_state.upgrade() {
let cur_state_unlocked = cur_state.lock();
if cur_state_unlocked.mtime != prev_state.mtime
|| cur_state_unlocked.path != prev_state.path
{
prev_state = cur_state_unlocked.clone();
drop(cur_state_unlocked);
callback(
observer,
FileHandle {
worktree,
state: cur_state,
},
cx,
);
}
}
});
}
}
impl PartialEq for FileHandle {
fn eq(&self, other: &Self) -> bool {
if Arc::ptr_eq(&self.state, &other.state) {
true
} else {
let self_state = self.state.lock();
let other_state = other.state.lock();
self_state.worktree_id == other_state.worktree_id && self_state.id == other_state.id
}
}
}
impl Eq for FileHandle {}
impl Hash for FileHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.state.lock().id.hash(state);
self.worktree.hash(state);
}
}
#[derive(Clone, Debug)]
@@ -998,23 +929,16 @@ impl<'a> sum_tree::Dimension<'a, EntrySummary> for VisibleFileCount {
struct BackgroundScanner {
snapshot: Arc<Mutex<Snapshot>>,
notify: Sender<ScanState>,
handles: Arc<Mutex<HashMap<Arc<Path>, Weak<Mutex<FileHandleState>>>>>,
thread_pool: scoped_pool::Pool,
root_char_bag: CharBag,
}
impl BackgroundScanner {
fn new(
snapshot: Arc<Mutex<Snapshot>>,
handles: Arc<Mutex<HashMap<Arc<Path>, Weak<Mutex<FileHandleState>>>>>,
notify: Sender<ScanState>,
worktree_id: usize,
) -> Self {
fn new(snapshot: Arc<Mutex<Snapshot>>, notify: Sender<ScanState>, worktree_id: usize) -> Self {
Self {
root_char_bag: Default::default(),
snapshot,
notify,
handles,
thread_pool: scoped_pool::Pool::new(16, format!("worktree-{}-scanner", worktree_id)),
}
}
@@ -1125,7 +1049,6 @@ impl BackgroundScanner {
});
}
self.mark_deleted_file_handles();
Ok(())
}
@@ -1231,73 +1154,6 @@ impl BackgroundScanner {
return false;
};
let mut renamed_paths: HashMap<u64, PathBuf> = HashMap::new();
let mut handles = self.handles.lock();
let mut updated_handles = HashMap::new();
for event in &events {
let path = if let Ok(path) = event.path.strip_prefix(&root_abs_path) {
path
} else {
continue;
};
let metadata = fs::metadata(&event.path);
if event.flags.contains(fsevent::StreamFlags::ITEM_RENAMED) {
if let Some(inode) = snapshot.inode_for_path(path) {
renamed_paths.insert(inode, path.to_path_buf());
} else if let Ok(metadata) = &metadata {
let new_path = path;
if let Some(old_path) = renamed_paths.get(&metadata.ino()) {
handles.retain(|handle_path, handle_state| {
if let Ok(path_suffix) = handle_path.strip_prefix(&old_path) {
let new_handle_path: Arc<Path> =
if path_suffix.file_name().is_some() {
new_path.join(path_suffix)
} else {
new_path.to_path_buf()
}
.into();
if let Some(handle_state) = Weak::upgrade(&handle_state) {
let mut state = handle_state.lock();
state.path = new_handle_path.clone();
updated_handles
.insert(new_handle_path, Arc::downgrade(&handle_state));
}
false
} else {
true
}
});
handles.extend(updated_handles.drain());
}
}
}
for state in handles.values_mut() {
if let Some(state) = Weak::upgrade(&state) {
let mut state = state.lock();
if state.path.as_ref() == path {
if let Ok(metadata) = &metadata {
state.mtime = metadata
.modified()
.unwrap()
.duration_since(UNIX_EPOCH)
.unwrap();
}
} else if state.path.starts_with(path) {
if let Ok(metadata) = fs::metadata(state.path.as_ref()) {
state.mtime = metadata
.modified()
.unwrap()
.duration_since(UNIX_EPOCH)
.unwrap();
}
}
}
}
}
drop(handles);
events.sort_unstable_by(|a, b| a.path.cmp(&b.path));
let mut abs_paths = events.into_iter().map(|e| e.path).peekable();
let (scan_queue_tx, scan_queue_rx) = crossbeam_channel::unbounded();
@@ -1363,7 +1219,6 @@ impl BackgroundScanner {
});
self.update_ignore_statuses();
self.mark_deleted_file_handles();
true
}
@@ -1453,20 +1308,6 @@ impl BackgroundScanner {
self.snapshot.lock().entries.edit(edits, &());
}
fn mark_deleted_file_handles(&self) {
let mut handles = self.handles.lock();
let snapshot = self.snapshot.lock();
handles.retain(|path, handle_state| {
if let Some(handle_state) = Weak::upgrade(&handle_state) {
let mut handle_state = handle_state.lock();
handle_state.is_deleted = snapshot.entry_for_path(&path).is_none();
true
} else {
false
}
});
}
fn fs_entry_for_path(&self, path: Arc<Path>, abs_path: &Path) -> Result<Option<Entry>> {
let metadata = match fs::metadata(&abs_path) {
Err(err) => {
@@ -1526,7 +1367,7 @@ struct UpdateIgnoreStatusJob {
}
pub trait WorktreeHandle {
fn file(&self, path: impl AsRef<Path>, cx: &mut MutableAppContext) -> Task<Result<FileHandle>>;
fn file(&self, path: impl AsRef<Path>, cx: &mut MutableAppContext) -> ModelHandle<File>;
#[cfg(test)]
fn flush_fs_events<'a>(
@@ -1536,108 +1377,10 @@ pub trait WorktreeHandle {
}
impl WorktreeHandle for ModelHandle<Worktree> {
fn file(&self, path: impl AsRef<Path>, cx: &mut MutableAppContext) -> Task<Result<FileHandle>> {
fn file(&self, path: impl AsRef<Path>, cx: &mut MutableAppContext) -> ModelHandle<File> {
let path = Arc::from(path.as_ref());
let handle = self.clone();
let tree = self.read(cx);
match tree {
Worktree::Local(tree) => {
let worktree_id = handle.id();
let abs_path = tree.absolutize(&path);
cx.spawn(|cx| async move {
let mtime = cx
.background_executor()
.spawn(async move { fs::metadata(&abs_path) })
.await?
.modified()?
.duration_since(UNIX_EPOCH)?;
let state = handle.read_with(&cx, |tree, _| {
let mut handles = tree.as_local().unwrap().handles.lock();
handles
.get(&path)
.and_then(Weak::upgrade)
.unwrap_or_else(|| {
let id =
tree.as_local().unwrap().next_handle_id.fetch_add(1, SeqCst);
let handle_state = if let Some(entry) = tree.entry_for_path(&path) {
FileHandleState {
path: entry.path().clone(),
is_deleted: false,
mtime,
worktree_id,
id,
rpc: None,
}
} else {
FileHandleState {
path: path.clone(),
is_deleted: !tree.path_is_pending(&path),
mtime,
worktree_id,
id,
rpc: None,
}
};
let state = Arc::new(Mutex::new(handle_state.clone()));
handles.insert(path, Arc::downgrade(&state));
state
})
});
Ok(FileHandle {
worktree: handle.clone(),
state,
})
})
}
Worktree::Remote(tree) => {
let remote_worktree_id = tree.remote_id;
let connection_id = tree.connection_id;
let rpc = tree.rpc.clone();
let handles = tree.handles.clone();
cx.spawn(|cx| async move {
let state = handles
.lock()
.entry(path.clone())
.or_insert_with(|| Arc::new(AsyncMutex::new(Weak::new())))
.clone();
let mut state = state.lock().await;
if let Some(state) = Weak::upgrade(&state) {
Ok(FileHandle {
worktree: handle,
state,
})
} else {
let response = rpc
.request(
connection_id,
proto::OpenFile {
worktree_id: remote_worktree_id as u64,
path: path.to_string_lossy().to_string(),
},
)
.await?;
let is_deleted = handle.read_with(&cx, |tree, _| {
tree.entry_for_path(&path).is_none() && !tree.path_is_pending(&path)
});
let new_state = Arc::new(Mutex::new(FileHandleState {
path,
is_deleted,
mtime: Duration::from_secs(response.mtime),
worktree_id: remote_worktree_id,
id: response.id,
rpc: Some((connection_id, rpc)),
}));
*state = Arc::downgrade(&new_state);
Ok(FileHandle {
worktree: handle,
state: new_state,
})
}
})
}
}
cx.add_model(|cx| File::new(handle, path, cx))
}
// When the worktree's FS event stream sometimes delivers "redundant" events for FS changes that
@@ -1850,8 +1593,10 @@ mod tests {
path
});
let file = cx.update(|cx| tree.file(&path, cx)).await.unwrap();
let history = cx.read(|cx| file.load_history(cx)).await.unwrap();
let history = cx
.update(|cx| tree.file(&path, cx).read(cx).load_history(cx.as_ref()))
.await
.unwrap();
cx.read(|cx| {
assert_eq!(history.base_text.as_ref(), buffer.read(cx).text());
});
@@ -1869,14 +1614,17 @@ mod tests {
cx.read(|cx| assert_eq!(tree.read(cx).file_count(), 1));
let buffer = cx.add_model(|cx| Buffer::new(1, "a line of text.\n".repeat(10 * 1024), cx));
let file = cx.update(|cx| tree.file("", cx));
let file = cx.update(|cx| tree.file("", cx)).await.unwrap();
cx.update(|cx| {
assert_eq!(file.path().file_name(), None);
smol::block_on(file.save(buffer.read(cx).snapshot().text(), cx.as_ref())).unwrap();
});
let history = file
.read_with(&cx, |file, cx| {
assert_eq!(file.path().file_name(), None);
smol::block_on(file.save(buffer.read(cx).snapshot().text(), cx.as_ref())).unwrap();
file.load_history(cx)
})
.await
.unwrap();
let history = cx.read(|cx| file.load_history(cx)).await.unwrap();
cx.read(|cx| assert_eq!(history.base_text.as_ref(), buffer.read(cx).text()));
}
@@ -1897,20 +1645,23 @@ mod tests {
}));
let tree = cx.add_model(|cx| Worktree::local(dir.path(), cx));
let file2 = cx.update(|cx| tree.file("a/file2", cx)).await.unwrap();
let file3 = cx.update(|cx| tree.file("a/file3", cx)).await.unwrap();
let file4 = cx.update(|cx| tree.file("b/c/file4", cx)).await.unwrap();
let file5 = cx.update(|cx| tree.file("b/c/file5", cx)).await.unwrap();
let non_existent_file = cx.update(|cx| tree.file("a/file_x", cx)).await.unwrap();
let file2 = cx.update(|cx| tree.file("a/file2", cx));
let file3 = cx.update(|cx| tree.file("a/file3", cx));
let file4 = cx.update(|cx| tree.file("b/c/file4", cx));
let file5 = cx.update(|cx| tree.file("b/c/file5", cx));
let non_existent_file = cx.update(|cx| tree.file("a/file_x", cx));
// After scanning, the worktree knows which files exist and which don't.
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
.await;
assert!(!file2.is_deleted());
assert!(!file3.is_deleted());
assert!(!file4.is_deleted());
assert!(!file5.is_deleted());
assert!(non_existent_file.is_deleted());
cx.read(|cx| {
assert!(!file2.read(cx).is_deleted(cx));
assert!(!file3.read(cx).is_deleted(cx));
assert!(!file4.read(cx).is_deleted(cx));
assert!(!file5.read(cx).is_deleted(cx));
assert!(non_existent_file.read(cx).is_deleted(cx));
});
tree.flush_fs_events(&cx).await;
std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap();
@@ -1936,18 +1687,14 @@ mod tests {
]
);
assert_eq!(file2.path().to_str().unwrap(), "a/file2.new");
assert_eq!(file4.path().as_ref(), Path::new("d/file4"));
assert_eq!(file5.path().as_ref(), Path::new("d/file5"));
assert!(!file2.is_deleted());
assert!(!file4.is_deleted());
assert!(file5.is_deleted());
// Right now, this rename isn't detected because the target path
// no longer exists on the file system by the time we process the
// rename event.
assert_eq!(file3.path().as_ref(), Path::new("a/file3"));
assert!(file3.is_deleted());
assert_eq!(file2.read(cx).path().as_ref(), Path::new("a/file2.new"));
assert_eq!(file3.read(cx).path().as_ref(), Path::new("d/file3"));
assert_eq!(file4.read(cx).path().as_ref(), Path::new("d/file4"));
assert_eq!(file5.read(cx).path().as_ref(), Path::new("b/c/file5"));
assert!(!file2.read(cx).is_deleted(cx));
assert!(!file3.read(cx).is_deleted(cx));
assert!(!file4.read(cx).is_deleted(cx));
assert!(file5.read(cx).is_deleted(cx));
});
}
@@ -2158,7 +1905,6 @@ mod tests {
ignores: Default::default(),
root_name: Default::default(),
})),
Arc::new(Mutex::new(Default::default())),
notify_tx,
0,
);
@@ -2193,7 +1939,6 @@ mod tests {
ignores: Default::default(),
root_name: Default::default(),
})),
Arc::new(Mutex::new(Default::default())),
notify_tx,
1,
);