SFTP 右键菜单:去掉目录的「打开」,加「复制路径」
文件名上的菜单不再有「打开」:目录双击就进入。「上传… / 下载…」下面 加「复制路径」,复制选中项目的完整路径,多个时每行一个,「..」是上级目录。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
40589c9931
commit
f1ac434ebf
6 files changed
+62
-12
No files matched your search
+1
-1
@@ -265,7 +265,7 @@ bash 在 shell 退出时才把这次执行的命令写进历史文件,所以
|
||||
- 框选:在名称右边的空白、大小、类型、时间等列或列表下方按下拖动,画出选框,框住的行被选中;按住 ⌘ 或 Shift 开始拖动则加到已有选择上。拖到列表上下边缘时自动滚动。拖动文件名才是把文件拖到另一侧传输:按下未选中的文件名时只选中它(其他选择取消),拖走的就是它一个;按下已选中的文件名则拖走全部已选。拖动时只有到了另一侧列表上才显示「上传 / 下载 N 个项目」,在自己的列表或其他不能放下的地方,指针显示为禁止。
|
||||
- 键盘:↑↓、Home、End、PageUp、PageDown 移动,加 Shift 扩展;空格或 Insert 切换当前行;⌘A 全选;Tab 切到另一侧。
|
||||
- 右键未选中的名称格会先选中它;右键空白处取消选择并弹出空白处菜单。
|
||||
- 文件名上的菜单:打开(目录)或编辑(文件;图片、SVG 和 Markdown 另有预览,SVG 以外的图片只有预览)、上传… / 下载…、删除、重命名…、属性…。
|
||||
- 文件名上的菜单:编辑(文件;图片、SVG 和 Markdown 另有预览,SVG 以外的图片只有预览)、上传… / 下载…、复制路径(选中项目的完整路径,多个时每行一个)、删除、重命名…、属性…。目录双击进入,菜单里没有「打开」。
|
||||
- 空白处的菜单:前往(上级 / 根 / 主目录 / 后退 / 前进)、刷新、添加路径到书签、新建。
|
||||
|
||||
### 快捷键
|
||||
|
||||
@@ -457,9 +457,14 @@ pub enum ExplorerCommand {
|
||||
OpenDirectory {
|
||||
remote: bool,
|
||||
},
|
||||
/// 复制路径 on the path label: the directory shown.
|
||||
CopyPath {
|
||||
remote: bool,
|
||||
},
|
||||
/// 复制路径 on a row: the selected rows' full paths, one per line.
|
||||
CopySelectedPaths {
|
||||
remote: bool,
|
||||
},
|
||||
/// Move keyboard focus to a pane's file list.
|
||||
FocusPane {
|
||||
remote: bool,
|
||||
|
||||
@@ -704,6 +704,23 @@ impl ExplorerPanel {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(path));
|
||||
}
|
||||
}
|
||||
ExplorerCommand::CopySelectedPaths { remote } => {
|
||||
let pane = self.pane(*remote).read(cx);
|
||||
let paths: Vec<String> = pane
|
||||
.selected_entries(cx)
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
if entry.is_parent() {
|
||||
pane.parent_path()
|
||||
} else {
|
||||
pane.child_path_of(&entry.name)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
if !paths.is_empty() {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(paths.join("\n")));
|
||||
}
|
||||
}
|
||||
ExplorerCommand::FocusPane { remote } => {
|
||||
let focus = self.pane(*remote).read(cx).focus_handle(cx);
|
||||
window.focus(&focus, cx);
|
||||
@@ -1177,6 +1194,9 @@ mod tests {
|
||||
assert!(!needs_connection(&ExplorerCommand::CopyPath {
|
||||
remote: true
|
||||
}));
|
||||
assert!(!needs_connection(&ExplorerCommand::CopySelectedPaths {
|
||||
remote: true
|
||||
}));
|
||||
assert!(!needs_connection(&ExplorerCommand::AddBookmark {
|
||||
remote: true,
|
||||
path: None
|
||||
|
||||
@@ -930,7 +930,6 @@ impl FilePane {
|
||||
PaneMenuState {
|
||||
remote: self.is_remote(),
|
||||
explorer: self.explorer,
|
||||
opens_directory: entries.len() == 1 && entries[0].is_dir(),
|
||||
edits_file: match entries.as_slice() {
|
||||
[entry] if !entry.is_dir() && !entry.is_parent() => {
|
||||
Some(self.child_path_of(&entry.name))
|
||||
|
||||
@@ -14,8 +14,6 @@ pub(super) struct PaneMenuState {
|
||||
pub explorer: ExplorerId,
|
||||
/// The rows the item commands act on (the selection).
|
||||
pub targets: Vec<String>,
|
||||
/// The single selected row is a directory (or a link to one).
|
||||
pub opens_directory: bool,
|
||||
/// The single selected row is a file the editor can try to open: its
|
||||
/// full path.
|
||||
pub edits_file: Option<String>,
|
||||
@@ -43,14 +41,8 @@ impl PaneMenuState {
|
||||
pub(super) fn item_menu(menu: PopupMenu, state: &PaneMenuState) -> PopupMenu {
|
||||
let remote = state.remote;
|
||||
let none = state.targets.is_empty() || !state.can_modify;
|
||||
let menu = if state.opens_directory && state.targets.len() == 1 {
|
||||
menu.menu_with_icon(
|
||||
"打开",
|
||||
Icon::new(IconName::FolderOpen),
|
||||
state.action(ExplorerCommand::Open { remote }),
|
||||
)
|
||||
.separator()
|
||||
} else if let Some(path) = state.edits_file.clone() {
|
||||
// A directory has no 打开: a double-click goes in.
|
||||
let menu = if let Some(path) = state.edits_file.clone() {
|
||||
// A picture is only previewed: as text it would be refused. An SVG
|
||||
// is text, and is both edited and previewed.
|
||||
let kind = state.preview_kind;
|
||||
@@ -93,6 +85,12 @@ pub(super) fn item_menu(menu: PopupMenu, state: &PaneMenuState) -> PopupMenu {
|
||||
state.action(ExplorerCommand::Transfer { remote }),
|
||||
state.targets.is_empty() || !state.can_transfer,
|
||||
)
|
||||
.menu_with_icon_and_disabled(
|
||||
"复制路径",
|
||||
Icon::new(IconName::Copy),
|
||||
state.action(ExplorerCommand::CopySelectedPaths { remote }),
|
||||
state.targets.is_empty(),
|
||||
)
|
||||
.separator()
|
||||
.menu_with_icon_and_disabled(
|
||||
"删除",
|
||||
|
||||
@@ -573,6 +573,34 @@ async fn sftp_path_label_opens_ancestors_and_the_open_directory_dialog(cx: &mut
|
||||
let explorer = workspace.read(cx).explorer(ExplorerId(SFTP_TAB)).unwrap();
|
||||
assert!(explorer.read(cx).local().read(cx).is_current());
|
||||
});
|
||||
|
||||
// 复制路径 from a row's menu: the selected rows' full paths, a line each.
|
||||
cx.update_window(handle.into(), |_, window, cx| {
|
||||
window.render_frame(cx);
|
||||
window
|
||||
.within(("local-pane", SFTP_TAB))
|
||||
.click("name:文件 甲.txt", cx);
|
||||
modified_click(
|
||||
window,
|
||||
("local-pane", SFTP_TAB),
|
||||
"name:目录",
|
||||
gpui_kit::Modifiers::secondary_key(),
|
||||
cx,
|
||||
);
|
||||
window.dispatch_action(
|
||||
Box::new(ExplorerAction::new(
|
||||
ExplorerId(SFTP_TAB),
|
||||
ExplorerCommand::CopySelectedPaths { remote: false },
|
||||
)),
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
assert_eq!(
|
||||
cx.read_from_clipboard().and_then(|item| item.text()),
|
||||
Some("/local/tester/目录\n/local/tester/文件 甲.txt".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui_kit::test]
|
||||
|
||||
Reference in new issue
Block a user