From a20b3bae3b4f481bc00c03b5b5aae684d5b793f2 Mon Sep 17 00:00:00 2001 From: Xuz Date: Fri, 2 Oct 2026 20:23:21 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=BB=E6=9C=BA=E5=8F=B3=E9=94=AE=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E5=A2=9E=E5=8A=A0=E5=A4=8D=E5=88=B6=20IP=20=E5=9C=B0?= =?UTF-8?q?=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- docs/manual.md | 2 +- src/host/host_panel.rs | 38 +++++++++++++++++++++++++++-------- src/workspace/recent_hosts.rs | 9 ++++++--- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/docs/manual.md b/docs/manual.md index d949523..93ba123 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -34,7 +34,7 @@ ShellRS 同一时间只运行一个:已经开着时再打开一次,会把已 对话框最后是「备注」,可以写多行(回车换行),记下机器的用途、负责人、到期时间之类,只保存在本地数据库里,不影响连接。 -鼠标停在主机上,行的右侧会显示 `用户@地址:端口`,主机有备注时下面接着显示备注(太长的只显示前 6 行)。右键主机可连接、打开 SFTP、编辑、复制、复制 ID 或删除。「复制 ID」复制的是主机的 16 位随机标识(形如 `Jwg5rHvXCxw89paM`),供其他工具标识这台主机:它在主机创建时生成,改名、改地址都不变,复制出来的主机得到新的 ID,删掉的 ID 不会再分配。没有打开任何标签时,中间区显示「最近连接」列表,双击或回车连接;右键其中的主机弹出同一个菜单,并选中该主机。 +鼠标停在主机上,行的右侧会显示 `用户@地址:端口`,主机有备注时下面接着显示备注(太长的只显示前 6 行)。右键主机可连接、打开 SFTP、编辑、复制、复制 IP 地址(地址是域名时叫「复制主机名」)、复制 ID 或删除。「复制 ID」复制的是主机的 16 位随机标识(形如 `Jwg5rHvXCxw89paM`),供其他工具标识这台主机:它在主机创建时生成,改名、改地址都不变,复制出来的主机得到新的 ID,删掉的 ID 不会再分配。没有打开任何标签时,中间区显示「最近连接」列表,双击或回车连接;右键其中的主机弹出同一个菜单,并选中该主机。 拖动分组或主机到同类项目的上侧 / 下侧可调整同级顺序;主机拖到分组会移入该分组,分组拖到另一个分组的中部会成为子分组。拖到列表空白处可移回根层级。分组始终排在同级主机之前,顺序与分组的展开状态会保存到本地数据库。搜索时暂停拖动排序。 diff --git a/src/host/host_panel.rs b/src/host/host_panel.rs index d4f643f..a67dafa 100644 --- a/src/host/host_panel.rs +++ b/src/host/host_panel.rs @@ -21,15 +21,16 @@ use gpui_kit::prelude::FluentBuilder as _; use gpui_kit::*; use crate::app::{ - CatalogIcon, CollapseAllGroups, ConnectGroup, ConnectHost, ConnectSelected, CopyHostId, - DeleteGroup, DeleteHost, DuplicateHost, EditHost, ExpandAllGroups, HOST_PANEL_CONTEXT, - MoveHostNode, NewChildGroup, NewGroup, NewHost, NewHostInGroup, OpenExplorer, RenameGroup, + CatalogIcon, CollapseAllGroups, ConnectGroup, ConnectHost, ConnectSelected, CopyHostAddress, + CopyHostId, DeleteGroup, DeleteHost, DuplicateHost, EditHost, ExpandAllGroups, + HOST_PANEL_CONTEXT, MoveHostNode, NewChildGroup, NewGroup, NewHost, NewHostInGroup, + OpenExplorer, RenameGroup, }; use crate::shared::{HostMark, RowTooltip, RowTooltips}; use super::{ - GroupId, HostId, HostNode, HostOs, HostStore, NodeDrop, host_tree_items, matches_query, + GroupId, Host, HostId, HostNode, HostOs, HostStore, NodeDrop, host_tree_items, matches_query, }; /// The host list of the left dock: a searchable, grouped tree of hosts. @@ -506,7 +507,12 @@ impl Render for HostPanel { }, ) }) - .context_menu(move |menu, _, _| build_context_menu(clicked_menu.get(), menu)), + .context_menu({ + let store = self.store.clone(); + move |menu, _, cx| { + build_context_menu(clicked_menu.get(), store.read(cx), menu) + } + }), ) .child(self.row_tooltips.overlay()) } @@ -778,7 +784,15 @@ fn valid_drop( /// The menu of one host, wherever it is listed: the host tree and the /// start page's recent hosts both build it here, so they cannot drift. -pub fn host_menu(menu: PopupMenu, id: HostId) -> PopupMenu { +/// Built as it opens, from the host as it is then. +pub fn host_menu(menu: PopupMenu, host: &Host) -> PopupMenu { + let id = host.id; + // Named after what the address is, as in the tabs' menus. + let copy_address = if host.address_is_ip() { + "复制 IP 地址" + } else { + "复制主机名" + }; menu.menu_with_icon( "连接", Icon::new(CatalogIcon::Plug), @@ -801,6 +815,11 @@ pub fn host_menu(menu: PopupMenu, id: HostId) -> PopupMenu { Box::new(DuplicateHost(id)), ) .separator() + .menu_with_icon( + copy_address, + Icon::new(CatalogIcon::ClipboardCopy), + Box::new(CopyHostAddress(id)), + ) .menu_with_icon( "复制 ID", Icon::new(CatalogIcon::ClipboardCopy), @@ -814,9 +833,12 @@ pub fn host_menu(menu: PopupMenu, id: HostId) -> PopupMenu { ) } -fn build_context_menu(node: Option, menu: PopupMenu) -> PopupMenu { +fn build_context_menu(node: Option, store: &HostStore, menu: PopupMenu) -> PopupMenu { match node { - Some(HostNode::Host(id)) => host_menu(menu, id), + Some(HostNode::Host(id)) => match store.host(id) { + Some(host) => host_menu(menu, host), + None => menu, + }, Some(HostNode::Group(id)) => menu .menu_with_icon( "连接组内主机", diff --git a/src/workspace/recent_hosts.rs b/src/workspace/recent_hosts.rs index 95c0045..b889fc4 100644 --- a/src/workspace/recent_hosts.rs +++ b/src/workspace/recent_hosts.rs @@ -144,9 +144,12 @@ impl RecentHosts { .gap_1() .capture_any_mouse_down(move |_, _, _| clear_hit.set(None)) .children(rows.into_iter().map(|row| self.render_row(row, cx))) - .context_menu(move |menu, _, _| match menu_hit.get() { - Some(id) => host_menu(menu, id), - None => menu, + .context_menu({ + let store = self.store.clone(); + move |menu, _, cx| match menu_hit.get().and_then(|id| store.read(cx).host(id)) { + Some(host) => host_menu(menu, host), + None => menu, + } }) }