mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-25 09:47:54 +00:00
Add the ability to make new directories by adding slashes to a file name
This commit is contained in:
parent
f6b64dc67a
commit
33f5248d4f
10 changed files with 259 additions and 8 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -5033,6 +5033,7 @@ dependencies = [
|
|||
"language",
|
||||
"menu",
|
||||
"postage",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"schemars",
|
||||
"serde",
|
||||
|
|
|
@ -101,6 +101,7 @@ time = { version = "0.3", features = ["serde", "serde-well-known"] }
|
|||
toml = { version = "0.5" }
|
||||
tree-sitter = "0.20"
|
||||
unindent = { version = "0.1.7" }
|
||||
pretty_assertions = "1.3.0"
|
||||
|
||||
[patch.crates-io]
|
||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "49226023693107fba9a1191136a4f47f38cdca73" }
|
||||
|
|
|
@ -67,7 +67,7 @@ fs = { path = "../fs", features = ["test-support"] }
|
|||
git = { path = "../git", features = ["test-support"] }
|
||||
live_kit_client = { path = "../live_kit_client", features = ["test-support"] }
|
||||
lsp = { path = "../lsp", features = ["test-support"] }
|
||||
pretty_assertions = "1.3.0"
|
||||
pretty_assertions = "*"
|
||||
project = { path = "../project", features = ["test-support"] }
|
||||
rpc = { path = "../rpc", features = ["test-support"] }
|
||||
settings = { path = "../settings", features = ["test-support"] }
|
||||
|
|
|
@ -279,6 +279,9 @@ impl Fs for RealFs {
|
|||
|
||||
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
|
||||
let buffer_size = text.summary().len.min(10 * 1024);
|
||||
if let Some(path) = path.parent() {
|
||||
self.create_dir(path).await?;
|
||||
}
|
||||
let file = smol::fs::File::create(path).await?;
|
||||
let mut writer = smol::io::BufWriter::with_capacity(buffer_size, file);
|
||||
for chunk in chunks(text, line_ending) {
|
||||
|
@ -1077,6 +1080,9 @@ impl Fs for FakeFs {
|
|||
self.simulate_random_delay().await;
|
||||
let path = normalize_path(path);
|
||||
let content = chunks(text, line_ending).collect();
|
||||
if let Some(path) = path.parent() {
|
||||
self.create_dir(path).await?;
|
||||
}
|
||||
self.write_file_internal(path, content)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ itertools = "0.10"
|
|||
[dev-dependencies]
|
||||
ctor.workspace = true
|
||||
env_logger.workspace = true
|
||||
pretty_assertions = "1.3.0"
|
||||
pretty_assertions = "*"
|
||||
client = { path = "../client", features = ["test-support"] }
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
db = { path = "../db", features = ["test-support"] }
|
||||
|
|
|
@ -1001,10 +1001,25 @@ impl LocalWorktree {
|
|||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
write.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.as_local_mut().unwrap().refresh_entry(path, None, cx)
|
||||
})
|
||||
.await
|
||||
let (result, refreshes) = this.update(&mut cx, |this, cx| {
|
||||
let mut refreshes = Vec::new();
|
||||
for path in path.ancestors().skip(1) {
|
||||
refreshes.push(this.as_local_mut().unwrap().refresh_entry(
|
||||
path.into(),
|
||||
None,
|
||||
cx,
|
||||
));
|
||||
}
|
||||
(
|
||||
this.as_local_mut().unwrap().refresh_entry(path, None, cx),
|
||||
refreshes,
|
||||
)
|
||||
});
|
||||
for refresh in refreshes {
|
||||
refresh.await.log_err();
|
||||
}
|
||||
|
||||
result.await
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -936,6 +936,83 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
|
||||
let client_fake = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||
|
||||
let fs_fake = FakeFs::new(cx.background());
|
||||
fs_fake.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
"a": {},
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let tree_fake = Worktree::local(
|
||||
client_fake,
|
||||
"/root".as_ref(),
|
||||
true,
|
||||
fs_fake,
|
||||
Default::default(),
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let entry = tree_fake
|
||||
.update(cx, |tree, cx| {
|
||||
tree.as_local_mut()
|
||||
.unwrap()
|
||||
.create_entry("a/b/c/d.txt".as_ref(), false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(entry.is_file());
|
||||
|
||||
cx.foreground().run_until_parked();
|
||||
tree_fake.read_with(cx, |tree, _| {
|
||||
assert!(tree.entry_for_path("a/b/c/d.txt").unwrap().is_file());
|
||||
assert!(tree.entry_for_path("a/b/c/").unwrap().is_dir());
|
||||
assert!(tree.entry_for_path("a/b/").unwrap().is_dir());
|
||||
});
|
||||
|
||||
let client_real = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||
|
||||
let fs_real = Arc::new(RealFs);
|
||||
let temp_root = temp_tree(json!({
|
||||
"a": {}
|
||||
}));
|
||||
|
||||
let tree_real = Worktree::local(
|
||||
client_real,
|
||||
temp_root.path(),
|
||||
true,
|
||||
fs_real,
|
||||
Default::default(),
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let entry = tree_real
|
||||
.update(cx, |tree, cx| {
|
||||
tree.as_local_mut()
|
||||
.unwrap()
|
||||
.create_entry("a/b/c/d.txt".as_ref(), false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(entry.is_file());
|
||||
|
||||
cx.foreground().run_until_parked();
|
||||
tree_real.read_with(cx, |tree, _| {
|
||||
assert!(tree.entry_for_path("a/b/c/d.txt").unwrap().is_file());
|
||||
assert!(tree.entry_for_path("a/b/c/").unwrap().is_dir());
|
||||
assert!(tree.entry_for_path("a/b/").unwrap().is_dir());
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
async fn test_random_worktree_operations_during_initial_scan(
|
||||
cx: &mut TestAppContext,
|
||||
|
|
|
@ -27,6 +27,7 @@ serde_derive.workspace = true
|
|||
serde_json.workspace = true
|
||||
anyhow.workspace = true
|
||||
schemars.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
unicase = "2.6"
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -64,7 +64,7 @@ pub struct ProjectPanel {
|
|||
pending_serialization: Task<Option<()>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct Selection {
|
||||
worktree_id: WorktreeId,
|
||||
entry_id: ProjectEntryId,
|
||||
|
@ -588,6 +588,7 @@ impl ProjectPanel {
|
|||
if selection.entry_id == edited_entry_id {
|
||||
selection.worktree_id = worktree_id;
|
||||
selection.entry_id = new_entry.id;
|
||||
this.expand_to_selection(cx);
|
||||
}
|
||||
}
|
||||
this.update_visible_entries(None, cx);
|
||||
|
@ -965,6 +966,25 @@ impl ProjectPanel {
|
|||
Some((worktree, entry))
|
||||
}
|
||||
|
||||
fn expand_to_selection(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
|
||||
let (worktree, entry) = self.selected_entry(cx)?;
|
||||
let expanded_dir_ids = self.expanded_dir_ids.entry(worktree.id()).or_default();
|
||||
|
||||
for path in entry.path.ancestors() {
|
||||
let Some(entry) = worktree.entry_for_path(path) else {
|
||||
continue;
|
||||
};
|
||||
if entry.is_dir() {
|
||||
if let Err(idx) = expanded_dir_ids.binary_search(&entry.id) {
|
||||
expanded_dir_ids.insert(idx, entry.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn update_visible_entries(
|
||||
&mut self,
|
||||
new_selected_entry: Option<(WorktreeId, ProjectEntryId)>,
|
||||
|
@ -1139,6 +1159,7 @@ impl ProjectPanel {
|
|||
for entry in visible_worktree_entries[entry_range].iter() {
|
||||
let status = git_status_setting.then(|| entry.git_status).flatten();
|
||||
|
||||
|
||||
let mut details = EntryDetails {
|
||||
filename: entry
|
||||
.path
|
||||
|
@ -1592,6 +1613,7 @@ impl ClipboardEntry {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use gpui::{TestAppContext, ViewHandle};
|
||||
use pretty_assertions::assert_eq;
|
||||
use project::FakeFs;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
|
@ -2002,6 +2024,134 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 30)]
|
||||
async fn test_adding_directories_via_file(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background());
|
||||
fs.insert_tree(
|
||||
"/root1",
|
||||
json!({
|
||||
".dockerignore": "",
|
||||
".git": {
|
||||
"HEAD": "",
|
||||
},
|
||||
"a": {
|
||||
"0": { "q": "", "r": "", "s": "" },
|
||||
"1": { "t": "", "u": "" },
|
||||
"2": { "v": "", "w": "", "x": "", "y": "" },
|
||||
},
|
||||
"b": {
|
||||
"3": { "Q": "" },
|
||||
"4": { "R": "", "S": "", "T": "", "U": "" },
|
||||
},
|
||||
"C": {
|
||||
"5": {},
|
||||
"6": { "V": "", "W": "" },
|
||||
"7": { "X": "" },
|
||||
"8": { "Y": {}, "Z": "" }
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
fs.insert_tree(
|
||||
"/root2",
|
||||
json!({
|
||||
"d": {
|
||||
"9": ""
|
||||
},
|
||||
"e": {}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
|
||||
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
|
||||
|
||||
select_path(&panel, "root1", cx);
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root1 <== selected",
|
||||
" > .git",
|
||||
" > a",
|
||||
" > b",
|
||||
" > C",
|
||||
" .dockerignore",
|
||||
"v root2",
|
||||
" > d",
|
||||
" > e",
|
||||
]
|
||||
);
|
||||
|
||||
// Add a file with the root folder selected. The filename editor is placed
|
||||
// before the first file in the root folder.
|
||||
panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx));
|
||||
cx.read_window(window_id, |cx| {
|
||||
let panel = panel.read(cx);
|
||||
assert!(panel.filename_editor.is_focused(cx));
|
||||
});
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root1",
|
||||
" > .git",
|
||||
" > a",
|
||||
" > b",
|
||||
" > C",
|
||||
" [EDITOR: ''] <== selected",
|
||||
" .dockerignore",
|
||||
"v root2",
|
||||
" > d",
|
||||
" > e",
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
let confirm = panel.update(cx, |panel, cx| {
|
||||
panel.filename_editor.update(cx, |editor, cx| {
|
||||
editor.set_text("bdir1/dir2/the-new-filename", cx)
|
||||
});
|
||||
panel.confirm(&Confirm, cx).unwrap()
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root1",
|
||||
" > .git",
|
||||
" > a",
|
||||
" > b",
|
||||
" > C",
|
||||
" [PROCESSING: 'bdir1/dir2/the-new-filename'] <== selected",
|
||||
" .dockerignore",
|
||||
"v root2",
|
||||
" > d",
|
||||
" > e",
|
||||
]
|
||||
);
|
||||
|
||||
confirm.await.unwrap();
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..13, cx),
|
||||
&[
|
||||
"v root1",
|
||||
" > .git",
|
||||
" > a",
|
||||
" > b",
|
||||
" v bdir1",
|
||||
" v dir2",
|
||||
" the-new-filename <== selected",
|
||||
" > C",
|
||||
" .dockerignore",
|
||||
"v root2",
|
||||
" > d",
|
||||
" > e",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_copy_paste(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
|
|
@ -38,5 +38,5 @@ tree-sitter-json = "*"
|
|||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
fs = { path = "../fs", features = ["test-support"] }
|
||||
indoc.workspace = true
|
||||
pretty_assertions = "1.3.0"
|
||||
pretty_assertions = "*"
|
||||
unindent.workspace = true
|
||||
|
|
Loading…
Reference in a new issue