mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-19 02:17:35 +03:00
project_panel: Add support for copy/paste between different worktrees (#15396)
Closes https://github.com/zed-industries/zed/issues/5362 Release Notes: - Added a way to copy/cut-paste between different worktrees ([#5362](https://github.com/zed-industries/zed/issues/5362))
This commit is contained in:
parent
81eb594037
commit
a5b82b2bf3
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -8311,9 +8311,9 @@ dependencies = [
|
|||||||
"db",
|
"db",
|
||||||
"editor",
|
"editor",
|
||||||
"file_icons",
|
"file_icons",
|
||||||
"futures 0.3.30",
|
|
||||||
"git",
|
"git",
|
||||||
"gpui",
|
"gpui",
|
||||||
|
"indexmap 1.9.3",
|
||||||
"language",
|
"language",
|
||||||
"menu",
|
"menu",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
|
@ -3178,7 +3178,7 @@ async fn test_fs_operations(
|
|||||||
|
|
||||||
project_b
|
project_b
|
||||||
.update(cx_b, |project, cx| {
|
.update(cx_b, |project, cx| {
|
||||||
project.copy_entry(entry.id, Path::new("f.txt"), cx)
|
project.copy_entry(entry.id, None, Path::new("f.txt"), cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -1607,6 +1607,7 @@ impl Project {
|
|||||||
pub fn copy_entry(
|
pub fn copy_entry(
|
||||||
&mut self,
|
&mut self,
|
||||||
entry_id: ProjectEntryId,
|
entry_id: ProjectEntryId,
|
||||||
|
relative_worktree_source_path: Option<PathBuf>,
|
||||||
new_path: impl Into<Arc<Path>>,
|
new_path: impl Into<Arc<Path>>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Option<Entry>>> {
|
) -> Task<Result<Option<Entry>>> {
|
||||||
@ -1614,7 +1615,7 @@ impl Project {
|
|||||||
return Task::ready(Ok(None));
|
return Task::ready(Ok(None));
|
||||||
};
|
};
|
||||||
worktree.update(cx, |worktree, cx| {
|
worktree.update(cx, |worktree, cx| {
|
||||||
worktree.copy_entry(entry_id, new_path, cx)
|
worktree.copy_entry(entry_id, relative_worktree_source_path, new_path, cx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -10986,7 +10987,7 @@ fn serialize_symbol(symbol: &Symbol) -> proto::Symbol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn relativize_path(base: &Path, path: &Path) -> PathBuf {
|
pub fn relativize_path(base: &Path, path: &Path) -> PathBuf {
|
||||||
let mut path_components = path.components();
|
let mut path_components = path.components();
|
||||||
let mut base_components = base.components();
|
let mut base_components = base.components();
|
||||||
let mut components: Vec<Component> = Vec::new();
|
let mut components: Vec<Component> = Vec::new();
|
||||||
|
@ -18,7 +18,7 @@ collections.workspace = true
|
|||||||
db.workspace = true
|
db.workspace = true
|
||||||
editor.workspace = true
|
editor.workspace = true
|
||||||
file_icons.workspace = true
|
file_icons.workspace = true
|
||||||
futures.workspace = true
|
indexmap.workspace = true
|
||||||
git.workspace = true
|
git.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
menu.workspace = true
|
menu.workspace = true
|
||||||
|
@ -23,8 +23,12 @@ use gpui::{
|
|||||||
PromptLevel, Render, Stateful, Styled, Subscription, Task, UniformListScrollHandle, View,
|
PromptLevel, Render, Stateful, Styled, Subscription, Task, UniformListScrollHandle, View,
|
||||||
ViewContext, VisualContext as _, WeakView, WindowContext,
|
ViewContext, VisualContext as _, WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
|
use indexmap::IndexMap;
|
||||||
use menu::{Confirm, SelectFirst, SelectLast, SelectNext, SelectPrev};
|
use menu::{Confirm, SelectFirst, SelectLast, SelectNext, SelectPrev};
|
||||||
use project::{Entry, EntryKind, Fs, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
|
use project::{
|
||||||
|
relativize_path, Entry, EntryKind, Fs, Project, ProjectEntryId, ProjectPath, Worktree,
|
||||||
|
WorktreeId,
|
||||||
|
};
|
||||||
use project_panel_settings::{ProjectPanelDockPosition, ProjectPanelSettings, ShowScrollbar};
|
use project_panel_settings::{ProjectPanelDockPosition, ProjectPanelSettings, ShowScrollbar};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
@ -495,23 +499,8 @@ impl ProjectPanel {
|
|||||||
.action("Copy", Box::new(Copy))
|
.action("Copy", Box::new(Copy))
|
||||||
.action("Duplicate", Box::new(Duplicate))
|
.action("Duplicate", Box::new(Duplicate))
|
||||||
// TODO: Paste should always be visible, cbut disabled when clipboard is empty
|
// TODO: Paste should always be visible, cbut disabled when clipboard is empty
|
||||||
.when_some(self.clipboard.as_ref(), |menu, entry| {
|
.when(self.clipboard.as_ref().is_some(), |menu| {
|
||||||
let entries_for_worktree_id = (SelectedEntry {
|
menu.action("Paste", Box::new(Paste))
|
||||||
worktree_id,
|
|
||||||
entry_id: ProjectEntryId::MIN,
|
|
||||||
})
|
|
||||||
..(SelectedEntry {
|
|
||||||
worktree_id,
|
|
||||||
entry_id: ProjectEntryId::MAX,
|
|
||||||
});
|
|
||||||
menu.when(
|
|
||||||
entry
|
|
||||||
.items()
|
|
||||||
.range(entries_for_worktree_id)
|
|
||||||
.next()
|
|
||||||
.is_some(),
|
|
||||||
|menu| menu.action("Paste", Box::new(Paste)),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.separator()
|
.separator()
|
||||||
.action("Copy Path", Box::new(CopyPath))
|
.action("Copy Path", Box::new(CopyPath))
|
||||||
@ -1304,46 +1293,99 @@ impl ProjectPanel {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.filter(|clipboard| !clipboard.items().is_empty())?;
|
.filter(|clipboard| !clipboard.items().is_empty())?;
|
||||||
|
|
||||||
let mut tasks = Vec::new();
|
enum PasteTask {
|
||||||
|
Rename(Task<Result<CreatedEntry>>),
|
||||||
|
Copy(Task<Result<Option<Entry>>>),
|
||||||
|
}
|
||||||
|
let mut paste_entry_tasks: IndexMap<(ProjectEntryId, bool), PasteTask> =
|
||||||
|
IndexMap::default();
|
||||||
|
let clip_is_cut = clipboard_entries.is_cut();
|
||||||
for clipboard_entry in clipboard_entries.items() {
|
for clipboard_entry in clipboard_entries.items() {
|
||||||
if clipboard_entry.worktree_id != worktree_id {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let new_path =
|
let new_path =
|
||||||
self.create_paste_path(clipboard_entry, self.selected_entry_handle(cx)?, cx)?;
|
self.create_paste_path(clipboard_entry, self.selected_entry_handle(cx)?, cx)?;
|
||||||
if clipboard_entries.is_cut() {
|
let clip_entry_id = clipboard_entry.entry_id;
|
||||||
self.project
|
let is_same_worktree = clipboard_entry.worktree_id == worktree_id;
|
||||||
.update(cx, |project, cx| {
|
let relative_worktree_source_path = if !is_same_worktree {
|
||||||
project.rename_entry(clipboard_entry.entry_id, new_path, cx)
|
let target_base_path = worktree.read(cx).abs_path();
|
||||||
})
|
let clipboard_project_path =
|
||||||
.detach_and_log_err(cx);
|
self.project.read(cx).path_for_entry(clip_entry_id, cx)?;
|
||||||
|
let clipboard_abs_path = self
|
||||||
|
.project
|
||||||
|
.read(cx)
|
||||||
|
.absolute_path(&clipboard_project_path, cx)?;
|
||||||
|
Some(relativize_path(
|
||||||
|
&target_base_path,
|
||||||
|
clipboard_abs_path.as_path(),
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let task = if clip_is_cut && is_same_worktree {
|
||||||
let task = self.project.update(cx, |project, cx| {
|
let task = self.project.update(cx, |project, cx| {
|
||||||
project.copy_entry(clipboard_entry.entry_id, new_path, cx)
|
project.rename_entry(clip_entry_id, new_path, cx)
|
||||||
});
|
});
|
||||||
tasks.push(task);
|
PasteTask::Rename(task)
|
||||||
}
|
} else {
|
||||||
|
let entry_id = if is_same_worktree {
|
||||||
|
clip_entry_id
|
||||||
|
} else {
|
||||||
|
entry.id
|
||||||
|
};
|
||||||
|
let task = self.project.update(cx, |project, cx| {
|
||||||
|
project.copy_entry(entry_id, relative_worktree_source_path, new_path, cx)
|
||||||
|
});
|
||||||
|
PasteTask::Copy(task)
|
||||||
|
};
|
||||||
|
let needs_delete = !is_same_worktree && clip_is_cut;
|
||||||
|
paste_entry_tasks.insert((clip_entry_id, needs_delete), task);
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.spawn(|project_panel, mut cx| async move {
|
cx.spawn(|project_panel, mut cx| async move {
|
||||||
let entry_ids = futures::future::join_all(tasks).await;
|
let mut last_succeed = None;
|
||||||
if let Some(Some(entry)) = entry_ids
|
let mut need_delete_ids = Vec::new();
|
||||||
.into_iter()
|
for ((entry_id, need_delete), task) in paste_entry_tasks.into_iter() {
|
||||||
.rev()
|
match task {
|
||||||
.find_map(|entry_id| entry_id.ok())
|
PasteTask::Rename(task) => {
|
||||||
{
|
if let Some(CreatedEntry::Included(entry)) = task.await.log_err() {
|
||||||
|
last_succeed = Some(entry.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PasteTask::Copy(task) => {
|
||||||
|
if let Some(Some(entry)) = task.await.log_err() {
|
||||||
|
last_succeed = Some(entry.id);
|
||||||
|
if need_delete {
|
||||||
|
need_delete_ids.push(entry_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// update selection
|
||||||
|
if let Some(entry_id) = last_succeed {
|
||||||
project_panel
|
project_panel
|
||||||
.update(&mut cx, |project_panel, _cx| {
|
.update(&mut cx, |project_panel, _cx| {
|
||||||
project_panel.selection = Some(SelectedEntry {
|
project_panel.selection = Some(SelectedEntry {
|
||||||
worktree_id,
|
worktree_id,
|
||||||
entry_id: entry.id,
|
entry_id,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
// remove entry for cut in difference worktree
|
||||||
|
for entry_id in need_delete_ids {
|
||||||
|
project_panel
|
||||||
|
.update(&mut cx, |project_panel, cx| {
|
||||||
|
project_panel
|
||||||
|
.project
|
||||||
|
.update(cx, |project, cx| project.delete_entry(entry_id, true, cx))
|
||||||
|
.ok_or_else(|| anyhow!("no such entry"))
|
||||||
|
})??
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
})
|
})
|
||||||
.detach();
|
.detach_and_log_err(cx);
|
||||||
|
|
||||||
self.expand_entry(worktree_id, entry.id, cx);
|
self.expand_entry(worktree_id, entry.id, cx);
|
||||||
Some(())
|
Some(())
|
||||||
@ -1842,7 +1884,7 @@ impl ProjectPanel {
|
|||||||
)?;
|
)?;
|
||||||
self.project
|
self.project
|
||||||
.update(cx, |project, cx| {
|
.update(cx, |project, cx| {
|
||||||
project.copy_entry(selection.entry_id, new_path, cx)
|
project.copy_entry(selection.entry_id, None, new_path, cx)
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx)
|
.detach_and_log_err(cx)
|
||||||
}
|
}
|
||||||
@ -3675,6 +3717,236 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_cut_paste_between_different_worktrees(cx: &mut gpui::TestAppContext) {
|
||||||
|
init_test(cx);
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.executor().clone());
|
||||||
|
fs.insert_tree(
|
||||||
|
"/root1",
|
||||||
|
json!({
|
||||||
|
"one.txt": "",
|
||||||
|
"two.txt": "",
|
||||||
|
"three.txt": "",
|
||||||
|
"a": {
|
||||||
|
"0": { "q": "", "r": "", "s": "" },
|
||||||
|
"1": { "t": "", "u": "" },
|
||||||
|
"2": { "v": "", "w": "", "x": "", "y": "" },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
fs.insert_tree(
|
||||||
|
"/root2",
|
||||||
|
json!({
|
||||||
|
"one.txt": "",
|
||||||
|
"two.txt": "",
|
||||||
|
"four.txt": "",
|
||||||
|
"b": {
|
||||||
|
"3": { "Q": "" },
|
||||||
|
"4": { "R": "", "S": "", "T": "", "U": "" },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
|
||||||
|
let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||||
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
|
let panel = workspace
|
||||||
|
.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
select_path(&panel, "root1/three.txt", cx);
|
||||||
|
panel.update(cx, |panel, cx| {
|
||||||
|
panel.cut(&Default::default(), cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
select_path(&panel, "root2/one.txt", cx);
|
||||||
|
panel.update(cx, |panel, cx| {
|
||||||
|
panel.select_next(&Default::default(), cx);
|
||||||
|
panel.paste(&Default::default(), cx);
|
||||||
|
});
|
||||||
|
cx.executor().run_until_parked();
|
||||||
|
assert_eq!(
|
||||||
|
visible_entries_as_strings(&panel, 0..50, cx),
|
||||||
|
&[
|
||||||
|
//
|
||||||
|
"v root1",
|
||||||
|
" > a",
|
||||||
|
" one.txt",
|
||||||
|
" two.txt",
|
||||||
|
"v root2",
|
||||||
|
" > b",
|
||||||
|
" four.txt",
|
||||||
|
" one.txt",
|
||||||
|
" three.txt <== selected",
|
||||||
|
" two.txt",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
select_path(&panel, "root1/a", cx);
|
||||||
|
panel.update(cx, |panel, cx| {
|
||||||
|
panel.cut(&Default::default(), cx);
|
||||||
|
});
|
||||||
|
select_path(&panel, "root2/two.txt", cx);
|
||||||
|
panel.update(cx, |panel, cx| {
|
||||||
|
panel.select_next(&Default::default(), cx);
|
||||||
|
panel.paste(&Default::default(), cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.executor().run_until_parked();
|
||||||
|
assert_eq!(
|
||||||
|
visible_entries_as_strings(&panel, 0..50, cx),
|
||||||
|
&[
|
||||||
|
//
|
||||||
|
"v root1",
|
||||||
|
" one.txt",
|
||||||
|
" two.txt",
|
||||||
|
"v root2",
|
||||||
|
" > a <== selected",
|
||||||
|
" > b",
|
||||||
|
" four.txt",
|
||||||
|
" one.txt",
|
||||||
|
" three.txt",
|
||||||
|
" two.txt",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_copy_paste_between_different_worktrees(cx: &mut gpui::TestAppContext) {
|
||||||
|
init_test(cx);
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.executor().clone());
|
||||||
|
fs.insert_tree(
|
||||||
|
"/root1",
|
||||||
|
json!({
|
||||||
|
"one.txt": "",
|
||||||
|
"two.txt": "",
|
||||||
|
"three.txt": "",
|
||||||
|
"a": {
|
||||||
|
"0": { "q": "", "r": "", "s": "" },
|
||||||
|
"1": { "t": "", "u": "" },
|
||||||
|
"2": { "v": "", "w": "", "x": "", "y": "" },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
fs.insert_tree(
|
||||||
|
"/root2",
|
||||||
|
json!({
|
||||||
|
"one.txt": "",
|
||||||
|
"two.txt": "",
|
||||||
|
"four.txt": "",
|
||||||
|
"b": {
|
||||||
|
"3": { "Q": "" },
|
||||||
|
"4": { "R": "", "S": "", "T": "", "U": "" },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
|
||||||
|
let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||||
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
|
let panel = workspace
|
||||||
|
.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
select_path(&panel, "root1/three.txt", cx);
|
||||||
|
panel.update(cx, |panel, cx| {
|
||||||
|
panel.copy(&Default::default(), cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
select_path(&panel, "root2/one.txt", cx);
|
||||||
|
panel.update(cx, |panel, cx| {
|
||||||
|
panel.select_next(&Default::default(), cx);
|
||||||
|
panel.paste(&Default::default(), cx);
|
||||||
|
});
|
||||||
|
cx.executor().run_until_parked();
|
||||||
|
assert_eq!(
|
||||||
|
visible_entries_as_strings(&panel, 0..50, cx),
|
||||||
|
&[
|
||||||
|
//
|
||||||
|
"v root1",
|
||||||
|
" > a",
|
||||||
|
" one.txt",
|
||||||
|
" three.txt",
|
||||||
|
" two.txt",
|
||||||
|
"v root2",
|
||||||
|
" > b",
|
||||||
|
" four.txt",
|
||||||
|
" one.txt",
|
||||||
|
" three.txt <== selected",
|
||||||
|
" two.txt",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
select_path(&panel, "root1/three.txt", cx);
|
||||||
|
panel.update(cx, |panel, cx| {
|
||||||
|
panel.copy(&Default::default(), cx);
|
||||||
|
});
|
||||||
|
select_path(&panel, "root2/two.txt", cx);
|
||||||
|
panel.update(cx, |panel, cx| {
|
||||||
|
panel.select_next(&Default::default(), cx);
|
||||||
|
panel.paste(&Default::default(), cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.executor().run_until_parked();
|
||||||
|
assert_eq!(
|
||||||
|
visible_entries_as_strings(&panel, 0..50, cx),
|
||||||
|
&[
|
||||||
|
//
|
||||||
|
"v root1",
|
||||||
|
" > a",
|
||||||
|
" one.txt",
|
||||||
|
" three.txt",
|
||||||
|
" two.txt",
|
||||||
|
"v root2",
|
||||||
|
" > b",
|
||||||
|
" four.txt",
|
||||||
|
" one.txt",
|
||||||
|
" three copy.txt <== selected",
|
||||||
|
" three.txt",
|
||||||
|
" two.txt",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
select_path(&panel, "root1/a", cx);
|
||||||
|
panel.update(cx, |panel, cx| {
|
||||||
|
panel.copy(&Default::default(), cx);
|
||||||
|
});
|
||||||
|
select_path(&panel, "root2/two.txt", cx);
|
||||||
|
panel.update(cx, |panel, cx| {
|
||||||
|
panel.select_next(&Default::default(), cx);
|
||||||
|
panel.paste(&Default::default(), cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.executor().run_until_parked();
|
||||||
|
assert_eq!(
|
||||||
|
visible_entries_as_strings(&panel, 0..50, cx),
|
||||||
|
&[
|
||||||
|
//
|
||||||
|
"v root1",
|
||||||
|
" > a",
|
||||||
|
" one.txt",
|
||||||
|
" three.txt",
|
||||||
|
" two.txt",
|
||||||
|
"v root2",
|
||||||
|
" > a <== selected",
|
||||||
|
" > b",
|
||||||
|
" four.txt",
|
||||||
|
" one.txt",
|
||||||
|
" three copy.txt",
|
||||||
|
" three.txt",
|
||||||
|
" two.txt",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_copy_paste_directory(cx: &mut gpui::TestAppContext) {
|
async fn test_copy_paste_directory(cx: &mut gpui::TestAppContext) {
|
||||||
init_test(cx);
|
init_test(cx);
|
||||||
@ -4360,9 +4632,9 @@ mod tests {
|
|||||||
&[
|
&[
|
||||||
"v project_root",
|
"v project_root",
|
||||||
" v dir_1",
|
" v dir_1",
|
||||||
" v nested_dir <== selected",
|
" v nested_dir",
|
||||||
" file_1.py <== marked",
|
" file_1.py <== marked",
|
||||||
" file_a.py <== marked",
|
" file_a.py <== selected <== marked",
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
cx.simulate_modifiers_change(modifiers_with_shift);
|
cx.simulate_modifiers_change(modifiers_with_shift);
|
||||||
|
@ -654,6 +654,7 @@ message CopyProjectEntry {
|
|||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
uint64 entry_id = 2;
|
uint64 entry_id = 2;
|
||||||
string new_path = 3;
|
string new_path = 3;
|
||||||
|
optional string relative_worktree_source_path = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DeleteProjectEntry {
|
message DeleteProjectEntry {
|
||||||
|
@ -786,16 +786,26 @@ impl Worktree {
|
|||||||
pub fn copy_entry(
|
pub fn copy_entry(
|
||||||
&mut self,
|
&mut self,
|
||||||
entry_id: ProjectEntryId,
|
entry_id: ProjectEntryId,
|
||||||
|
relative_worktree_source_path: Option<PathBuf>,
|
||||||
new_path: impl Into<Arc<Path>>,
|
new_path: impl Into<Arc<Path>>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Option<Entry>>> {
|
) -> Task<Result<Option<Entry>>> {
|
||||||
let new_path = new_path.into();
|
let new_path = new_path.into();
|
||||||
match self {
|
match self {
|
||||||
Worktree::Local(this) => this.copy_entry(entry_id, new_path, cx),
|
Worktree::Local(this) => {
|
||||||
|
this.copy_entry(entry_id, relative_worktree_source_path, new_path, cx)
|
||||||
|
}
|
||||||
Worktree::Remote(this) => {
|
Worktree::Remote(this) => {
|
||||||
|
let relative_worktree_source_path =
|
||||||
|
if let Some(relative_worktree_source_path) = relative_worktree_source_path {
|
||||||
|
Some(relative_worktree_source_path.to_string_lossy().into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
let response = this.client.request(proto::CopyProjectEntry {
|
let response = this.client.request(proto::CopyProjectEntry {
|
||||||
project_id: this.project_id,
|
project_id: this.project_id,
|
||||||
entry_id: entry_id.to_proto(),
|
entry_id: entry_id.to_proto(),
|
||||||
|
relative_worktree_source_path,
|
||||||
new_path: new_path.to_string_lossy().into(),
|
new_path: new_path.to_string_lossy().into(),
|
||||||
});
|
});
|
||||||
cx.spawn(move |this, mut cx| async move {
|
cx.spawn(move |this, mut cx| async move {
|
||||||
@ -948,10 +958,18 @@ impl Worktree {
|
|||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<proto::ProjectEntryResponse> {
|
) -> Result<proto::ProjectEntryResponse> {
|
||||||
let (scan_id, task) = this.update(&mut cx, |this, cx| {
|
let (scan_id, task) = this.update(&mut cx, |this, cx| {
|
||||||
|
let relative_worktree_source_path = if let Some(relative_worktree_source_path) =
|
||||||
|
request.relative_worktree_source_path
|
||||||
|
{
|
||||||
|
Some(PathBuf::from(relative_worktree_source_path))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
(
|
(
|
||||||
this.scan_id(),
|
this.scan_id(),
|
||||||
this.copy_entry(
|
this.copy_entry(
|
||||||
ProjectEntryId::from_proto(request.entry_id),
|
ProjectEntryId::from_proto(request.entry_id),
|
||||||
|
relative_worktree_source_path,
|
||||||
PathBuf::from(request.new_path),
|
PathBuf::from(request.new_path),
|
||||||
cx,
|
cx,
|
||||||
),
|
),
|
||||||
@ -1529,6 +1547,7 @@ impl LocalWorktree {
|
|||||||
fn copy_entry(
|
fn copy_entry(
|
||||||
&self,
|
&self,
|
||||||
entry_id: ProjectEntryId,
|
entry_id: ProjectEntryId,
|
||||||
|
relative_worktree_source_path: Option<PathBuf>,
|
||||||
new_path: impl Into<Arc<Path>>,
|
new_path: impl Into<Arc<Path>>,
|
||||||
cx: &mut ModelContext<Worktree>,
|
cx: &mut ModelContext<Worktree>,
|
||||||
) -> Task<Result<Option<Entry>>> {
|
) -> Task<Result<Option<Entry>>> {
|
||||||
@ -1537,7 +1556,12 @@ impl LocalWorktree {
|
|||||||
None => return Task::ready(Ok(None)),
|
None => return Task::ready(Ok(None)),
|
||||||
};
|
};
|
||||||
let new_path = new_path.into();
|
let new_path = new_path.into();
|
||||||
let abs_old_path = self.absolutize(&old_path);
|
let abs_old_path =
|
||||||
|
if let Some(relative_worktree_source_path) = relative_worktree_source_path {
|
||||||
|
Ok(self.abs_path().join(relative_worktree_source_path))
|
||||||
|
} else {
|
||||||
|
self.absolutize(&old_path)
|
||||||
|
};
|
||||||
let abs_new_path = self.absolutize(&new_path);
|
let abs_new_path = self.absolutize(&new_path);
|
||||||
let fs = self.fs.clone();
|
let fs = self.fs.clone();
|
||||||
let copy = cx.background_executor().spawn(async move {
|
let copy = cx.background_executor().spawn(async move {
|
||||||
|
Loading…
Reference in New Issue
Block a user