mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-08 07:35:01 +03:00
Implement copy paste for ProjectPanel
This commit is contained in:
parent
37a0c7f046
commit
3336bc6ab3
@ -172,6 +172,7 @@ impl Server {
|
||||
.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::RenameProjectEntry>)
|
||||
.add_request_handler(Server::forward_project_request::<proto::CopyProjectEntry>)
|
||||
.add_request_handler(Server::forward_project_request::<proto::DeleteProjectEntry>)
|
||||
.add_request_handler(Server::update_buffer)
|
||||
.add_message_handler(Server::update_buffer_file)
|
||||
|
@ -15,6 +15,7 @@ use text::Rope;
|
||||
pub trait Fs: Send + Sync {
|
||||
async fn create_dir(&self, path: &Path) -> Result<()>;
|
||||
async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()>;
|
||||
async fn copy(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()>;
|
||||
async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()>;
|
||||
async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()>;
|
||||
async fn remove_file(&self, path: &Path, options: RemoveOptions) -> Result<()>;
|
||||
@ -44,6 +45,12 @@ pub struct CreateOptions {
|
||||
pub ignore_if_exists: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct CopyOptions {
|
||||
pub overwrite: bool,
|
||||
pub ignore_if_exists: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct RenameOptions {
|
||||
pub overwrite: bool,
|
||||
@ -84,6 +91,35 @@ impl Fs for RealFs {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn copy(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> {
|
||||
if !options.overwrite && smol::fs::metadata(target).await.is_ok() {
|
||||
if options.ignore_if_exists {
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(anyhow!("{target:?} already exists"));
|
||||
}
|
||||
}
|
||||
|
||||
let metadata = smol::fs::metadata(source).await?;
|
||||
let _ = smol::fs::remove_dir_all(target).await;
|
||||
if metadata.is_dir() {
|
||||
self.create_dir(target).await?;
|
||||
let mut children = smol::fs::read_dir(source).await?;
|
||||
while let Some(child) = children.next().await {
|
||||
if let Ok(child) = child {
|
||||
let child_source_path = child.path();
|
||||
let child_target_path = target.join(child.file_name());
|
||||
self.copy(&child_source_path, &child_target_path, options)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
smol::fs::copy(source, target).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn rename(&self, source: &Path, target: &Path, options: RenameOptions) -> Result<()> {
|
||||
if !options.overwrite && smol::fs::metadata(target).await.is_ok() {
|
||||
if options.ignore_if_exists {
|
||||
@ -511,6 +547,40 @@ impl Fs for FakeFs {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn copy(&self, source: &Path, target: &Path, options: CopyOptions) -> Result<()> {
|
||||
let source = normalize_path(source);
|
||||
let target = normalize_path(target);
|
||||
|
||||
let mut state = self.state.lock().await;
|
||||
state.validate_path(&source)?;
|
||||
state.validate_path(&target)?;
|
||||
|
||||
if !options.overwrite && state.entries.contains_key(&target) {
|
||||
if options.ignore_if_exists {
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(anyhow!("{target:?} already exists"));
|
||||
}
|
||||
}
|
||||
|
||||
let mut new_entries = Vec::new();
|
||||
for (path, entry) in &state.entries {
|
||||
if let Ok(relative_path) = path.strip_prefix(&source) {
|
||||
new_entries.push((relative_path.to_path_buf(), entry.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
let mut events = Vec::new();
|
||||
for (relative_path, entry) in new_entries {
|
||||
let new_path = normalize_path(&target.join(relative_path));
|
||||
events.push(new_path.clone());
|
||||
state.entries.insert(new_path, entry);
|
||||
}
|
||||
|
||||
state.emit_event(&events).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_dir(&self, dir_path: &Path, options: RemoveOptions) -> Result<()> {
|
||||
let dir_path = normalize_path(dir_path);
|
||||
let mut state = self.state.lock().await;
|
||||
|
@ -281,6 +281,7 @@ impl Project {
|
||||
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_rename_project_entry);
|
||||
client.add_model_request_handler(Self::handle_copy_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_code_action);
|
||||
@ -778,6 +779,49 @@ impl Project {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_entry(
|
||||
&mut self,
|
||||
entry_id: ProjectEntryId,
|
||||
new_path: impl Into<Arc<Path>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Option<Task<Result<Entry>>> {
|
||||
let worktree = self.worktree_for_entry(entry_id, cx)?;
|
||||
let new_path = new_path.into();
|
||||
if self.is_local() {
|
||||
worktree.update(cx, |worktree, cx| {
|
||||
worktree
|
||||
.as_local_mut()
|
||||
.unwrap()
|
||||
.copy_entry(entry_id, new_path, cx)
|
||||
})
|
||||
} else {
|
||||
let client = self.client.clone();
|
||||
let project_id = self.remote_id().unwrap();
|
||||
|
||||
Some(cx.spawn_weak(|_, mut cx| async move {
|
||||
let response = client
|
||||
.request(proto::CopyProjectEntry {
|
||||
project_id,
|
||||
entry_id: entry_id.to_proto(),
|
||||
new_path: new_path.as_os_str().as_bytes().to_vec(),
|
||||
})
|
||||
.await?;
|
||||
let entry = response
|
||||
.entry
|
||||
.ok_or_else(|| anyhow!("missing entry in response"))?;
|
||||
worktree
|
||||
.update(&mut cx, |worktree, cx| {
|
||||
worktree.as_remote().unwrap().insert_entry(
|
||||
entry,
|
||||
response.worktree_scan_id as usize,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rename_entry(
|
||||
&mut self,
|
||||
entry_id: ProjectEntryId,
|
||||
@ -4027,6 +4071,34 @@ impl Project {
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_copy_project_entry(
|
||||
this: ModelHandle<Self>,
|
||||
envelope: TypedEnvelope<proto::CopyProjectEntry>,
|
||||
_: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::ProjectEntryResponse> {
|
||||
let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
|
||||
let worktree = this.read_with(&cx, |this, cx| {
|
||||
this.worktree_for_entry(entry_id, cx)
|
||||
.ok_or_else(|| anyhow!("worktree not found"))
|
||||
})?;
|
||||
let worktree_scan_id = worktree.read_with(&cx, |worktree, _| worktree.scan_id());
|
||||
let entry = worktree
|
||||
.update(&mut cx, |worktree, cx| {
|
||||
let new_path = PathBuf::from(OsString::from_vec(envelope.payload.new_path));
|
||||
worktree
|
||||
.as_local_mut()
|
||||
.unwrap()
|
||||
.copy_entry(entry_id, new_path, cx)
|
||||
.ok_or_else(|| anyhow!("invalid entry"))
|
||||
})?
|
||||
.await?;
|
||||
Ok(proto::ProjectEntryResponse {
|
||||
entry: Some((&entry).into()),
|
||||
worktree_scan_id: worktree_scan_id as u64,
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_delete_project_entry(
|
||||
this: ModelHandle<Self>,
|
||||
envelope: TypedEnvelope<proto::DeleteProjectEntry>,
|
||||
|
@ -774,6 +774,46 @@ impl LocalWorktree {
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn copy_entry(
|
||||
&self,
|
||||
entry_id: ProjectEntryId,
|
||||
new_path: impl Into<Arc<Path>>,
|
||||
cx: &mut ModelContext<Worktree>,
|
||||
) -> Option<Task<Result<Entry>>> {
|
||||
let old_path = self.entry_for_id(entry_id)?.path.clone();
|
||||
let new_path = new_path.into();
|
||||
let abs_old_path = self.absolutize(&old_path);
|
||||
let abs_new_path = self.absolutize(&new_path);
|
||||
let copy = cx.background().spawn({
|
||||
let fs = self.fs.clone();
|
||||
let abs_new_path = abs_new_path.clone();
|
||||
async move {
|
||||
fs.copy(&abs_old_path, &abs_new_path, Default::default())
|
||||
.await
|
||||
}
|
||||
});
|
||||
|
||||
Some(cx.spawn(|this, mut cx| async move {
|
||||
copy.await?;
|
||||
let entry = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.as_local_mut().unwrap().refresh_entry(
|
||||
new_path.clone(),
|
||||
abs_new_path,
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.poll_snapshot(cx);
|
||||
this.as_local().unwrap().broadcast_snapshot()
|
||||
})
|
||||
.await;
|
||||
Ok(entry)
|
||||
}))
|
||||
}
|
||||
|
||||
fn write_entry_internal(
|
||||
&self,
|
||||
path: impl Into<Arc<Path>>,
|
||||
|
@ -698,6 +698,11 @@ impl ProjectPanel {
|
||||
})
|
||||
.map(|task| task.detach_and_log_err(cx));
|
||||
} else {
|
||||
self.project
|
||||
.update(cx, |project, cx| {
|
||||
project.copy_entry(clipboard_entry.entry_id(), new_path, cx)
|
||||
})
|
||||
.map(|task| task.detach_and_log_err(cx));
|
||||
}
|
||||
}
|
||||
None
|
||||
|
@ -41,66 +41,67 @@ message Envelope {
|
||||
|
||||
CreateProjectEntry create_project_entry = 33;
|
||||
RenameProjectEntry rename_project_entry = 34;
|
||||
DeleteProjectEntry delete_project_entry = 35;
|
||||
ProjectEntryResponse project_entry_response = 36;
|
||||
CopyProjectEntry copy_project_entry = 35;
|
||||
DeleteProjectEntry delete_project_entry = 36;
|
||||
ProjectEntryResponse project_entry_response = 37;
|
||||
|
||||
UpdateDiagnosticSummary update_diagnostic_summary = 37;
|
||||
StartLanguageServer start_language_server = 38;
|
||||
UpdateLanguageServer update_language_server = 39;
|
||||
UpdateDiagnosticSummary update_diagnostic_summary = 38;
|
||||
StartLanguageServer start_language_server = 39;
|
||||
UpdateLanguageServer update_language_server = 40;
|
||||
|
||||
OpenBufferById open_buffer_by_id = 40;
|
||||
OpenBufferByPath open_buffer_by_path = 41;
|
||||
OpenBufferResponse open_buffer_response = 42;
|
||||
UpdateBuffer update_buffer = 43;
|
||||
UpdateBufferFile update_buffer_file = 44;
|
||||
SaveBuffer save_buffer = 45;
|
||||
BufferSaved buffer_saved = 46;
|
||||
BufferReloaded buffer_reloaded = 47;
|
||||
ReloadBuffers reload_buffers = 48;
|
||||
ReloadBuffersResponse reload_buffers_response = 49;
|
||||
FormatBuffers format_buffers = 50;
|
||||
FormatBuffersResponse format_buffers_response = 51;
|
||||
GetCompletions get_completions = 52;
|
||||
GetCompletionsResponse get_completions_response = 53;
|
||||
ApplyCompletionAdditionalEdits apply_completion_additional_edits = 54;
|
||||
ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 55;
|
||||
GetCodeActions get_code_actions = 56;
|
||||
GetCodeActionsResponse get_code_actions_response = 57;
|
||||
ApplyCodeAction apply_code_action = 58;
|
||||
ApplyCodeActionResponse apply_code_action_response = 59;
|
||||
PrepareRename prepare_rename = 60;
|
||||
PrepareRenameResponse prepare_rename_response = 61;
|
||||
PerformRename perform_rename = 62;
|
||||
PerformRenameResponse perform_rename_response = 63;
|
||||
SearchProject search_project = 64;
|
||||
SearchProjectResponse search_project_response = 65;
|
||||
OpenBufferById open_buffer_by_id = 41;
|
||||
OpenBufferByPath open_buffer_by_path = 42;
|
||||
OpenBufferResponse open_buffer_response = 43;
|
||||
UpdateBuffer update_buffer = 44;
|
||||
UpdateBufferFile update_buffer_file = 45;
|
||||
SaveBuffer save_buffer = 46;
|
||||
BufferSaved buffer_saved = 47;
|
||||
BufferReloaded buffer_reloaded = 48;
|
||||
ReloadBuffers reload_buffers = 49;
|
||||
ReloadBuffersResponse reload_buffers_response = 50;
|
||||
FormatBuffers format_buffers = 51;
|
||||
FormatBuffersResponse format_buffers_response = 52;
|
||||
GetCompletions get_completions = 53;
|
||||
GetCompletionsResponse get_completions_response = 54;
|
||||
ApplyCompletionAdditionalEdits apply_completion_additional_edits = 55;
|
||||
ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 56;
|
||||
GetCodeActions get_code_actions = 57;
|
||||
GetCodeActionsResponse get_code_actions_response = 58;
|
||||
ApplyCodeAction apply_code_action = 59;
|
||||
ApplyCodeActionResponse apply_code_action_response = 60;
|
||||
PrepareRename prepare_rename = 61;
|
||||
PrepareRenameResponse prepare_rename_response = 62;
|
||||
PerformRename perform_rename = 63;
|
||||
PerformRenameResponse perform_rename_response = 64;
|
||||
SearchProject search_project = 65;
|
||||
SearchProjectResponse search_project_response = 66;
|
||||
|
||||
GetChannels get_channels = 66;
|
||||
GetChannelsResponse get_channels_response = 67;
|
||||
JoinChannel join_channel = 68;
|
||||
JoinChannelResponse join_channel_response = 69;
|
||||
LeaveChannel leave_channel = 70;
|
||||
SendChannelMessage send_channel_message = 71;
|
||||
SendChannelMessageResponse send_channel_message_response = 72;
|
||||
ChannelMessageSent channel_message_sent = 73;
|
||||
GetChannelMessages get_channel_messages = 74;
|
||||
GetChannelMessagesResponse get_channel_messages_response = 75;
|
||||
GetChannels get_channels = 67;
|
||||
GetChannelsResponse get_channels_response = 68;
|
||||
JoinChannel join_channel = 69;
|
||||
JoinChannelResponse join_channel_response = 70;
|
||||
LeaveChannel leave_channel = 71;
|
||||
SendChannelMessage send_channel_message = 72;
|
||||
SendChannelMessageResponse send_channel_message_response = 73;
|
||||
ChannelMessageSent channel_message_sent = 74;
|
||||
GetChannelMessages get_channel_messages = 75;
|
||||
GetChannelMessagesResponse get_channel_messages_response = 76;
|
||||
|
||||
UpdateContacts update_contacts = 76;
|
||||
UpdateInviteInfo update_invite_info = 77;
|
||||
ShowContacts show_contacts = 78;
|
||||
UpdateContacts update_contacts = 77;
|
||||
UpdateInviteInfo update_invite_info = 78;
|
||||
ShowContacts show_contacts = 79;
|
||||
|
||||
GetUsers get_users = 79;
|
||||
FuzzySearchUsers fuzzy_search_users = 80;
|
||||
UsersResponse users_response = 81;
|
||||
RequestContact request_contact = 82;
|
||||
RespondToContactRequest respond_to_contact_request = 83;
|
||||
RemoveContact remove_contact = 84;
|
||||
GetUsers get_users = 80;
|
||||
FuzzySearchUsers fuzzy_search_users = 81;
|
||||
UsersResponse users_response = 82;
|
||||
RequestContact request_contact = 83;
|
||||
RespondToContactRequest respond_to_contact_request = 84;
|
||||
RemoveContact remove_contact = 85;
|
||||
|
||||
Follow follow = 85;
|
||||
FollowResponse follow_response = 86;
|
||||
UpdateFollowers update_followers = 87;
|
||||
Unfollow unfollow = 88;
|
||||
Follow follow = 86;
|
||||
FollowResponse follow_response = 87;
|
||||
UpdateFollowers update_followers = 88;
|
||||
Unfollow unfollow = 89;
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,6 +211,12 @@ message RenameProjectEntry {
|
||||
bytes new_path = 3;
|
||||
}
|
||||
|
||||
message CopyProjectEntry {
|
||||
uint64 project_id = 1;
|
||||
uint64 entry_id = 2;
|
||||
bytes new_path = 3;
|
||||
}
|
||||
|
||||
message DeleteProjectEntry {
|
||||
uint64 project_id = 1;
|
||||
uint64 entry_id = 2;
|
||||
|
@ -84,6 +84,7 @@ messages!(
|
||||
(BufferSaved, Foreground),
|
||||
(RemoveContact, Foreground),
|
||||
(ChannelMessageSent, Foreground),
|
||||
(CopyProjectEntry, Foreground),
|
||||
(CreateProjectEntry, Foreground),
|
||||
(DeleteProjectEntry, Foreground),
|
||||
(Error, Foreground),
|
||||
@ -167,6 +168,7 @@ request_messages!(
|
||||
ApplyCompletionAdditionalEdits,
|
||||
ApplyCompletionAdditionalEditsResponse
|
||||
),
|
||||
(CopyProjectEntry, ProjectEntryResponse),
|
||||
(CreateProjectEntry, ProjectEntryResponse),
|
||||
(DeleteProjectEntry, ProjectEntryResponse),
|
||||
(Follow, FollowResponse),
|
||||
@ -211,8 +213,8 @@ entity_messages!(
|
||||
ApplyCompletionAdditionalEdits,
|
||||
BufferReloaded,
|
||||
BufferSaved,
|
||||
CopyProjectEntry,
|
||||
CreateProjectEntry,
|
||||
RenameProjectEntry,
|
||||
DeleteProjectEntry,
|
||||
Follow,
|
||||
FormatBuffers,
|
||||
@ -233,6 +235,7 @@ entity_messages!(
|
||||
ProjectUnshared,
|
||||
ReloadBuffers,
|
||||
RemoveProjectCollaborator,
|
||||
RenameProjectEntry,
|
||||
RequestJoinProject,
|
||||
SaveBuffer,
|
||||
SearchProject,
|
||||
|
Loading…
Reference in New Issue
Block a user