diff --git a/README.en.md b/README.en.md index 335f951..c741029 100644 --- a/README.en.md +++ b/README.en.md @@ -16,6 +16,7 @@ The brand-new v0.3 introduces comprehensive upgrades to both architecture and us - ✨ **Persistent Workspace State**: Your sidebar width adjustments, terminal themes, font settings, and file visibility preferences are completely restored upon your next launch! - ✨ **Intelligent SSH Disconnect/Reconnect**: Greatly enhanced SSH connection resilience. Provides a beautiful and localized overlay for one-click re-connection on unexpected disconnections. - ✨ **Hot-Swappable i18n System**: Deeply optimized internationalization. Switch instantly between English and Simplified Chinese without restarting the application. +- ✨ **Smart Right-Click Actions**: Added intelligent right-click copy and paste for the terminal, featuring a dedicated settings toggle and OS-native keyboard shortcut hints. ## Download diff --git a/README.md b/README.md index 92acc45..d4324a5 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ - ✨ **持久化工作区记忆**:您对侧边栏的尺寸拖拽、终端主题、字体、隐藏文件开关等一切设置都会在下次启动时原样恢复! - ✨ **智能断线重连与状态感知**:全面增强的 SSH 网络异常处理机制,断线后直接提供美观的覆盖层用于一键重连或关闭。 - ✨ **多语言热切换增强**:深度优化的 i18n 系统,包含中文与英文在内的所有界面文案现在均支持热切换。 +- ✨ **智能右键快捷操作**:新增终端右键智能复制与粘贴功能,提供专属设置项自由控制开关,并支持根据不同操作系统自适应显示原生快捷键提示。 ## 下载 diff --git a/locales/en.yml b/locales/en.yml index 026d776..3bb49a6 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -91,4 +91,7 @@ failed: "Failed" cancelled: "Cancelled" session: "Session" -software_builtin: "Built-in" \ No newline at end of file +software_builtin: "Built-in" +retry: "Retry" +right_click_copy_paste: "Right-click Copy/Paste" +copy_paste_hint: "Tip: Regardless of this switch, you can always use %{key} + C/V to copy/paste." \ No newline at end of file diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index a7f1e48..001cde5 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -91,4 +91,7 @@ failed: "失败" cancelled: "已取消" session: "会话" -software_builtin: "软件内置" \ No newline at end of file +software_builtin: "软件内置" +retry: "重试" +right_click_copy_paste: "右键复制/粘贴" +copy_paste_hint: "提示:不管是否开启此开关,都可以使用 %{key} + C/V 快捷键进行复制和粘贴。" \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index b962e6d..d26a71f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -109,6 +109,8 @@ pub struct ConfigFile { pub terminal_font_size: f32, #[serde(default = "default_ui_font_size")] pub ui_font_size: f32, + #[serde(default)] + pub right_click_copy_paste: bool, #[serde(default = "default_ui_font_family")] pub ui_font_family: String, #[serde(default = "default_terminal_font_family")] @@ -302,6 +304,14 @@ impl ConfigStore { self.cache.ui_font_family = family.to_string(); } + pub fn right_click_copy_paste(&self) -> bool { + self.cache.right_click_copy_paste + } + + pub fn set_right_click_copy_paste(&mut self, val: bool) { + self.cache.right_click_copy_paste = val; + } + pub fn terminal_font_family(&self) -> &str { if self.cache.terminal_font_family.is_empty() { "Maple Mono NF CN" diff --git a/src/main.rs b/src/main.rs index 6a6395f..142ae9e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,7 @@ use gpui_component::{ ThemeRegistry, WindowExt as _, button::{Button, ButtonVariants as _}, checkbox::Checkbox, + switch::Switch, dialog::Dialog, h_flex, input::{Input, InputEvent, InputState}, @@ -939,7 +940,7 @@ impl Ashell { self.selector_selection = self.default_selector_index(); window.open_dialog(cx, move |dialog: Dialog, _window, _| { dialog - .title("Open Session") + .title(t!("open_session").to_string()) .w(px(520.)) .on_ok({ let view = view.clone(); @@ -1616,6 +1617,34 @@ impl Ashell { ), ), ) + .child( + v_flex() + .gap_1() + .child( + h_flex() + .items_center() + .gap_3() + .child(div().w(px(240.)).child(t!("right_click_copy_paste").to_string())) + .child( + Switch::new("right-click-copy-paste") + .checked(view.read(cx).config.right_click_copy_paste()) + .on_click(window.listener_for( + &view, + |this, checked, _, cx| { + this.config.set_right_click_copy_paste(*checked); + let _ = this.config.save(); + cx.notify(); + }, + )), + ) + ) + .child( + div() + .text_size(rems(0.85)) + .text_color(cx.theme().muted_foreground) + .child(t!("copy_paste_hint", key = if cfg!(target_os = "macos") { "Command" } else { "Ctrl" }).to_string()) + ) + ) .child( h_flex() .items_center() @@ -2499,6 +2528,43 @@ impl Ashell { cx.notify(); } + fn on_terminal_right_click( + &mut self, + _event: &MouseDownEvent, + window: &mut Window, + cx: &mut Context, + ) { + if !self.config.right_click_copy_paste() { + return; + } + + let mut handled = false; + if let Some(text) = self.active_terminal_selection_text() { + if !text.is_empty() { + cx.write_to_clipboard(gpui::ClipboardItem::new_string(text)); + + let active_id = self.active_tab.clone(); + if let Some(active_id) = active_id { + if let Some(tab) = self.tabs.iter_mut().find(|tab| tab.id == active_id) { + tab.clear_selection(); + } + } + cx.notify(); + handled = true; + } + } + + if !handled { + if let Some(clipboard_item) = cx.read_from_clipboard() { + if let Some(text) = clipboard_item.text() { + if !text.is_empty() { + self.paste_into_terminal(&text, window, cx); + } + } + } + } + } + fn begin_terminal_selection(&mut self, event: &MouseDownEvent, cx: &mut Context) { let click_count = event.click_count.max(1); let selection_type = match click_count { @@ -3913,6 +3979,7 @@ impl Render for Ashell { .track_focus(&self.focus_handle) .key_context(TERMINAL_KEY_CONTEXT) .on_mouse_down(MouseButton::Left, cx.listener(Self::focus_terminal)) + .on_mouse_down(MouseButton::Right, cx.listener(Self::on_terminal_right_click)) .on_mouse_move(cx.listener(Self::on_terminal_mouse_move)) .on_mouse_up(MouseButton::Left, cx.listener(Self::on_terminal_mouse_up)) .on_key_down(cx.listener(Self::on_terminal_key_down)) @@ -4082,14 +4149,14 @@ impl Render for Ashell { .child( Button::new("ssh-connect-progress-retry") .primary() - .label("retry") + .label(t!("retry").to_string()) .on_click(cx.listener(|this, _, _, cx| { this.retry_connection_progress(cx) })), ) .child( Button::new("ssh-connect-progress-close") - .label("cancel") + .label(t!("cancel").to_string()) .on_click(cx.listener(|this, _, _, cx| { this.cancel_connection_progress(cx) })),