Fix excluded file creation (#12620)
Fixes https://github.com/zed-industries/zed/issues/10890 * removes `unwrap()` that caused panics for text elements with no text, remaining after edit state is cleared but project entries are not updated, having the fake, "new entry" * improves discoverability of the FS errors during file/directory creation: now those are shown as workspace notifications * stops printing anyhow backtraces in workspace notifications, printing the more readable chain of contexts instead * better indicates when new entries are created as excluded ones Release Notes: - Improve excluded entry creation workflow in the project panel ([10890](https://github.com/zed-industries/zed/issues/10890))
This commit is contained in:
parent
edd613062a
commit
47122a3115
12 changed files with 447 additions and 58 deletions
|
@ -97,6 +97,25 @@ pub enum Worktree {
|
|||
Remote(RemoteWorktree),
|
||||
}
|
||||
|
||||
/// An entry, created in the worktree.
|
||||
#[derive(Debug)]
|
||||
pub enum CreatedEntry {
|
||||
/// Got created and indexed by the worktree, receiving a corresponding entry.
|
||||
Included(Entry),
|
||||
/// Got created, but not indexed due to falling under exclusion filters.
|
||||
Excluded { abs_path: PathBuf },
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl CreatedEntry {
|
||||
pub fn to_included(self) -> Option<Entry> {
|
||||
match self {
|
||||
CreatedEntry::Included(entry) => Some(entry),
|
||||
CreatedEntry::Excluded { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LocalWorktree {
|
||||
snapshot: LocalSnapshot,
|
||||
scan_requests_tx: channel::Sender<ScanRequest>,
|
||||
|
@ -1322,22 +1341,34 @@ impl LocalWorktree {
|
|||
path: impl Into<Arc<Path>>,
|
||||
is_dir: bool,
|
||||
cx: &mut ModelContext<Worktree>,
|
||||
) -> Task<Result<Option<Entry>>> {
|
||||
) -> Task<Result<CreatedEntry>> {
|
||||
let path = path.into();
|
||||
let lowest_ancestor = self.lowest_ancestor(&path);
|
||||
let abs_path = self.absolutize(&path);
|
||||
let abs_path = match self.absolutize(&path) {
|
||||
Ok(path) => path,
|
||||
Err(e) => return Task::ready(Err(e.context(format!("absolutizing path {path:?}")))),
|
||||
};
|
||||
let path_excluded = self.is_path_excluded(&abs_path);
|
||||
let fs = self.fs.clone();
|
||||
let task_abs_path = abs_path.clone();
|
||||
let write = cx.background_executor().spawn(async move {
|
||||
if is_dir {
|
||||
fs.create_dir(&abs_path?).await
|
||||
} else {
|
||||
fs.save(&abs_path?, &Default::default(), Default::default())
|
||||
fs.create_dir(&task_abs_path)
|
||||
.await
|
||||
.with_context(|| format!("creating directory {task_abs_path:?}"))
|
||||
} else {
|
||||
fs.save(&task_abs_path, &Rope::default(), LineEnding::default())
|
||||
.await
|
||||
.with_context(|| format!("creating file {task_abs_path:?}"))
|
||||
}
|
||||
});
|
||||
|
||||
let lowest_ancestor = self.lowest_ancestor(&path);
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
write.await?;
|
||||
if path_excluded {
|
||||
return Ok(CreatedEntry::Excluded { abs_path });
|
||||
}
|
||||
|
||||
let (result, refreshes) = this.update(&mut cx, |this, cx| {
|
||||
let mut refreshes = Vec::new();
|
||||
let refresh_paths = path.strip_prefix(&lowest_ancestor).unwrap();
|
||||
|
@ -1362,7 +1393,10 @@ impl LocalWorktree {
|
|||
refresh.await.log_err();
|
||||
}
|
||||
|
||||
result.await
|
||||
Ok(result
|
||||
.await?
|
||||
.map(CreatedEntry::Included)
|
||||
.unwrap_or_else(|| CreatedEntry::Excluded { abs_path }))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1448,19 +1482,22 @@ impl LocalWorktree {
|
|||
entry_id: ProjectEntryId,
|
||||
new_path: impl Into<Arc<Path>>,
|
||||
cx: &mut ModelContext<Worktree>,
|
||||
) -> Task<Result<Option<Entry>>> {
|
||||
) -> Task<Result<CreatedEntry>> {
|
||||
let old_path = match self.entry_for_id(entry_id) {
|
||||
Some(entry) => entry.path.clone(),
|
||||
None => return Task::ready(Ok(None)),
|
||||
None => return Task::ready(Err(anyhow!("no entry to rename for id {entry_id:?}"))),
|
||||
};
|
||||
let new_path = new_path.into();
|
||||
let abs_old_path = self.absolutize(&old_path);
|
||||
let abs_new_path = self.absolutize(&new_path);
|
||||
let Ok(abs_new_path) = self.absolutize(&new_path) else {
|
||||
return Task::ready(Err(anyhow!("absolutizing path {new_path:?}")));
|
||||
};
|
||||
let abs_path = abs_new_path.clone();
|
||||
let fs = self.fs.clone();
|
||||
let case_sensitive = self.fs_case_sensitive;
|
||||
let rename = cx.background_executor().spawn(async move {
|
||||
let abs_old_path = abs_old_path?;
|
||||
let abs_new_path = abs_new_path?;
|
||||
let abs_new_path = abs_new_path;
|
||||
|
||||
let abs_old_path_lower = abs_old_path.to_str().map(|p| p.to_lowercase());
|
||||
let abs_new_path_lower = abs_new_path.to_str().map(|p| p.to_lowercase());
|
||||
|
@ -1480,16 +1517,20 @@ impl LocalWorktree {
|
|||
},
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("Renaming {abs_old_path:?} into {abs_new_path:?}"))
|
||||
});
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
rename.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.as_local_mut()
|
||||
.unwrap()
|
||||
.refresh_entry(new_path.clone(), Some(old_path), cx)
|
||||
})?
|
||||
.await
|
||||
Ok(this
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.as_local_mut()
|
||||
.unwrap()
|
||||
.refresh_entry(new_path.clone(), Some(old_path), cx)
|
||||
})?
|
||||
.await?
|
||||
.map(CreatedEntry::Included)
|
||||
.unwrap_or_else(|| CreatedEntry::Excluded { abs_path }))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1212,6 +1212,7 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
|
|||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.to_included()
|
||||
.unwrap();
|
||||
assert!(entry.is_dir());
|
||||
|
||||
|
@ -1268,6 +1269,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
|||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.to_included()
|
||||
.unwrap();
|
||||
assert!(entry.is_file());
|
||||
|
||||
|
@ -1310,6 +1312,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
|||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.to_included()
|
||||
.unwrap();
|
||||
assert!(entry.is_file());
|
||||
|
||||
|
@ -1329,6 +1332,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
|||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.to_included()
|
||||
.unwrap();
|
||||
assert!(entry.is_file());
|
||||
|
||||
|
@ -1346,6 +1350,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
|||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.to_included()
|
||||
.unwrap();
|
||||
assert!(entry.is_file());
|
||||
|
||||
|
@ -1673,7 +1678,7 @@ fn randomly_mutate_worktree(
|
|||
);
|
||||
let task = worktree.rename_entry(entry.id, new_path, cx);
|
||||
cx.background_executor().spawn(async move {
|
||||
task.await?.unwrap();
|
||||
task.await?.to_included().unwrap();
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue