Fill out some missing parts of the new worktree module
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
4878bf82ff
commit
3fa4e5acee
1 changed files with 115 additions and 13 deletions
|
@ -8,7 +8,7 @@ use crate::{
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
pub use fuzzy::match_paths;
|
pub use fuzzy::match_paths;
|
||||||
use fuzzy::PathEntry;
|
use fuzzy::PathEntry;
|
||||||
use gpui::{scoped_pool, AppContext, Entity, ModelContext, Task};
|
use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, Task};
|
||||||
use ignore::dir::{Ignore, IgnoreBuilder};
|
use ignore::dir::{Ignore, IgnoreBuilder};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use smol::{channel::Sender, Timer};
|
use smol::{channel::Sender, Timer};
|
||||||
|
@ -27,6 +27,8 @@ use std::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use fuzzy::PathMatch;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum ScanState {
|
enum ScanState {
|
||||||
Idle,
|
Idle,
|
||||||
|
@ -48,6 +50,12 @@ pub struct Worktree {
|
||||||
poll_scheduled: bool,
|
poll_scheduled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct FileHandle {
|
||||||
|
worktree: ModelHandle<Worktree>,
|
||||||
|
inode: u64,
|
||||||
|
}
|
||||||
|
|
||||||
impl Worktree {
|
impl Worktree {
|
||||||
fn new(path: impl Into<Arc<Path>>, ctx: &mut ModelContext<Self>) -> Self {
|
fn new(path: impl Into<Arc<Path>>, ctx: &mut ModelContext<Self>) -> Self {
|
||||||
let path = path.into();
|
let path = path.into();
|
||||||
|
@ -98,7 +106,7 @@ impl Worktree {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn root_ino(&self) -> Option<u64> {
|
fn root_inode(&self) -> Option<u64> {
|
||||||
let ino = self.scanner.root_ino.load(atomic::Ordering::SeqCst);
|
let ino = self.scanner.root_ino.load(atomic::Ordering::SeqCst);
|
||||||
if ino == 0 {
|
if ino == 0 {
|
||||||
None
|
None
|
||||||
|
@ -121,21 +129,20 @@ impl Worktree {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn root_entry(&self) -> Option<&Entry> {
|
fn root_entry(&self) -> Option<&Entry> {
|
||||||
self.root_ino().and_then(|ino| self.entries.get(&ino))
|
self.root_inode().and_then(|ino| self.entries.get(&ino))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn file_count(&self) -> usize {
|
fn file_count(&self) -> usize {
|
||||||
self.entries.summary().file_count
|
self.entries.summary().file_count
|
||||||
}
|
}
|
||||||
|
|
||||||
fn abs_entry_path(&self, ino: u64) -> Result<PathBuf> {
|
fn abs_path_for_inode(&self, ino: u64) -> Result<PathBuf> {
|
||||||
let mut result = self.path.to_path_buf();
|
let mut result = self.path.to_path_buf();
|
||||||
result.pop();
|
result.push(self.path_for_inode(ino, false)?);
|
||||||
result.push(self.entry_path(ino)?);
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn entry_path(&self, ino: u64) -> Result<PathBuf> {
|
pub fn path_for_inode(&self, ino: u64, include_root: bool) -> Result<PathBuf> {
|
||||||
let mut components = Vec::new();
|
let mut components = Vec::new();
|
||||||
let mut entry = self
|
let mut entry = self
|
||||||
.entries
|
.entries
|
||||||
|
@ -147,15 +154,39 @@ impl Worktree {
|
||||||
components.push(entry.name());
|
components.push(entry.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut components = components.into_iter().rev();
|
||||||
|
if !include_root {
|
||||||
|
components.next();
|
||||||
|
}
|
||||||
|
|
||||||
let mut path = PathBuf::new();
|
let mut path = PathBuf::new();
|
||||||
for component in components.into_iter().rev() {
|
for component in components {
|
||||||
path.push(component);
|
path.push(component);
|
||||||
}
|
}
|
||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn inode_for_path(&self, path: impl AsRef<Path>) -> Option<u64> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
self.root_inode().and_then(|mut inode| {
|
||||||
|
'components: for path_component in path {
|
||||||
|
if let Some(Entry::Dir { children, .. }) = &self.entries.get(&inode) {
|
||||||
|
for child in children.as_ref() {
|
||||||
|
if self.entries.get(child).map(|entry| entry.name()) == Some(path_component)
|
||||||
|
{
|
||||||
|
inode = *child;
|
||||||
|
continue 'components;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(inode)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load_file(&self, ino: u64, ctx: &AppContext) -> impl Future<Output = Result<String>> {
|
pub fn load_file(&self, ino: u64, ctx: &AppContext) -> impl Future<Output = Result<String>> {
|
||||||
let path = self.abs_entry_path(ino);
|
let path = self.abs_path_for_inode(ino);
|
||||||
ctx.background_executor().spawn(async move {
|
ctx.background_executor().spawn(async move {
|
||||||
let mut file = std::fs::File::open(&path?)?;
|
let mut file = std::fs::File::open(&path?)?;
|
||||||
let mut base_text = String::new();
|
let mut base_text = String::new();
|
||||||
|
@ -165,7 +196,7 @@ impl Worktree {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save<'a>(&self, ino: u64, content: Snapshot, ctx: &AppContext) -> Task<Result<()>> {
|
pub fn save<'a>(&self, ino: u64, content: Snapshot, ctx: &AppContext) -> Task<Result<()>> {
|
||||||
let path = self.abs_entry_path(ino);
|
let path = self.abs_path_for_inode(ino);
|
||||||
eprintln!("save to path: {:?}", path);
|
eprintln!("save to path: {:?}", path);
|
||||||
ctx.background_executor().spawn(async move {
|
ctx.background_executor().spawn(async move {
|
||||||
let buffer_size = content.text_summary().bytes.min(10 * 1024);
|
let buffer_size = content.text_summary().bytes.min(10 * 1024);
|
||||||
|
@ -211,7 +242,7 @@ impl Entity for Worktree {
|
||||||
|
|
||||||
impl fmt::Debug for Worktree {
|
impl fmt::Debug for Worktree {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
if let Some(root_ino) = self.root_ino() {
|
if let Some(root_ino) = self.root_inode() {
|
||||||
self.fmt_entry(f, root_ino, 0)
|
self.fmt_entry(f, root_ino, 0)
|
||||||
} else {
|
} else {
|
||||||
write!(f, "Empty tree\n")
|
write!(f, "Empty tree\n")
|
||||||
|
@ -219,6 +250,28 @@ impl fmt::Debug for Worktree {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FileHandle {
|
||||||
|
pub fn path(&self, ctx: &AppContext) -> PathBuf {
|
||||||
|
self.worktree
|
||||||
|
.read(ctx)
|
||||||
|
.path_for_inode(self.inode, false)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(&self, ctx: &AppContext) -> impl Future<Output = Result<String>> {
|
||||||
|
self.worktree.read(ctx).load_file(self.inode, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save<'a>(&self, content: Snapshot, ctx: &AppContext) -> Task<Result<()>> {
|
||||||
|
let worktree = self.worktree.read(ctx);
|
||||||
|
worktree.save(self.inode, content, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn entry_id(&self) -> (usize, u64) {
|
||||||
|
(self.worktree.id(), self.inode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Entry {
|
pub enum Entry {
|
||||||
Dir {
|
Dir {
|
||||||
|
@ -339,6 +392,14 @@ impl BackgroundScanner {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self) {
|
fn run(&self) {
|
||||||
|
let event_stream = fsevent::EventStream::new(
|
||||||
|
&[self.path.as_ref()],
|
||||||
|
Duration::from_millis(100),
|
||||||
|
|events| {
|
||||||
|
eprintln!("events: {:?}", events);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if smol::block_on(self.notify.send(ScanState::Scanning)).is_err() {
|
if smol::block_on(self.notify.send(ScanState::Scanning)).is_err() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -353,7 +414,7 @@ impl BackgroundScanner {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Update when dir changes
|
event_stream.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scan_dirs(&self) -> io::Result<()> {
|
fn scan_dirs(&self) -> io::Result<()> {
|
||||||
|
@ -588,7 +649,7 @@ mod tests {
|
||||||
ctx.thread_pool().clone(),
|
ctx.thread_pool().clone(),
|
||||||
)
|
)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|result| tree.entry_path(result.entry_id))
|
.map(|result| tree.path_for_inode(result.entry_id, true))
|
||||||
.collect::<Result<Vec<PathBuf>, _>>()
|
.collect::<Result<Vec<PathBuf>, _>>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -632,4 +693,45 @@ mod tests {
|
||||||
assert_eq!(loaded_text, buffer.text());
|
assert_eq!(loaded_text, buffer.text());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rescan() {
|
||||||
|
App::test_async((), |mut app| async move {
|
||||||
|
let dir = temp_tree(json!({
|
||||||
|
"dir1": {
|
||||||
|
"file": "contents"
|
||||||
|
},
|
||||||
|
"dir2": {
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
|
||||||
|
assert_condition(1, 300, || app.read(|ctx| tree.read(ctx).file_count() == 1)).await;
|
||||||
|
|
||||||
|
let file_entry = app.read(|ctx| tree.read(ctx).inode_for_path("dir1/file").unwrap());
|
||||||
|
app.read(|ctx| {
|
||||||
|
let tree = tree.read(ctx);
|
||||||
|
assert_eq!(
|
||||||
|
tree.path_for_inode(file_entry, false)
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"dir1/file"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
std::fs::rename(dir.path().join("dir1/file"), dir.path().join("dir2/file")).unwrap();
|
||||||
|
assert_condition(1, 300, || {
|
||||||
|
app.read(|ctx| {
|
||||||
|
let tree = tree.read(ctx);
|
||||||
|
tree.path_for_inode(file_entry, false)
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
== "dir2/file"
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue