mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-23 18:32:17 +00:00
Allow deleting entries from the project panel
This commit is contained in:
parent
509ede0e80
commit
4b1c46fa45
9 changed files with 198 additions and 8 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -3330,7 +3330,9 @@ name = "project_panel"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"editor",
|
"editor",
|
||||||
|
"futures",
|
||||||
"gpui",
|
"gpui",
|
||||||
|
"postage",
|
||||||
"project",
|
"project",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"settings",
|
"settings",
|
||||||
|
|
|
@ -332,7 +332,8 @@
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"left": "project_panel::CollapseSelectedEntry",
|
"left": "project_panel::CollapseSelectedEntry",
|
||||||
"right": "project_panel::ExpandSelectedEntry",
|
"right": "project_panel::ExpandSelectedEntry",
|
||||||
"f2": "project_panel::Rename"
|
"f2": "project_panel::Rename",
|
||||||
|
"backspace": "project_panel::Delete"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -128,6 +128,7 @@ impl Server {
|
||||||
.add_request_handler(Server::forward_project_request::<proto::FormatBuffers>)
|
.add_request_handler(Server::forward_project_request::<proto::FormatBuffers>)
|
||||||
.add_request_handler(Server::forward_project_request::<proto::CreateProjectEntry>)
|
.add_request_handler(Server::forward_project_request::<proto::CreateProjectEntry>)
|
||||||
.add_request_handler(Server::forward_project_request::<proto::RenameProjectEntry>)
|
.add_request_handler(Server::forward_project_request::<proto::RenameProjectEntry>)
|
||||||
|
.add_request_handler(Server::forward_project_request::<proto::DeleteProjectEntry>)
|
||||||
.add_request_handler(Server::update_buffer)
|
.add_request_handler(Server::update_buffer)
|
||||||
.add_message_handler(Server::update_buffer_file)
|
.add_message_handler(Server::update_buffer_file)
|
||||||
.add_message_handler(Server::buffer_reloaded)
|
.add_message_handler(Server::buffer_reloaded)
|
||||||
|
@ -1900,7 +1901,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
project_b
|
let dir_entry = project_b
|
||||||
.update(cx_b, |project, cx| {
|
.update(cx_b, |project, cx| {
|
||||||
project
|
project
|
||||||
.create_entry((worktree_id, "DIR"), true, cx)
|
.create_entry((worktree_id, "DIR"), true, cx)
|
||||||
|
@ -1926,6 +1927,56 @@ mod tests {
|
||||||
[".zed.toml", "DIR", "a.txt", "b.txt", "d.txt"]
|
[".zed.toml", "DIR", "a.txt", "b.txt", "d.txt"]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
project_b
|
||||||
|
.update(cx_b, |project, cx| {
|
||||||
|
project.delete_entry(dir_entry.id, cx).unwrap()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
worktree_a.read_with(cx_a, |worktree, _| {
|
||||||
|
assert_eq!(
|
||||||
|
worktree
|
||||||
|
.paths()
|
||||||
|
.map(|p| p.to_string_lossy())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
[".zed.toml", "a.txt", "b.txt", "d.txt"]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
worktree_b.read_with(cx_b, |worktree, _| {
|
||||||
|
assert_eq!(
|
||||||
|
worktree
|
||||||
|
.paths()
|
||||||
|
.map(|p| p.to_string_lossy())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
[".zed.toml", "a.txt", "b.txt", "d.txt"]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
project_b
|
||||||
|
.update(cx_b, |project, cx| {
|
||||||
|
project.delete_entry(entry.id, cx).unwrap()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
worktree_a.read_with(cx_a, |worktree, _| {
|
||||||
|
assert_eq!(
|
||||||
|
worktree
|
||||||
|
.paths()
|
||||||
|
.map(|p| p.to_string_lossy())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
[".zed.toml", "a.txt", "b.txt"]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
worktree_b.read_with(cx_b, |worktree, _| {
|
||||||
|
assert_eq!(
|
||||||
|
worktree
|
||||||
|
.paths()
|
||||||
|
.map(|p| p.to_string_lossy())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
[".zed.toml", "a.txt", "b.txt"]
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test(iterations = 10)]
|
#[gpui::test(iterations = 10)]
|
||||||
|
|
|
@ -263,6 +263,7 @@ impl Project {
|
||||||
client.add_model_message_handler(Self::handle_update_worktree);
|
client.add_model_message_handler(Self::handle_update_worktree);
|
||||||
client.add_model_request_handler(Self::handle_create_project_entry);
|
client.add_model_request_handler(Self::handle_create_project_entry);
|
||||||
client.add_model_request_handler(Self::handle_rename_project_entry);
|
client.add_model_request_handler(Self::handle_rename_project_entry);
|
||||||
|
client.add_model_request_handler(Self::handle_delete_project_entry);
|
||||||
client.add_model_request_handler(Self::handle_apply_additional_edits_for_completion);
|
client.add_model_request_handler(Self::handle_apply_additional_edits_for_completion);
|
||||||
client.add_model_request_handler(Self::handle_apply_code_action);
|
client.add_model_request_handler(Self::handle_apply_code_action);
|
||||||
client.add_model_request_handler(Self::handle_reload_buffers);
|
client.add_model_request_handler(Self::handle_reload_buffers);
|
||||||
|
@ -768,6 +769,35 @@ impl Project {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn delete_entry(
|
||||||
|
&mut self,
|
||||||
|
entry_id: ProjectEntryId,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Option<Task<Result<()>>> {
|
||||||
|
let worktree = self.worktree_for_entry(entry_id, cx)?;
|
||||||
|
if self.is_local() {
|
||||||
|
worktree.update(cx, |worktree, cx| {
|
||||||
|
worktree.as_local_mut().unwrap().delete_entry(entry_id, cx)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let client = self.client.clone();
|
||||||
|
let project_id = self.remote_id().unwrap();
|
||||||
|
Some(cx.spawn_weak(|_, mut cx| async move {
|
||||||
|
client
|
||||||
|
.request(proto::DeleteProjectEntry {
|
||||||
|
project_id,
|
||||||
|
entry_id: entry_id.to_proto(),
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
worktree
|
||||||
|
.update(&mut cx, move |worktree, cx| {
|
||||||
|
worktree.as_remote().unwrap().delete_entry(entry_id, cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn can_share(&self, cx: &AppContext) -> bool {
|
pub fn can_share(&self, cx: &AppContext) -> bool {
|
||||||
self.is_local() && self.visible_worktrees(cx).next().is_some()
|
self.is_local() && self.visible_worktrees(cx).next().is_some()
|
||||||
}
|
}
|
||||||
|
@ -3858,6 +3888,21 @@ impl Project {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_delete_project_entry(
|
||||||
|
this: ModelHandle<Self>,
|
||||||
|
envelope: TypedEnvelope<proto::DeleteProjectEntry>,
|
||||||
|
_: Arc<Client>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> Result<proto::Ack> {
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
|
||||||
|
this.delete_entry(entry_id, cx)
|
||||||
|
.ok_or_else(|| anyhow!("invalid entry"))
|
||||||
|
})?
|
||||||
|
.await?;
|
||||||
|
Ok(proto::Ack {})
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_update_diagnostic_summary(
|
async fn handle_update_diagnostic_summary(
|
||||||
this: ModelHandle<Self>,
|
this: ModelHandle<Self>,
|
||||||
envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
|
envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::ProjectEntryId;
|
use crate::{ProjectEntryId, RemoveOptions};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
fs::{self, Fs},
|
fs::{self, Fs},
|
||||||
|
@ -712,6 +712,44 @@ impl LocalWorktree {
|
||||||
self.write_entry_internal(path, Some(text), cx)
|
self.write_entry_internal(path, Some(text), cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn delete_entry(
|
||||||
|
&self,
|
||||||
|
entry_id: ProjectEntryId,
|
||||||
|
cx: &mut ModelContext<Worktree>,
|
||||||
|
) -> Option<Task<Result<()>>> {
|
||||||
|
let entry = self.entry_for_id(entry_id)?.clone();
|
||||||
|
let abs_path = self.absolutize(&entry.path);
|
||||||
|
let delete = cx.background().spawn({
|
||||||
|
let fs = self.fs.clone();
|
||||||
|
let abs_path = abs_path.clone();
|
||||||
|
async move {
|
||||||
|
if entry.is_file() {
|
||||||
|
fs.remove_file(&abs_path, Default::default()).await
|
||||||
|
} else {
|
||||||
|
fs.remove_dir(
|
||||||
|
&abs_path,
|
||||||
|
RemoveOptions {
|
||||||
|
recursive: true,
|
||||||
|
ignore_if_not_exists: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(cx.spawn(|this, mut cx| async move {
|
||||||
|
delete.await?;
|
||||||
|
this.update(&mut cx, |this, _| {
|
||||||
|
let this = this.as_local_mut().unwrap();
|
||||||
|
let mut snapshot = this.background_snapshot.lock();
|
||||||
|
snapshot.delete_entry(entry_id);
|
||||||
|
});
|
||||||
|
this.update(&mut cx, |this, cx| this.poll_snapshot(cx));
|
||||||
|
Ok(())
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn rename_entry(
|
pub fn rename_entry(
|
||||||
&self,
|
&self,
|
||||||
entry_id: ProjectEntryId,
|
entry_id: ProjectEntryId,
|
||||||
|
@ -1019,6 +1057,29 @@ impl RemoteWorktree {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn delete_entry(
|
||||||
|
&self,
|
||||||
|
id: ProjectEntryId,
|
||||||
|
cx: &mut ModelContext<Worktree>,
|
||||||
|
) -> Task<Result<()>> {
|
||||||
|
cx.spawn(|this, mut cx| async move {
|
||||||
|
this.update(&mut cx, |worktree, _| {
|
||||||
|
worktree
|
||||||
|
.as_remote_mut()
|
||||||
|
.unwrap()
|
||||||
|
.finish_pending_remote_updates()
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
this.update(&mut cx, |worktree, _| {
|
||||||
|
let worktree = worktree.as_remote_mut().unwrap();
|
||||||
|
let mut snapshot = worktree.background_snapshot.lock();
|
||||||
|
snapshot.delete_entry(id);
|
||||||
|
worktree.snapshot = snapshot.clone();
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Snapshot {
|
impl Snapshot {
|
||||||
|
@ -1048,6 +1109,15 @@ impl Snapshot {
|
||||||
Ok(entry)
|
Ok(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn delete_entry(&mut self, entry_id: ProjectEntryId) -> bool {
|
||||||
|
if let Some(entry) = self.entries_by_id.remove(&entry_id, &()) {
|
||||||
|
self.entries_by_path.remove(&PathKey(entry.path), &());
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn apply_remote_update(&mut self, update: proto::UpdateWorktree) -> Result<()> {
|
pub(crate) fn apply_remote_update(&mut self, update: proto::UpdateWorktree) -> Result<()> {
|
||||||
let mut entries_by_path_edits = Vec::new();
|
let mut entries_by_path_edits = Vec::new();
|
||||||
let mut entries_by_id_edits = Vec::new();
|
let mut entries_by_id_edits = Vec::new();
|
||||||
|
|
|
@ -15,6 +15,8 @@ settings = { path = "../settings" }
|
||||||
theme = { path = "../theme" }
|
theme = { path = "../theme" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
workspace = { path = "../workspace" }
|
workspace = { path = "../workspace" }
|
||||||
|
postage = { version = "0.4.1", features = ["futures-traits"] }
|
||||||
|
futures = "0.3"
|
||||||
unicase = "2.6"
|
unicase = "2.6"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
use editor::{Cancel, Editor};
|
use editor::{Cancel, Editor};
|
||||||
|
use futures::stream::StreamExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions,
|
actions,
|
||||||
anyhow::Result,
|
anyhow::{anyhow, Result},
|
||||||
elements::{
|
elements::{
|
||||||
ChildView, ConstrainedBox, Empty, Flex, Label, MouseEventHandler, ParentElement,
|
ChildView, ConstrainedBox, Empty, Flex, Label, MouseEventHandler, ParentElement,
|
||||||
ScrollTarget, Svg, UniformList, UniformListState,
|
ScrollTarget, Svg, UniformList, UniformListState,
|
||||||
},
|
},
|
||||||
impl_internal_actions, keymap,
|
impl_internal_actions, keymap,
|
||||||
platform::CursorStyle,
|
platform::CursorStyle,
|
||||||
AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, Task, View,
|
AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, PromptLevel, Task,
|
||||||
ViewContext, ViewHandle, WeakViewHandle,
|
View, ViewContext, ViewHandle, WeakViewHandle,
|
||||||
};
|
};
|
||||||
use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
|
use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
|
@ -77,6 +78,7 @@ actions!(
|
||||||
CollapseSelectedEntry,
|
CollapseSelectedEntry,
|
||||||
AddDirectory,
|
AddDirectory,
|
||||||
AddFile,
|
AddFile,
|
||||||
|
Delete,
|
||||||
Rename
|
Rename
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
@ -92,6 +94,7 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
cx.add_action(ProjectPanel::add_file);
|
cx.add_action(ProjectPanel::add_file);
|
||||||
cx.add_action(ProjectPanel::add_directory);
|
cx.add_action(ProjectPanel::add_directory);
|
||||||
cx.add_action(ProjectPanel::rename);
|
cx.add_action(ProjectPanel::rename);
|
||||||
|
cx.add_async_action(ProjectPanel::delete);
|
||||||
cx.add_async_action(ProjectPanel::confirm);
|
cx.add_async_action(ProjectPanel::confirm);
|
||||||
cx.add_action(ProjectPanel::cancel);
|
cx.add_action(ProjectPanel::cancel);
|
||||||
}
|
}
|
||||||
|
@ -432,6 +435,22 @@ impl ProjectPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
|
||||||
|
let Selection { entry_id, .. } = self.selection?;
|
||||||
|
let mut answer = cx.prompt(PromptLevel::Info, "Delete?", &["Delete", "Cancel"]);
|
||||||
|
Some(cx.spawn(|this, mut cx| async move {
|
||||||
|
if answer.next().await != Some(0) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.project
|
||||||
|
.update(cx, |project, cx| project.delete_entry(entry_id, cx))
|
||||||
|
.ok_or_else(|| anyhow!("no such entry"))
|
||||||
|
})?
|
||||||
|
.await
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
|
fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
|
||||||
if let Some(selection) = self.selection {
|
if let Some(selection) = self.selection {
|
||||||
let (mut worktree_ix, mut entry_ix, _) =
|
let (mut worktree_ix, mut entry_ix, _) =
|
||||||
|
|
|
@ -179,8 +179,7 @@ message RenameProjectEntry {
|
||||||
|
|
||||||
message DeleteProjectEntry {
|
message DeleteProjectEntry {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
uint64 worktree_id = 2;
|
uint64 entry_id = 2;
|
||||||
string path = 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message ProjectEntryResponse {
|
message ProjectEntryResponse {
|
||||||
|
|
|
@ -225,6 +225,7 @@ request_messages!(
|
||||||
ApplyCompletionAdditionalEditsResponse
|
ApplyCompletionAdditionalEditsResponse
|
||||||
),
|
),
|
||||||
(CreateProjectEntry, ProjectEntryResponse),
|
(CreateProjectEntry, ProjectEntryResponse),
|
||||||
|
(DeleteProjectEntry, Ack),
|
||||||
(Follow, FollowResponse),
|
(Follow, FollowResponse),
|
||||||
(FormatBuffers, FormatBuffersResponse),
|
(FormatBuffers, FormatBuffersResponse),
|
||||||
(GetChannelMessages, GetChannelMessagesResponse),
|
(GetChannelMessages, GetChannelMessagesResponse),
|
||||||
|
|
Loading…
Reference in a new issue