diff --git a/.github/workflows/preview-packages.yml b/.github/workflows/preview-packages.yml index 5289662..614e12c 100644 --- a/.github/workflows/preview-packages.yml +++ b/.github/workflows/preview-packages.yml @@ -151,7 +151,7 @@ jobs: run: python3 -m unittest scripts.tests.test_shell_integration -v - name: Test SSH confirmation and password dialogs - run: cargo test --locked -p nebula --features gpui-test-support ssh_dialog + run: cargo test --locked -p nebula --features gpui-test-support dialog - name: Build GPUI release binary run: cargo build --locked --release -p nebula --bin nebula --features gpui-shell @@ -306,7 +306,7 @@ jobs: run: python3 -m unittest scripts.tests.test_shell_integration -v - name: Test SSH confirmation and password dialogs - run: cargo test --locked -p nebula --features gpui-test-support ssh_dialog + run: cargo test --locked -p nebula --features gpui-test-support dialog - name: Build GPUI release binary run: cargo build --locked --release -p nebula --bin nebula --features gpui-shell @@ -439,7 +439,7 @@ jobs: - name: Test workspace natively run: cargo test --locked --workspace - name: Test SSH confirmation and password dialogs - run: cargo test --locked -p nebula --features gpui-test-support ssh_dialog + run: cargo test --locked -p nebula --features gpui-test-support dialog - name: Build GPUI comparison binary run: cargo build --locked --release -p nebula --bin nebula --features gpui-shell - name: Run Windows conformance diff --git a/Cargo.lock b/Cargo.lock index 8a659ea..12bdde5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3180,7 +3180,7 @@ dependencies = [ [[package]] name = "gpui-component" version = "0.5.2" -source = "git+https://github.com/Kuddev/gpui-component?rev=c20dbef8ed0d4c0fbebbf2e8f02d39e4fa0ac0a7#c20dbef8ed0d4c0fbebbf2e8f02d39e4fa0ac0a7" +source = "git+https://github.com/Kuddev/gpui-component?rev=fa3763c681dba5b3ea028395148488e1281d478d#fa3763c681dba5b3ea028395148488e1281d478d" dependencies = [ "aho-corasick", "anyhow", @@ -3263,7 +3263,7 @@ dependencies = [ [[package]] name = "gpui-component-assets" version = "0.5.1" -source = "git+https://github.com/Kuddev/gpui-component?rev=c20dbef8ed0d4c0fbebbf2e8f02d39e4fa0ac0a7#c20dbef8ed0d4c0fbebbf2e8f02d39e4fa0ac0a7" +source = "git+https://github.com/Kuddev/gpui-component?rev=fa3763c681dba5b3ea028395148488e1281d478d#fa3763c681dba5b3ea028395148488e1281d478d" dependencies = [ "anyhow", "gpui", @@ -3277,7 +3277,7 @@ dependencies = [ [[package]] name = "gpui-component-macros" version = "0.5.1" -source = "git+https://github.com/Kuddev/gpui-component?rev=c20dbef8ed0d4c0fbebbf2e8f02d39e4fa0ac0a7#c20dbef8ed0d4c0fbebbf2e8f02d39e4fa0ac0a7" +source = "git+https://github.com/Kuddev/gpui-component?rev=fa3763c681dba5b3ea028395148488e1281d478d#fa3763c681dba5b3ea028395148488e1281d478d" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index ba4b2fe..629b173 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,6 +92,9 @@ winit = { path = "third_party/winit-0.30.13" } # Kuddev/gpui-component nebula-v1.16.1-math # @ c20dbef8ed0d4c0fbebbf2e8f02d39e4fa0ac0a7 # (4eb39176 owned base + 数学薄桥、GFM TextView 语义和紧凑阴影 helper) +# Kuddev/gpui-component nebula-v1.16.1-test-support +# @ fa3763c681dba5b3ea028395148488e1281d478d +# (c20dbef8 基线 + 下游虚拟窗口测试支持) # # 所有 Zed 依赖统一使用 Kuddev URL:Cargo 的 source identity 包含 URL,应用 # 与 component 内部若混用官方/自有 URL,即使 SHA 相同也会解析成两套 gpui @@ -151,7 +154,13 @@ winit = { path = "third_party/winit-0.30.13" } # update_scroll_offset,边界 clamp 现成)。补丁 4 修的是键盘移动那条路, # 这条修的是鼠标拖选,两者都要。上游 PR 候选,合并即撤。 # -# 当前精确引用已包含补丁 1-3;Input 补丁 4-5 仍保持独立,尚未带入本次升级。 +# 6. 下游虚拟窗口测试支持(2026-09-05):crates/ui/Cargo.toml 增加显式 +# test-support feature;src/lib.rs 与 src/root.rs 在该模式下不安装需要 +# 原生窗口句柄的 macOS 无障碍转发器。普通构建的无障碍接线不变。 +# tests/root_test_window.rs 验证以依赖方式编译的 Root 可使用虚拟窗口; +# 上游提供等价的下游 headless 测试支持后撤销本补丁。 +# +# 当前精确引用已包含补丁 1-3 与 6;Input 补丁 4-5 仍保持独立,尚未带入本次升级。 gpui = { git = "https://github.com/Kuddev/zed", rev = "eb8e1c8b5502b7007465fbbc465f4a736fa39210", version = "=0.2.2" } -gpui-component = { git = "https://github.com/Kuddev/gpui-component", rev = "c20dbef8ed0d4c0fbebbf2e8f02d39e4fa0ac0a7", version = "=0.5.2" } -gpui-component-assets = { git = "https://github.com/Kuddev/gpui-component", rev = "c20dbef8ed0d4c0fbebbf2e8f02d39e4fa0ac0a7", version = "=0.5.1" } +gpui-component = { git = "https://github.com/Kuddev/gpui-component", rev = "fa3763c681dba5b3ea028395148488e1281d478d", version = "=0.5.2" } +gpui-component-assets = { git = "https://github.com/Kuddev/gpui-component", rev = "fa3763c681dba5b3ea028395148488e1281d478d", version = "=0.5.1" } diff --git a/nebula_app/Cargo.toml b/nebula_app/Cargo.toml index fa394e2..74420b8 100644 --- a/nebula_app/Cargo.toml +++ b/nebula_app/Cargo.toml @@ -284,4 +284,4 @@ gpui-shell = [ ] # 真实 GPUI hitbox / 鼠标路由测试显式启用;不能放进 dev-dependencies, # 否则不带 gpui-shell 的默认 `cargo test -p nebula` 也会编译整棵 GPUI。 -gpui-test-support = ["gpui-shell", "gpui/test-support"] +gpui-test-support = ["gpui-shell", "gpui/test-support", "gpui-component/test-support"] diff --git a/nebula_app/src/gpui_shell/prelude.rs b/nebula_app/src/gpui_shell/prelude.rs index 0fa1397..7f68825 100644 --- a/nebula_app/src/gpui_shell/prelude.rs +++ b/nebula_app/src/gpui_shell/prelude.rs @@ -54,11 +54,41 @@ const CONFIRM_DIALOG_BOTTOM_PADDING: f32 = 20.0; const CONFIRM_DIALOG_BASE_HEIGHT: f32 = 130.0; const CONFIRM_DIALOG_BODY_LINE_HEIGHT: f32 = 24.0; +fn modal_dialog_width(window: &Window) -> f32 { + CONFIRM_DIALOG_WIDTH.min((f32::from(window.viewport_size().width) - 32.0).max(240.0)) +} + +fn confirm_body_height(description: SharedString, window: &Window) -> f32 { + let available_width = + (modal_dialog_width(window) - CONFIRM_DIALOG_HORIZONTAL_PADDING * 2.0).max(1.0); + let text_style = window.text_style(); + window + .text_system() + .shape_text( + description.clone(), + px(16.0), + &[text_style.to_run(description.len())], + Some(px(available_width)), + None, + ) + .map(|lines| { + lines + .iter() + .map(|line| f32::from(line.size(px(CONFIRM_DIALOG_BODY_LINE_HEIGHT)).height)) + .sum::() + .max(CONFIRM_DIALOG_BODY_LINE_HEIGHT) + }) + .unwrap_or_else(|error| { + log::warn!("Could not measure confirmation text: {error}"); + description.lines().count().max(1) as f32 * CONFIRM_DIALOG_BODY_LINE_HEIGHT + }) +} + /// gpui-component 0.5.2 默认把 Dialog 放在视口高度的 1/10;旧 GPUI 壳则 /// 使用 480px 内容宽度并垂直居中。这里集中恢复旧壳几何,同时保留窄窗口限幅。 pub fn center_modal_dialog(dialog: Dialog, window: &Window, estimated_height: f32) -> Dialog { let viewport = window.viewport_size(); - let width = CONFIRM_DIALOG_WIDTH.min((f32::from(viewport.width) - 32.0).max(240.0)); + let width = modal_dialog_width(window); let margin_top = ((f32::from(viewport.height) - estimated_height) * 0.5).max(16.0); dialog @@ -86,27 +116,8 @@ pub fn confirm_dialog( let ok_text = ok_text.into(); let cancel_text = cancel_text.into(); - let text_style = window.text_style(); - let body_width = if description.is_empty() { - 0.0 - } else { - f32::from( - window - .text_system() - .shape_line( - description.clone(), - px(16.0), - &[text_style.to_run(description.len())], - None, - ) - .width, - ) - }; - let available_body_width = - (CONFIRM_DIALOG_WIDTH - CONFIRM_DIALOG_HORIZONTAL_PADDING * 2.0).max(1.0); - let body_lines = (body_width / available_body_width).ceil().max(1.0); let estimated_height = - CONFIRM_DIALOG_BASE_HEIGHT + body_lines * CONFIRM_DIALOG_BODY_LINE_HEIGHT; + CONFIRM_DIALOG_BASE_HEIGHT + confirm_body_height(description.clone(), window); let content_description = description.clone(); center_modal_dialog(dialog, window, estimated_height) @@ -195,7 +206,7 @@ mod tests { dialog, window, "Confirm action?", - "Choose an action.", + "example.test:22\n\nSHA256:verify-me\n\nChoose an action.", "Confirm", "Cancel", ButtonVariant::Primary, @@ -244,4 +255,25 @@ mod tests { ); assert!(confirmed.get(), "鼠标点击确认必须派发 Dialog 的确认动作"); } + + #[gpui::test] + fn confirm_dialog_measurement_preserves_explicit_and_wrapped_lines(cx: &mut TestAppContext) { + cx.update(gpui_component::init); + let (_, cx) = cx.add_window_view(|window, cx| { + let view = cx.new(|_| ConfirmDialogProbe); + Root::new(view, window, cx) + }); + cx.update(|window, _| { + assert_eq!(confirm_body_height("".into(), window), CONFIRM_DIALOG_BODY_LINE_HEIGHT); + assert_eq!(confirm_body_height("Host".into(), window), CONFIRM_DIALOG_BODY_LINE_HEIGHT); + for description in ["Host\n\nFingerprint", "Host\r\n\r\nFingerprint"] { + assert_eq!( + confirm_body_height(description.into(), window), + 3.0 * CONFIRM_DIALOG_BODY_LINE_HEIGHT, + ); + } + let wrapped = "Verify this fingerprint through a trusted channel. ".repeat(20); + assert!(confirm_body_height(wrapped.into(), window) > CONFIRM_DIALOG_BODY_LINE_HEIGHT); + }); + } } diff --git a/nebula_app/src/gpui_shell/workspace/ssh_dialog.rs b/nebula_app/src/gpui_shell/workspace/ssh_dialog.rs index 0969f7d..9f428ea 100644 --- a/nebula_app/src/gpui_shell/workspace/ssh_dialog.rs +++ b/nebula_app/src/gpui_shell/workspace/ssh_dialog.rs @@ -129,10 +129,15 @@ mod tests { }); cx.update(|window, cx| { show(request, window, cx); + }); + cx.update(|window, cx| { let _ = window.draw(cx); }); click(cx, "confirm-dialog-cancel"); - assert!(matches!(response.try_recv(), Ok(PromptResponse::Cancel))); + assert_eq!( + response.try_recv().map(|response| matches!(response, PromptResponse::Cancel)), + Ok(true) + ); } #[gpui::test] @@ -148,6 +153,8 @@ mod tests { }); cx.update(|window, cx| { show(request, window, cx); + }); + cx.update(|window, cx| { let _ = window.draw(cx); }); cx.simulate_input("test-secret"); diff --git a/scripts/tests/test_shell_integration.py b/scripts/tests/test_shell_integration.py index 31f2aef..1c34004 100644 --- a/scripts/tests/test_shell_integration.py +++ b/scripts/tests/test_shell_integration.py @@ -86,7 +86,7 @@ class ShellIntegrationTests(unittest.TestCase): wrapper.mkdir() for source, target in [("zshenv", ".zshenv"), ("zprofile", ".zprofile"), ("zshrc", ".zshrc")]: shutil.copyfile(SCRIPTS / source, wrapper / target) - args = ["-l", "-i"] + args = ["-d", "-l", "-i"] env = {"ZDOTDIR": str(wrapper), "NEBULA_ZSH_INTEGRATION": str(wrapper), "NEBULA_ZDOTDIR_WAS_SET": "1" if original_zdotdir else "0"} if original_zdotdir: @@ -111,6 +111,11 @@ class ShellIntegrationTests(unittest.TestCase): def test_zsh_command_status_and_utf8_cwd(self) -> None: self.check_protocol("zsh") + def test_zsh_loads_user_rcs_without_host_global_rcs(self) -> None: + session = self.start("zsh") + session.command('print -r -- "RCS=$options[rcs] GLOBAL_RCS=$options[globalrcs]"', + b"RCS=on GLOBAL_RCS=off") + def test_bash_preserves_prompt_command(self) -> None: session = self.start("bash", "PROMPT_COMMAND=\"printf 'USER_PROMPT\\\\n'\"\n") session.command("(exit 7)", b"\x1b]133;D;7\x07")