Identify Worktree entries by their inode

This will allow us to re-parent elements when re-scanning when the file system changes.
This commit is contained in:
Nathan Sobo 2021-04-08 16:42:58 -06:00 committed by Nathan Sobo
parent 3c0bbe5eb5
commit 24cdfd2471
8 changed files with 95 additions and 89 deletions

View file

@ -433,7 +433,7 @@ impl Buffer {
self.file.as_ref().map(|file| file.path(app)) self.file.as_ref().map(|file| file.path(app))
} }
pub fn entry_id(&self) -> Option<(usize, usize)> { pub fn entry_id(&self) -> Option<(usize, u64)> {
self.file.as_ref().map(|file| file.entry_id()) self.file.as_ref().map(|file| file.entry_id())
} }

View file

@ -1225,7 +1225,7 @@ impl workspace::ItemView for BufferView {
} }
} }
fn entry_id(&self, app: &AppContext) -> Option<(usize, usize)> { fn entry_id(&self, app: &AppContext) -> Option<(usize, u64)> {
self.buffer.read(app).entry_id() self.buffer.read(app).entry_id()
} }

View file

@ -44,7 +44,7 @@ pub fn init(app: &mut MutableAppContext) {
} }
pub enum Event { pub enum Event {
Selected(usize, usize), Selected(usize, u64),
Dismissed, Dismissed,
} }
@ -339,7 +339,7 @@ impl FileFinder {
} }
} }
fn select(&mut self, entry: &(usize, usize), ctx: &mut ViewContext<Self>) { fn select(&mut self, entry: &(usize, u64), ctx: &mut ViewContext<Self>) {
let (tree_id, entry_id) = *entry; let (tree_id, entry_id) = *entry;
ctx.emit(Event::Selected(tree_id, entry_id)); ctx.emit(Event::Selected(tree_id, entry_id));
} }
@ -347,7 +347,7 @@ impl FileFinder {
fn spawn_search(&mut self, query: String, ctx: &mut ViewContext<Self>) { fn spawn_search(&mut self, query: String, ctx: &mut ViewContext<Self>) {
let worktrees = self.worktrees(ctx.as_ref()); let worktrees = self.worktrees(ctx.as_ref());
let search_id = util::post_inc(&mut self.search_count); let search_id = util::post_inc(&mut self.search_count);
let pool = ctx.app().scoped_pool().clone(); let pool = ctx.as_ref().scoped_pool().clone();
let task = ctx.background_executor().spawn(async move { let task = ctx.background_executor().spawn(async move {
let matches = match_paths(worktrees.as_slice(), &query, false, false, 100, pool); let matches = match_paths(worktrees.as_slice(), &query, false, false, 100, pool);
(search_id, matches) (search_id, matches)

View file

@ -105,15 +105,12 @@ impl Pane {
self.items.get(self.active_item).cloned() self.items.get(self.active_item).cloned()
} }
pub fn activate_entry( pub fn activate_entry(&mut self, entry_id: (usize, u64), ctx: &mut ViewContext<Self>) -> bool {
&mut self, if let Some(index) = self
entry_id: (usize, usize), .items
ctx: &mut ViewContext<Self>, .iter()
) -> bool { .position(|item| item.entry_id(ctx.as_ref()).map_or(false, |id| id == entry_id))
if let Some(index) = self.items.iter().position(|item| { {
item.entry_id(ctx.as_ref())
.map_or(false, |id| id == entry_id)
}) {
self.activate_item(index, ctx); self.activate_item(index, ctx);
true true
} else { } else {

View file

@ -76,7 +76,7 @@ enum OpenedItem {
pub struct Workspace { pub struct Workspace {
replica_id: ReplicaId, replica_id: ReplicaId,
worktrees: HashSet<ModelHandle<Worktree>>, worktrees: HashSet<ModelHandle<Worktree>>,
items: HashMap<(usize, usize), OpenedItem>, items: HashMap<(usize, u64), OpenedItem>,
} }
impl Workspace { impl Workspace {
@ -125,7 +125,7 @@ impl Workspace {
pub fn open_entry( pub fn open_entry(
&mut self, &mut self,
entry: (usize, usize), entry: (usize, u64),
ctx: &mut ModelContext<'_, Self>, ctx: &mut ModelContext<'_, Self>,
) -> anyhow::Result<Pin<Box<dyn Future<Output = OpenResult> + Send>>> { ) -> anyhow::Result<Pin<Box<dyn Future<Output = OpenResult> + Send>>> {
if let Some(item) = self.items.get(&entry).cloned() { if let Some(item) = self.items.get(&entry).cloned() {
@ -200,12 +200,12 @@ impl Entity for Workspace {
#[cfg(test)] #[cfg(test)]
pub trait WorkspaceHandle { pub trait WorkspaceHandle {
fn file_entries(&self, app: &AppContext) -> Vec<(usize, usize)>; fn file_entries(&self, app: &AppContext) -> Vec<(usize, u64)>;
} }
#[cfg(test)] #[cfg(test)]
impl WorkspaceHandle for ModelHandle<Workspace> { impl WorkspaceHandle for ModelHandle<Workspace> {
fn file_entries(&self, app: &AppContext) -> Vec<(usize, usize)> { fn file_entries(&self, app: &AppContext) -> Vec<(usize, u64)> {
self.read(app) self.read(app)
.worktrees() .worktrees()
.iter() .iter()

View file

@ -19,7 +19,7 @@ pub fn init(app: &mut MutableAppContext) {
pub trait ItemView: View { pub trait ItemView: View {
fn title(&self, app: &AppContext) -> String; fn title(&self, app: &AppContext) -> String;
fn entry_id(&self, app: &AppContext) -> Option<(usize, usize)>; fn entry_id(&self, app: &AppContext) -> Option<(usize, u64)>;
fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self> fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
where where
Self: Sized, Self: Sized,
@ -42,7 +42,7 @@ pub trait ItemView: View {
pub trait ItemViewHandle: Send + Sync { pub trait ItemViewHandle: Send + Sync {
fn title(&self, app: &AppContext) -> String; fn title(&self, app: &AppContext) -> String;
fn entry_id(&self, app: &AppContext) -> Option<(usize, usize)>; fn entry_id(&self, app: &AppContext) -> Option<(usize, u64)>;
fn boxed_clone(&self) -> Box<dyn ItemViewHandle>; fn boxed_clone(&self) -> Box<dyn ItemViewHandle>;
fn clone_on_split(&self, app: &mut MutableAppContext) -> Option<Box<dyn ItemViewHandle>>; fn clone_on_split(&self, app: &mut MutableAppContext) -> Option<Box<dyn ItemViewHandle>>;
fn set_parent_pane(&self, pane: &ViewHandle<Pane>, app: &mut MutableAppContext); fn set_parent_pane(&self, pane: &ViewHandle<Pane>, app: &mut MutableAppContext);
@ -57,7 +57,7 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
self.read(app).title(app) self.read(app).title(app)
} }
fn entry_id(&self, app: &AppContext) -> Option<(usize, usize)> { fn entry_id(&self, app: &AppContext) -> Option<(usize, u64)> {
self.read(app).entry_id(app) self.read(app).entry_id(app)
} }
@ -124,7 +124,7 @@ pub struct WorkspaceView {
center: PaneGroup, center: PaneGroup,
panes: Vec<ViewHandle<Pane>>, panes: Vec<ViewHandle<Pane>>,
active_pane: ViewHandle<Pane>, active_pane: ViewHandle<Pane>,
loading_entries: HashSet<(usize, usize)>, loading_entries: HashSet<(usize, u64)>,
} }
impl WorkspaceView { impl WorkspaceView {
@ -189,7 +189,7 @@ impl WorkspaceView {
} }
} }
pub fn open_entry(&mut self, entry: (usize, usize), ctx: &mut ViewContext<Self>) { pub fn open_entry(&mut self, entry: (usize, u64), ctx: &mut ViewContext<Self>) {
if self.loading_entries.contains(&entry) { if self.loading_entries.contains(&entry) {
return; return;
} }

View file

@ -12,7 +12,7 @@ const ADDITIONAL_DISTANCE_PENALTY: f64 = 0.05;
const MIN_DISTANCE_PENALTY: f64 = 0.2; const MIN_DISTANCE_PENALTY: f64 = 0.2;
pub struct PathEntry { pub struct PathEntry {
pub entry_id: usize, pub ino: u64,
pub path_chars: CharBag, pub path_chars: CharBag,
pub path: Vec<char>, pub path: Vec<char>,
pub lowercase_path: Vec<char>, pub lowercase_path: Vec<char>,
@ -24,7 +24,7 @@ pub struct PathMatch {
pub score: f64, pub score: f64,
pub positions: Vec<usize>, pub positions: Vec<usize>,
pub tree_id: usize, pub tree_id: usize,
pub entry_id: usize, pub entry_id: u64,
pub skipped_prefix_len: usize, pub skipped_prefix_len: usize,
} }
@ -191,7 +191,7 @@ fn match_single_tree_paths(
if score > 0.0 { if score > 0.0 {
results.push(Reverse(PathMatch { results.push(Reverse(PathMatch {
tree_id, tree_id,
entry_id: path_entry.entry_id, entry_id: path_entry.ino,
score, score,
positions: match_positions.clone(), positions: match_positions.clone(),
skipped_prefix_len, skipped_prefix_len,
@ -453,7 +453,7 @@ mod tests {
let path_chars = CharBag::from(&lowercase_path[..]); let path_chars = CharBag::from(&lowercase_path[..]);
let path = path.chars().collect(); let path = path.chars().collect();
path_entries.push(PathEntry { path_entries.push(PathEntry {
entry_id: i, ino: i as u64,
path_chars, path_chars,
path, path,
lowercase_path, lowercase_path,
@ -490,7 +490,12 @@ mod tests {
results results
.into_iter() .into_iter()
.rev() .rev()
.map(|result| (paths[result.0.entry_id].clone(), result.0.positions)) .map(|result| {
(
paths[result.0.entry_id as usize].clone(),
result.0.positions,
)
})
.collect() .collect()
} }
} }

View file

@ -33,15 +33,15 @@ pub struct Worktree(Arc<RwLock<WorktreeState>>);
struct WorktreeState { struct WorktreeState {
id: usize, id: usize,
path: PathBuf, path: PathBuf,
root_ino: Option<usize>, root_ino: Option<u64>,
entries: HashMap<usize, Entry>, entries: HashMap<u64, Entry>,
file_paths: Vec<PathEntry>, file_paths: Vec<PathEntry>,
histories: HashMap<usize, History>, histories: HashMap<u64, History>,
scanning: bool, scanning: bool,
} }
struct DirToScan { struct DirToScan {
id: usize, ino: u64,
path: PathBuf, path: PathBuf,
relative_path: PathBuf, relative_path: PathBuf,
ignore: Option<Ignore>, ignore: Option<Ignore>,
@ -102,14 +102,13 @@ impl Worktree {
} }
let is_ignored = ignore.matched(&path, metadata.is_dir()).is_ignore(); let is_ignored = ignore.matched(&path, metadata.is_dir()).is_ignore();
self.0.write().root_ino = Some(0);
if metadata.file_type().is_dir() { if metadata.file_type().is_dir() {
let is_ignored = is_ignored || name == ".git"; let is_ignored = is_ignored || name == ".git";
let id = self.insert_dir(None, name, ino, is_symlink, is_ignored); self.insert_dir(None, name, ino, is_symlink, is_ignored);
let (tx, rx) = channel::unbounded(); let (tx, rx) = channel::unbounded();
tx.send(Ok(DirToScan { tx.send(Ok(DirToScan {
id, ino,
path, path,
relative_path, relative_path,
ignore: Some(ignore), ignore: Some(ignore),
@ -131,6 +130,7 @@ impl Worktree {
} else { } else {
self.insert_file(None, name, ino, is_symlink, is_ignored, relative_path); self.insert_file(None, name, ino, is_symlink, is_ignored, relative_path);
} }
self.0.write().root_ino = Some(ino);
Ok(()) Ok(())
} }
@ -159,12 +159,12 @@ impl Worktree {
} }
} }
let id = self.insert_dir(Some(to_scan.id), name, ino, is_symlink, is_ignored); self.insert_dir(Some(to_scan.ino), name, ino, is_symlink, is_ignored);
new_children.push(id); new_children.push(ino);
let dirs_to_scan = to_scan.dirs_to_scan.clone(); let dirs_to_scan = to_scan.dirs_to_scan.clone();
let _ = to_scan.dirs_to_scan.send(Ok(DirToScan { let _ = to_scan.dirs_to_scan.send(Ok(DirToScan {
id, ino,
path, path,
relative_path, relative_path,
ignore, ignore,
@ -175,18 +175,19 @@ impl Worktree {
i.matched(to_scan.path.join(&name), false).is_ignore() i.matched(to_scan.path.join(&name), false).is_ignore()
}); });
new_children.push(self.insert_file( self.insert_file(
Some(to_scan.id), Some(to_scan.ino),
name, name,
ino, ino,
is_symlink, is_symlink,
is_ignored, is_ignored,
relative_path, relative_path,
)); );
new_children.push(ino);
}; };
} }
if let Some(Entry::Dir { children, .. }) = &mut self.0.write().entries.get_mut(&to_scan.id) if let Some(Entry::Dir { children, .. }) = &mut self.0.write().entries.get_mut(&to_scan.ino)
{ {
*children = new_children.clone(); *children = new_children.clone();
} }
@ -196,16 +197,15 @@ impl Worktree {
fn insert_dir( fn insert_dir(
&self, &self,
parent: Option<usize>, parent: Option<u64>,
name: OsString, name: OsString,
ino: u64, ino: u64,
is_symlink: bool, is_symlink: bool,
is_ignored: bool, is_ignored: bool,
) -> usize { ) {
let entries = &mut self.0.write().entries; let entries = &mut self.0.write().entries;
let dir_id = entries.len();
entries.insert( entries.insert(
dir_id, ino,
Entry::Dir { Entry::Dir {
parent, parent,
name, name,
@ -215,27 +215,25 @@ impl Worktree {
children: Vec::new(), children: Vec::new(),
}, },
); );
dir_id
} }
fn insert_file( fn insert_file(
&self, &self,
parent: Option<usize>, parent: Option<u64>,
name: OsString, name: OsString,
ino: u64, ino: u64,
is_symlink: bool, is_symlink: bool,
is_ignored: bool, is_ignored: bool,
path: PathBuf, path: PathBuf,
) -> usize { ) {
let path = path.to_string_lossy(); let path = path.to_string_lossy();
let lowercase_path = path.to_lowercase().chars().collect::<Vec<_>>(); let lowercase_path = path.to_lowercase().chars().collect::<Vec<_>>();
let path = path.chars().collect::<Vec<_>>(); let path = path.chars().collect::<Vec<_>>();
let path_chars = CharBag::from(&path[..]); let path_chars = CharBag::from(&path[..]);
let mut state = self.0.write(); let mut state = self.0.write();
let entry_id = state.entries.len();
state.entries.insert( state.entries.insert(
entry_id, ino,
Entry::File { Entry::File {
parent, parent,
name, name,
@ -245,25 +243,23 @@ impl Worktree {
}, },
); );
state.file_paths.push(PathEntry { state.file_paths.push(PathEntry {
entry_id, ino,
path_chars, path_chars,
path, path,
lowercase_path, lowercase_path,
is_ignored, is_ignored,
}); });
entry_id
} }
pub fn entry_path(&self, mut entry_id: usize) -> Result<PathBuf> { pub fn entry_path(&self, mut entry_id: u64) -> Result<PathBuf> {
let state = self.0.read(); let state = self.0.read();
if entry_id >= state.entries.len() {
return Err(anyhow!("Entry does not exist in tree"));
}
let mut entries = Vec::new(); let mut entries = Vec::new();
loop { loop {
let entry = &state.entries[&entry_id]; let entry = state
.entries
.get(&entry_id)
.ok_or_else(|| anyhow!("entry does not exist in worktree"))?;
entries.push(entry); entries.push(entry);
if let Some(parent_id) = entry.parent() { if let Some(parent_id) = entry.parent() {
entry_id = parent_id; entry_id = parent_id;
@ -279,13 +275,13 @@ impl Worktree {
Ok(path) Ok(path)
} }
pub fn abs_entry_path(&self, entry_id: usize) -> Result<PathBuf> { pub fn abs_entry_path(&self, entry_id: u64) -> Result<PathBuf> {
let mut path = self.0.read().path.clone(); let mut path = self.0.read().path.clone();
path.pop(); path.pop();
Ok(path.join(self.entry_path(entry_id)?)) Ok(path.join(self.entry_path(entry_id)?))
} }
fn fmt_entry(&self, f: &mut fmt::Formatter<'_>, entry_id: usize, indent: usize) -> fmt::Result { fn fmt_entry(&self, f: &mut fmt::Formatter<'_>, entry_id: u64, indent: usize) -> fmt::Result {
match &self.0.read().entries[&entry_id] { match &self.0.read().entries[&entry_id] {
Entry::Dir { name, children, .. } => { Entry::Dir { name, children, .. } => {
write!( write!(
@ -333,6 +329,10 @@ impl Worktree {
} }
} }
pub fn has_entry(&self, entry_id: u64) -> bool {
self.0.read().entries.contains_key(&entry_id)
}
pub fn entry_count(&self) -> usize { pub fn entry_count(&self) -> usize {
self.0.read().entries.len() self.0.read().entries.len()
} }
@ -341,7 +341,7 @@ impl Worktree {
self.0.read().file_paths.len() self.0.read().file_paths.len()
} }
pub fn load_history(&self, entry_id: usize) -> impl Future<Output = Result<History>> { pub fn load_history(&self, entry_id: u64) -> impl Future<Output = Result<History>> {
let tree = self.clone(); let tree = self.clone();
async move { async move {
@ -360,12 +360,7 @@ impl Worktree {
} }
} }
pub fn save<'a>( pub fn save<'a>(&self, entry_id: u64, content: Snapshot, ctx: &AppContext) -> Task<Result<()>> {
&self,
entry_id: usize,
content: Snapshot,
ctx: &AppContext,
) -> Task<Result<()>> {
let path = self.abs_entry_path(entry_id); let path = self.abs_entry_path(entry_id);
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);
@ -420,34 +415,34 @@ impl WorktreeState {
} }
pub trait WorktreeHandle { pub trait WorktreeHandle {
fn file(&self, entry_id: usize, app: &AppContext) -> Result<FileHandle>; fn file(&self, entry_id: u64, app: &AppContext) -> Result<FileHandle>;
} }
impl WorktreeHandle for ModelHandle<Worktree> { impl WorktreeHandle for ModelHandle<Worktree> {
fn file(&self, entry_id: usize, app: &AppContext) -> Result<FileHandle> { fn file(&self, entry_id: u64, app: &AppContext) -> Result<FileHandle> {
if entry_id >= self.read(app).entry_count() { if self.read(app).has_entry(entry_id) {
return Err(anyhow!("Entry does not exist in tree")); Err(anyhow!("entry does not exist in tree"))
} else {
Ok(FileHandle {
worktree: self.clone(),
entry_id,
})
} }
Ok(FileHandle {
worktree: self.clone(),
entry_id,
})
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Entry { pub enum Entry {
Dir { Dir {
parent: Option<usize>, parent: Option<u64>,
name: OsString, name: OsString,
ino: u64, ino: u64,
is_symlink: bool, is_symlink: bool,
is_ignored: bool, is_ignored: bool,
children: Vec<usize>, children: Vec<u64>,
}, },
File { File {
parent: Option<usize>, parent: Option<u64>,
name: OsString, name: OsString,
ino: u64, ino: u64,
is_symlink: bool, is_symlink: bool,
@ -456,12 +451,18 @@ pub enum Entry {
} }
impl Entry { impl Entry {
fn parent(&self) -> Option<usize> { fn parent(&self) -> Option<u64> {
match self { match self {
Entry::Dir { parent, .. } | Entry::File { parent, .. } => *parent, Entry::Dir { parent, .. } | Entry::File { parent, .. } => *parent,
} }
} }
fn ino(&self) -> u64 {
match self {
Entry::Dir { ino, .. } | Entry::File { ino, .. } => *ino,
}
}
fn name(&self) -> &OsStr { fn name(&self) -> &OsStr {
match self { match self {
Entry::Dir { name, .. } | Entry::File { name, .. } => name, Entry::Dir { name, .. } | Entry::File { name, .. } => name,
@ -472,7 +473,7 @@ impl Entry {
#[derive(Clone)] #[derive(Clone)]
pub struct FileHandle { pub struct FileHandle {
worktree: ModelHandle<Worktree>, worktree: ModelHandle<Worktree>,
entry_id: usize, entry_id: u64,
} }
impl FileHandle { impl FileHandle {
@ -489,13 +490,13 @@ impl FileHandle {
worktree.save(self.entry_id, content, ctx) worktree.save(self.entry_id, content, ctx)
} }
pub fn entry_id(&self) -> (usize, usize) { pub fn entry_id(&self) -> (usize, u64) {
(self.worktree.id(), self.entry_id) (self.worktree.id(), self.entry_id)
} }
} }
struct IterStackEntry { struct IterStackEntry {
entry_id: usize, entry_id: u64,
child_idx: usize, child_idx: usize,
} }
@ -516,18 +517,21 @@ impl Iterator for Iter {
return if let Some(entry) = state.root_entry().cloned() { return if let Some(entry) = state.root_entry().cloned() {
self.stack.push(IterStackEntry { self.stack.push(IterStackEntry {
entry_id: 0, entry_id: entry.ino(),
child_idx: 0, child_idx: 0,
}); });
Some(Traversal::Push { entry_id: 0, entry }) Some(Traversal::Push {
entry_id: entry.ino(),
entry,
})
} else { } else {
None None
}; };
} }
while let Some(parent) = self.stack.last_mut() { while let Some(parent) = self.stack.last_mut() {
if let Entry::Dir { children, .. } = &state.entries[&parent.entry_id] { if let Some(Entry::Dir { children, .. }) = &state.entries.get(&parent.entry_id) {
if parent.child_idx < children.len() { if parent.child_idx < children.len() {
let child_id = children[post_inc(&mut parent.child_idx)]; let child_id = children[post_inc(&mut parent.child_idx)];
@ -558,7 +562,7 @@ impl Iterator for Iter {
#[derive(Debug)] #[derive(Debug)]
pub enum Traversal { pub enum Traversal {
Push { entry_id: usize, entry: Entry }, Push { entry_id: u64, entry: Entry },
Pop, Pop,
} }
@ -568,7 +572,7 @@ pub struct FilesIter {
} }
pub struct FilesIterItem { pub struct FilesIterItem {
pub entry_id: usize, pub entry_id: u64,
pub path: PathBuf, pub path: PathBuf,
} }