mirror of
https://github.com/zed-industries/zed.git
synced 2024-10-28 01:07:09 +00:00
WIP: Associate entry names with directory children
Co-Authored-By: Max Brunsfeld <max@zed.dev>
This commit is contained in:
parent
f8f6a85ab0
commit
122926dcde
2 changed files with 85 additions and 207 deletions
|
@ -12,7 +12,7 @@ use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, Task};
|
|||
use ignore::dir::{Ignore, IgnoreBuilder};
|
||||
use parking_lot::Mutex;
|
||||
use smol::{channel::Sender, Timer};
|
||||
use std::{collections::HashSet, future::Future};
|
||||
use std::future::Future;
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
fmt, fs,
|
||||
|
@ -204,15 +204,18 @@ impl Snapshot {
|
|||
self.root_inode.and_then(|inode| self.entries.get(&inode))
|
||||
}
|
||||
|
||||
pub fn root_name(&self) -> Option<&OsStr> {
|
||||
self.path.file_name()
|
||||
}
|
||||
|
||||
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;
|
||||
for (child_inode, name) in children.as_ref() {
|
||||
if name.as_ref() == path_component {
|
||||
inode = *child_inode;
|
||||
continue 'components;
|
||||
}
|
||||
}
|
||||
|
@ -228,183 +231,91 @@ impl Snapshot {
|
|||
.and_then(|inode| self.entries.get(&inode))
|
||||
}
|
||||
|
||||
pub fn path_for_inode(&self, ino: u64, include_root: bool) -> Result<PathBuf> {
|
||||
pub fn path_for_inode(&self, mut ino: u64, include_root: bool) -> Result<PathBuf> {
|
||||
let mut components = Vec::new();
|
||||
let mut entry = self
|
||||
.entries
|
||||
.get(&ino)
|
||||
.ok_or_else(|| anyhow!("entry does not exist in worktree"))?;
|
||||
components.push(entry.name());
|
||||
while let Some(parent) = entry.parent() {
|
||||
entry = self.entries.get(&parent).unwrap();
|
||||
components.push(entry.name());
|
||||
if let Entry::Dir { children, .. } = entry {
|
||||
let (_, child_name) = children
|
||||
.iter()
|
||||
.find(|(child_inode, _)| *child_inode == ino)
|
||||
.unwrap();
|
||||
components.push(child_name.as_ref());
|
||||
ino = parent;
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
if include_root {
|
||||
components.push(self.path.file_name().unwrap());
|
||||
}
|
||||
|
||||
let mut components = components.into_iter().rev();
|
||||
if !include_root {
|
||||
components.next();
|
||||
}
|
||||
|
||||
let mut path = PathBuf::new();
|
||||
for component in components {
|
||||
path.push(component);
|
||||
}
|
||||
Ok(path)
|
||||
Ok(components.into_iter().rev().collect())
|
||||
}
|
||||
|
||||
fn remove_entry(&mut self, inode: u64) {
|
||||
if let Some(entry) = self.entries.get(&inode).cloned() {
|
||||
fn remove_path(&mut self, path: &Path) {
|
||||
if let Some(parent_path) = path.parent() {
|
||||
let mut edits = Vec::new();
|
||||
|
||||
if let Some(parent) = entry.parent() {
|
||||
let mut parent_entry = self.entries.get(&parent).unwrap().clone();
|
||||
if let Entry::Dir { children, .. } = &mut parent_entry {
|
||||
*children = children
|
||||
.into_iter()
|
||||
.copied()
|
||||
.filter(|child| *child != entry.inode())
|
||||
.collect::<Vec<_>>()
|
||||
.into();
|
||||
edits.push(Edit::Insert(parent_entry));
|
||||
} else {
|
||||
unreachable!();
|
||||
let mut parent_entry = self.entry_for_path(parent_path).unwrap().clone();
|
||||
let parent_inode = parent_entry.inode();
|
||||
let mut entry_inode = None;
|
||||
if let Entry::Dir { children, .. } = &mut parent_entry {
|
||||
let mut new_children = Vec::new();
|
||||
for (child_inode, child_name) in children.as_ref() {
|
||||
if Some(child_name.as_ref()) == path.file_name() {
|
||||
entry_inode = Some(*child_inode);
|
||||
} else {
|
||||
new_children.push((*child_inode, child_name.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
if new_children.iter().any(|c| Some(c.0) == entry_inode) {
|
||||
entry_inode = None;
|
||||
}
|
||||
|
||||
dbg!(&children, &new_children, entry_inode, parent_inode);
|
||||
*children = new_children.into();
|
||||
edits.push(Edit::Insert(parent_entry));
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
// Recursively remove the orphaned nodes' descendants.
|
||||
let mut descendant_stack = vec![entry.inode()];
|
||||
while let Some(inode) = descendant_stack.pop() {
|
||||
if let Some(entry) = self.entries.get(&inode) {
|
||||
edits.push(Edit::Remove(inode));
|
||||
if let Entry::Dir { children, .. } = entry {
|
||||
descendant_stack.extend_from_slice(children.as_ref());
|
||||
if let Some(entry_inode) = entry_inode {
|
||||
let entry = self.entries.get(&entry_inode).unwrap();
|
||||
|
||||
// Recursively remove the orphaned nodes' descendants.
|
||||
let mut descendant_stack = Vec::new();
|
||||
if entry.parent() == Some(parent_inode) {
|
||||
descendant_stack.push(entry_inode);
|
||||
while let Some(inode) = descendant_stack.pop() {
|
||||
if let Some(entry) = self.entries.get(&inode) {
|
||||
edits.push(Edit::Remove(inode));
|
||||
if let Entry::Dir { children, .. } = entry {
|
||||
descendant_stack.extend(children.iter().map(|c| c.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dbg!(&edits);
|
||||
self.entries.edit(edits);
|
||||
}
|
||||
}
|
||||
|
||||
fn move_entry(
|
||||
&mut self,
|
||||
child_inode: u64,
|
||||
new_path: Option<&Path>,
|
||||
old_parent_inode: Option<u64>,
|
||||
new_parent_inode: Option<u64>,
|
||||
) {
|
||||
let mut edits_len = 1;
|
||||
if old_parent_inode.is_some() {
|
||||
edits_len += 1;
|
||||
}
|
||||
if new_parent_inode.is_some() {
|
||||
edits_len += 1;
|
||||
}
|
||||
let mut deletions = Vec::with_capacity(edits_len);
|
||||
let mut insertions = Vec::with_capacity(edits_len);
|
||||
|
||||
// Remove the entries from the sum tree.
|
||||
deletions.push(Edit::Remove(child_inode));
|
||||
if old_parent_inode != new_parent_inode {
|
||||
if let Some(old_parent_inode) = old_parent_inode {
|
||||
deletions.push(Edit::Remove(old_parent_inode));
|
||||
}
|
||||
if let Some(new_parent_inode) = new_parent_inode {
|
||||
deletions.push(Edit::Remove(new_parent_inode));
|
||||
}
|
||||
}
|
||||
let removed_entries = self.entries.edit(deletions);
|
||||
let mut child_entry = None;
|
||||
let mut old_parent_entry = None;
|
||||
let mut new_parent_entry = None;
|
||||
for removed_entry in removed_entries {
|
||||
if removed_entry.inode() == child_inode {
|
||||
child_entry = Some(removed_entry);
|
||||
} else if Some(removed_entry.inode()) == old_parent_inode {
|
||||
old_parent_entry = Some(removed_entry);
|
||||
} else if Some(removed_entry.inode()) == new_parent_inode {
|
||||
new_parent_entry = Some(removed_entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the child entry's parent.
|
||||
let mut child_entry = child_entry.expect("cannot move non-existent entry");
|
||||
child_entry.set_parent(new_parent_inode);
|
||||
if let Some(new_path) = new_path {
|
||||
let new_path = new_path.strip_prefix(self.path.parent().unwrap()).unwrap();
|
||||
child_entry.set_name(new_path.file_name().unwrap());
|
||||
|
||||
// Recompute the PathEntry for each file under this subtree.
|
||||
let mut stack = Vec::new();
|
||||
stack.push((child_entry, new_path.parent().unwrap().to_path_buf()));
|
||||
while let Some((mut entry, mut new_path)) = stack.pop() {
|
||||
new_path.push(entry.name());
|
||||
match &mut entry {
|
||||
Entry::Dir {
|
||||
children, inode, ..
|
||||
} => {
|
||||
for child_inode in children.as_ref() {
|
||||
let child_entry = self.entries.get(child_inode).unwrap();
|
||||
stack.push((child_entry.clone(), new_path.clone()));
|
||||
}
|
||||
|
||||
// Descendant directories don't need to be mutated because their properties
|
||||
// haven't changed, so only re-insert this directory if it is the top entry
|
||||
// we were moving.
|
||||
if *inode == child_inode {
|
||||
insertions.push(Edit::Insert(entry));
|
||||
}
|
||||
}
|
||||
Entry::File {
|
||||
inode,
|
||||
is_ignored,
|
||||
path,
|
||||
..
|
||||
} => {
|
||||
*path = PathEntry::new(*inode, &new_path, *is_ignored);
|
||||
insertions.push(Edit::Insert(entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
insertions.push(Edit::Insert(child_entry));
|
||||
}
|
||||
|
||||
// Remove the child entry from its old parent's children.
|
||||
if let Some(mut old_parent_entry) = old_parent_entry {
|
||||
if let Entry::Dir { children, .. } = &mut old_parent_entry {
|
||||
*children = children
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.filter(|c| *c != child_inode)
|
||||
.collect();
|
||||
insertions.push(Edit::Insert(old_parent_entry));
|
||||
} else {
|
||||
panic!("snapshot entry's new parent was not a directory");
|
||||
}
|
||||
}
|
||||
|
||||
// Add the child entry to its new parent's children.
|
||||
if let Some(mut new_parent_entry) = new_parent_entry {
|
||||
if let Entry::Dir { children, .. } = &mut new_parent_entry {
|
||||
*children = children
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.chain(Some(child_inode))
|
||||
.collect();
|
||||
insertions.push(Edit::Insert(new_parent_entry));
|
||||
} else {
|
||||
panic!("snapshot entry's new parent is not a directory");
|
||||
}
|
||||
}
|
||||
|
||||
self.entries.edit(insertions);
|
||||
}
|
||||
|
||||
fn fmt_entry(&self, f: &mut fmt::Formatter<'_>, ino: u64, indent: usize) -> fmt::Result {
|
||||
fn fmt_entry(
|
||||
&self,
|
||||
f: &mut fmt::Formatter<'_>,
|
||||
ino: u64,
|
||||
name: &OsStr,
|
||||
indent: usize,
|
||||
) -> fmt::Result {
|
||||
match self.entries.get(&ino).unwrap() {
|
||||
Entry::Dir { name, children, .. } => {
|
||||
Entry::Dir { children, .. } => {
|
||||
write!(
|
||||
f,
|
||||
"{}{}/ ({})\n",
|
||||
|
@ -412,12 +323,12 @@ impl Snapshot {
|
|||
name.to_string_lossy(),
|
||||
ino
|
||||
)?;
|
||||
for child_id in children.iter() {
|
||||
self.fmt_entry(f, *child_id, indent + 2)?;
|
||||
for (child_inode, child_name) in children.iter() {
|
||||
self.fmt_entry(f, *child_inode, child_name, indent + 2)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Entry::File { name, .. } => write!(
|
||||
Entry::File { .. } => write!(
|
||||
f,
|
||||
"{}{} ({})\n",
|
||||
" ".repeat(indent),
|
||||
|
@ -431,7 +342,7 @@ impl Snapshot {
|
|||
impl fmt::Debug for Snapshot {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if let Some(root_ino) = self.root_inode {
|
||||
self.fmt_entry(f, root_ino, 0)
|
||||
self.fmt_entry(f, root_ino, self.path.file_name().unwrap(), 0)
|
||||
} else {
|
||||
write!(f, "Empty tree\n")
|
||||
}
|
||||
|
@ -464,16 +375,14 @@ impl FileHandle {
|
|||
pub enum Entry {
|
||||
Dir {
|
||||
parent: Option<u64>,
|
||||
name: Arc<OsStr>,
|
||||
inode: u64,
|
||||
is_symlink: bool,
|
||||
is_ignored: bool,
|
||||
children: Arc<[u64]>,
|
||||
children: Arc<[(u64, Arc<OsStr>)]>,
|
||||
pending: bool,
|
||||
},
|
||||
File {
|
||||
parent: Option<u64>,
|
||||
name: Arc<OsStr>,
|
||||
path: PathEntry,
|
||||
inode: u64,
|
||||
is_symlink: bool,
|
||||
|
@ -499,27 +408,6 @@ impl Entry {
|
|||
Entry::File { parent, .. } => *parent,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_parent(&mut self, new_parent: Option<u64>) {
|
||||
match self {
|
||||
Entry::Dir { parent, .. } => *parent = new_parent,
|
||||
Entry::File { parent, .. } => *parent = new_parent,
|
||||
}
|
||||
}
|
||||
|
||||
fn name(&self) -> &OsStr {
|
||||
match self {
|
||||
Entry::Dir { name, .. } => name,
|
||||
Entry::File { name, .. } => name,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_name(&mut self, new_name: &OsStr) {
|
||||
match self {
|
||||
Entry::Dir { name, .. } => *name = new_name.into(),
|
||||
Entry::File { name, .. } => *name = new_name.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::Item for Entry {
|
||||
|
@ -660,7 +548,6 @@ impl BackgroundScanner {
|
|||
let is_ignored = is_ignored || name.as_ref() == ".git";
|
||||
let dir_entry = Entry::Dir {
|
||||
parent: None,
|
||||
name,
|
||||
inode,
|
||||
is_symlink,
|
||||
is_ignored,
|
||||
|
@ -702,7 +589,6 @@ impl BackgroundScanner {
|
|||
} else {
|
||||
self.insert_entries(Some(Entry::File {
|
||||
parent: None,
|
||||
name,
|
||||
path: PathEntry::new(inode, &relative_path, is_ignored),
|
||||
inode,
|
||||
is_symlink,
|
||||
|
@ -731,7 +617,7 @@ impl BackgroundScanner {
|
|||
let is_symlink = metadata.file_type().is_symlink();
|
||||
let path = job.path.join(name.as_ref());
|
||||
|
||||
new_children.push(ino);
|
||||
new_children.push((ino, name.clone()));
|
||||
if metadata.is_dir() {
|
||||
let mut is_ignored = true;
|
||||
let mut ignore = None;
|
||||
|
@ -747,7 +633,6 @@ impl BackgroundScanner {
|
|||
|
||||
let dir_entry = Entry::Dir {
|
||||
parent: Some(job.inode),
|
||||
name,
|
||||
inode: ino,
|
||||
is_symlink,
|
||||
is_ignored,
|
||||
|
@ -770,7 +655,6 @@ impl BackgroundScanner {
|
|||
.map_or(true, |i| i.matched(&path, false).is_ignore());
|
||||
new_entries.push(Entry::File {
|
||||
parent: Some(job.inode),
|
||||
name,
|
||||
path: PathEntry::new(ino, &relative_path, is_ignored),
|
||||
inode: ino,
|
||||
is_symlink,
|
||||
|
@ -818,24 +702,22 @@ impl BackgroundScanner {
|
|||
paths.next();
|
||||
}
|
||||
|
||||
if let Some(snapshot_inode) = snapshot.inode_for_path(&relative_path) {
|
||||
snapshot.remove_entry(snapshot_inode);
|
||||
}
|
||||
snapshot.remove_path(&relative_path);
|
||||
|
||||
match self.fs_entry_for_path(&snapshot.path, &path) {
|
||||
Ok(Some((fs_entry, ignore))) => {
|
||||
snapshot.remove_entry(fs_entry.inode());
|
||||
|
||||
// snapshot.remove_entry(fs_entry.inode());
|
||||
let mut edits = Vec::new();
|
||||
edits.push(Edit::Insert(fs_entry.clone()));
|
||||
if let Some(parent) = fs_entry.parent() {
|
||||
let mut parent_entry = snapshot.entries.get(&parent).unwrap().clone();
|
||||
if let Entry::Dir { children, .. } = &mut parent_entry {
|
||||
if !children.contains(&fs_entry.inode()) {
|
||||
if !children.iter().any(|c| c.0 == fs_entry.inode()) {
|
||||
let name = Arc::from(path.file_name().unwrap());
|
||||
*children = children
|
||||
.into_iter()
|
||||
.copied()
|
||||
.chain(Some(fs_entry.inode()))
|
||||
.cloned()
|
||||
.chain(Some((fs_entry.inode(), name)))
|
||||
.collect::<Vec<_>>()
|
||||
.into();
|
||||
edits.push(Edit::Insert(parent_entry));
|
||||
|
@ -899,7 +781,6 @@ impl BackgroundScanner {
|
|||
let is_ignored = ignore.matched(&path, metadata.is_dir()).is_ignore();
|
||||
|
||||
let inode = metadata.ino();
|
||||
let name: Arc<OsStr> = Arc::from(path.file_name().unwrap_or(OsStr::new("/")));
|
||||
let is_symlink = fs::symlink_metadata(&path)?.file_type().is_symlink();
|
||||
let parent = if path == root_path {
|
||||
None
|
||||
|
@ -910,7 +791,6 @@ impl BackgroundScanner {
|
|||
Ok(Some((
|
||||
Entry::Dir {
|
||||
parent,
|
||||
name,
|
||||
inode,
|
||||
is_symlink,
|
||||
is_ignored,
|
||||
|
@ -923,7 +803,6 @@ impl BackgroundScanner {
|
|||
Ok(Some((
|
||||
Entry::File {
|
||||
parent,
|
||||
name,
|
||||
path: PathEntry::new(
|
||||
inode,
|
||||
root_path
|
||||
|
@ -1349,7 +1228,7 @@ mod tests {
|
|||
let computed_path = self.path_for_inode(inode, true).unwrap();
|
||||
match self.entries.get(&inode).unwrap() {
|
||||
Entry::Dir { children, .. } => {
|
||||
stack.extend_from_slice(children);
|
||||
stack.extend(children.iter().map(|c| c.0));
|
||||
}
|
||||
Entry::File { path, .. } => {
|
||||
assert_eq!(
|
||||
|
|
|
@ -128,12 +128,11 @@ where
|
|||
|
||||
let skipped_prefix_len = if include_root_name {
|
||||
0
|
||||
} else if let Some(Entry::Dir { name, .. }) = snapshot.root_entry() {
|
||||
let name = name.to_string_lossy();
|
||||
if name == "/" {
|
||||
1
|
||||
} else if let Some(Entry::Dir { .. }) = snapshot.root_entry() {
|
||||
if let Some(name) = snapshot.root_name() {
|
||||
name.to_string_lossy().chars().count() + 1
|
||||
} else {
|
||||
name.chars().count() + 1
|
||||
1
|
||||
}
|
||||
} else {
|
||||
0
|
||||
|
|
Loading…
Reference in a new issue