From ff7be61363421cfc987276703a6ae39da854e819 Mon Sep 17 00:00:00 2001 From: Sakyvo Date: Fri, 28 Aug 2026 14:47:19 +0800 Subject: [PATCH 1/2] feat(gpui): permanent mute for multi-line paste confirmation --- nebula_app/src/gpui_shell/settings_pane.rs | 7 +++ nebula_app/src/gpui_shell/terminal/view.rs | 62 ++++++++++++++++++---- nebula_settings/src/lib.rs | 5 ++ 3 files changed, 63 insertions(+), 11 deletions(-) diff --git a/nebula_app/src/gpui_shell/settings_pane.rs b/nebula_app/src/gpui_shell/settings_pane.rs index e560bf8..7c0f094 100644 --- a/nebula_app/src/gpui_shell/settings_pane.rs +++ b/nebula_app/src/gpui_shell/settings_pane.rs @@ -3033,6 +3033,13 @@ impl SettingsPane { self.runtime.copy_on_select, cx, )) + .child(self.switch_row( + "multiline_paste_confirm", + "多行粘贴前询问(关 = 永久直接粘贴)", + "开:粘贴 ≥20 行时弹确认框;关:不弹直接粘贴。", + self.runtime.multiline_paste_confirm, + cx, + )) .child(self.switch_row( "panel_resize", "拖拽调节侧栏宽度", diff --git a/nebula_app/src/gpui_shell/terminal/view.rs b/nebula_app/src/gpui_shell/terminal/view.rs index 0b5f5fe..fa2b09c 100644 --- a/nebula_app/src/gpui_shell/terminal/view.rs +++ b/nebula_app/src/gpui_shell/terminal/view.rs @@ -13,6 +13,8 @@ use gpui::{ Styled as _, TextRun, UTF16Selection, Window, div, point, px, }; use gpui_component::WindowExt as _; +use gpui_component::checkbox::Checkbox; +use gpui_component::Sizable as _; use nebula_settings::CellWidthModeName; use nebula_terminal::event::{Event as TermEvent, Notify as _, OnResize as _, WindowSize}; use nebula_terminal::event_loop::Msg; @@ -1345,7 +1347,10 @@ impl TerminalView { pub fn paste(&mut self, window: &mut Window, cx: &mut Context) { let Some(text) = cx.read_from_clipboard().and_then(|item| item.text()) else { return }; let lines = paste_line_count(&text); - if lines >= PASTE_CONFIRM_LINES { + // 「多行粘贴前询问」关闭时不再弹确认,直接 paste_now。 + if lines >= PASTE_CONFIRM_LINES + && nebula_settings::RuntimeSettings::load().multiline_paste_confirm + { self.confirm_paste(text, lines, window, cx); return; } @@ -1393,22 +1398,57 @@ impl TerminalView { // pane」的 view 释放不掉。 let text = Arc::new(text); let view = cx.entity().downgrade(); + // 「不再询问」勾选状态:确认按钮点上后先用 memory,on_ok UI 上直接写回设置。 + let never_ask_again = Arc::new(std::sync::atomic::AtomicBool::new(false)); + let ok_text2 = ok_text.clone(); + let cancel_text2 = cancel_text.clone(); + let title2 = title.clone(); + let body2 = body.clone(); window.open_dialog(cx, move |dialog, window, _cx| { let text = text.clone(); let view = view.clone(); - confirm_dialog( + let never_ask_again2 = never_ask_again.clone(); + let dialog = confirm_dialog( dialog, window, - title.clone(), - body.clone(), - ok_text.clone(), - cancel_text.clone(), + title2.clone(), + body2.clone(), + ok_text2.clone(), + cancel_text2.clone(), ButtonVariant::Primary, - ) - .on_ok(move |_, _window, cx| { - let _ = view.update(cx, |this, cx| this.paste_now(&text, cx)); - true - }) + ); + let never_ask_again_c = never_ask_again2.clone(); + let view_c = view.clone(); + dialog + .child( + Checkbox::new("nebula-paste-never-ask") + .label(match language { + crate::display::UiLanguage::EnUs => "Don't ask again", + _ => "不再询问", + }) + .checked(false) + .small() + .on_click(move |_v, _window, _cx| { + never_ask_again_c + .store(true, std::sync::atomic::Ordering::Relaxed); + }), + ) + .on_ok({ + let never_ask_again = never_ask_again2.clone(); + let view = view_c; + move |_, _window, cx| { + if never_ask_again.load(std::sync::atomic::Ordering::Relaxed) { + let _ = nebula_settings::persist_keys(&[( + "multiline_paste_confirm", + "0".to_string(), + )]); + } + let _ = view.update(cx, |this, cx| { + this.paste_now(&text, cx) + }); + true + } + }) }); } diff --git a/nebula_settings/src/lib.rs b/nebula_settings/src/lib.rs index 367877f..e3b080f 100644 --- a/nebula_settings/src/lib.rs +++ b/nebula_settings/src/lib.rs @@ -809,6 +809,9 @@ pub struct RuntimeSettings { pub cursor_shape: Option, pub cursor_blink: Option, pub copy_on_select: bool, + /// 多行粘贴确认弹窗:开 = 粘贴 ≥20 行时弹确认;关 = 直接粘贴。 + /// 上游默认开 (true)。 + pub multiline_paste_confirm: bool, pub powerline: bool, /// 默认 shell 的原始 id(`shell=` 原文:powershell/bash/cmd/pwsh/WSL /// 发行版等)。解析归 shell 检测层,这里只做持久化往返。 @@ -902,6 +905,7 @@ impl RuntimeSettings { cursor_shape: raw.value("cursor_shape").and_then(CursorShapeName::from_settings), cursor_blink: raw.bool_on("cursor_blink"), copy_on_select: raw.bool_on("copy_on_select").unwrap_or(false), + multiline_paste_confirm: raw.bool_on("multiline_paste_confirm").unwrap_or(true), powerline: raw.bool_on("powerline").unwrap_or(true), shell: raw.value("shell").or_else(|| raw.value("executor")).map(str::to_owned), startup_directory: raw.value("startup_directory").map(str::to_owned), @@ -1086,6 +1090,7 @@ mod tests { assert_eq!(settings.theme, ThemeName::Nebula); assert_eq!(settings.font_family, None); assert!(!settings.copy_on_select); + assert!(settings.multiline_paste_confirm, "多行粘贴确认默认开(上游兼容)"); assert!(settings.powerline); assert!(settings.ghost); assert_eq!(settings.accept, AcceptKeyName::Both); From a629192ddd8d74d340a54c56a4ded2a4821c8544 Mon Sep 17 00:00:00 2001 From: Sakyvo Date: Fri, 28 Aug 2026 18:11:31 +0800 Subject: [PATCH 2/2] feat(gpui): hide the tab close button via Appearance setting --- nebula_app/src/gpui_shell/settings_pane.rs | 7 +++++ .../src/gpui_shell/workspace/sidebar.rs | 23 +++++++++------- .../src/gpui_shell/workspace/top_tabs.rs | 27 +++++++++++-------- nebula_settings/src/lib.rs | 5 ++++ 4 files changed, 42 insertions(+), 20 deletions(-) diff --git a/nebula_app/src/gpui_shell/settings_pane.rs b/nebula_app/src/gpui_shell/settings_pane.rs index e560bf8..80fd6a1 100644 --- a/nebula_app/src/gpui_shell/settings_pane.rs +++ b/nebula_app/src/gpui_shell/settings_pane.rs @@ -2249,6 +2249,13 @@ impl SettingsPane { )); let interface = self .group("界面", cx) + .child(self.switch_row( + "tab_close_visible", + "显示标签关闭按钮", + "关闭后侧栏与顶栏的标签都不再渲染叉号,中键点标签仍可关闭。", + self.runtime.tab_close_visible, + cx, + )) .child(self.select_row( "language", "语言", diff --git a/nebula_app/src/gpui_shell/workspace/sidebar.rs b/nebula_app/src/gpui_shell/workspace/sidebar.rs index a0a1c85..d274a58 100644 --- a/nebula_app/src/gpui_shell/workspace/sidebar.rs +++ b/nebula_app/src/gpui_shell/workspace/sidebar.rs @@ -493,15 +493,20 @@ impl NebulaWorkspace { .items_center() .invisible() .group_hover(hover_group, |slot| slot.visible()) - .child( - Button::new(("close-tab", ix)) - .icon(IconName::Close) - .ghost() - .xsmall() - .on_click(cx.listener(move |this, _, window, cx| { - cx.stop_propagation(); - this.request_close_tab(ix, window, cx); - })), + .when( + nebula_settings::RuntimeSettings::load().tab_close_visible, + |slot| { + slot.child( + Button::new(("close-tab", ix)) + .icon(IconName::Close) + .ghost() + .xsmall() + .on_click(cx.listener(move |this, _, window, cx| { + cx.stop_propagation(); + this.request_close_tab(ix, window, cx); + })), + ) + }, ), ), ) diff --git a/nebula_app/src/gpui_shell/workspace/top_tabs.rs b/nebula_app/src/gpui_shell/workspace/top_tabs.rs index 8e29156..2d260e0 100644 --- a/nebula_app/src/gpui_shell/workspace/top_tabs.rs +++ b/nebula_app/src/gpui_shell/workspace/top_tabs.rs @@ -408,17 +408,22 @@ impl NebulaWorkspace { .items_center() .invisible() .group_hover(hover_group, |slot| slot.visible()) - .child( - Button::new(("top-close-tab", ix)) - .icon(IconName::Close) - .ghost() - .xsmall() - .on_click(cx.listener( - move |this, _, window, cx| { - cx.stop_propagation(); - this.request_close_tab(ix, window, cx); - }, - )), + .when( + nebula_settings::RuntimeSettings::load().tab_close_visible, + |slot| { + slot.child( + Button::new(("top-close-tab", ix)) + .icon(IconName::Close) + .ghost() + .xsmall() + .on_click(cx.listener( + move |this, _, window, cx| { + cx.stop_propagation(); + this.request_close_tab(ix, window, cx); + }, + )), + ) + }, ), ), ) diff --git a/nebula_settings/src/lib.rs b/nebula_settings/src/lib.rs index 367877f..a89bb64 100644 --- a/nebula_settings/src/lib.rs +++ b/nebula_settings/src/lib.rs @@ -809,6 +809,9 @@ pub struct RuntimeSettings { pub cursor_shape: Option, pub cursor_blink: Option, pub copy_on_select: bool, + /// 标签页关闭按钮(叉号)是否渲染:关 = 不渲染,仍可用中键关闭。 + /// 上游默认开 (true)。 + pub tab_close_visible: bool, pub powerline: bool, /// 默认 shell 的原始 id(`shell=` 原文:powershell/bash/cmd/pwsh/WSL /// 发行版等)。解析归 shell 检测层,这里只做持久化往返。 @@ -902,6 +905,7 @@ impl RuntimeSettings { cursor_shape: raw.value("cursor_shape").and_then(CursorShapeName::from_settings), cursor_blink: raw.bool_on("cursor_blink"), copy_on_select: raw.bool_on("copy_on_select").unwrap_or(false), + tab_close_visible: raw.bool_on("tab_close_visible").unwrap_or(true), powerline: raw.bool_on("powerline").unwrap_or(true), shell: raw.value("shell").or_else(|| raw.value("executor")).map(str::to_owned), startup_directory: raw.value("startup_directory").map(str::to_owned), @@ -1086,6 +1090,7 @@ mod tests { assert_eq!(settings.theme, ThemeName::Nebula); assert_eq!(settings.font_family, None); assert!(!settings.copy_on_select); + assert!(settings.tab_close_visible, "标签关闭按钮默认可见(上游兼容)"); assert!(settings.powerline); assert!(settings.ghost); assert_eq!(settings.accept, AcceptKeyName::Both);