From 7f72a526463e871d963303553a802280bc72b5d8 Mon Sep 17 00:00:00 2001 From: icefery Date: Tue, 20 Oct 2026 09:54:15 +0800 Subject: [PATCH] =?UTF-8?q?feat(macos):=20=E7=BB=88=E7=AB=AF=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E8=A1=A5=E5=85=85=20TERM=5FPROGRAM=5FVERSION?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 背景 - Windows 端已随 `TERM_PROGRAM` 一并导出 `TERM_PROGRAM_VERSION`(`windows/tauri/crates/terminal/src/connection.rs:268`),macOS 端缺失,同一产品两平台环境不一致。 - 工具按版本门控终端能力是既有惯例(如 oh-my-pi 以该变量区分 tmux 版本行为);Lithe 引入新的外部可观察能力(如背景变更推送)后,也需要一个版本位供 TUI 安全启用。 ## 改动 - `TerminalTransport` 协议新增 `termProgramVersion: String?`(无法取得时为 nil),extension 默认 nil - `TerminalSession` 以可选值直赋导出 `TERM_PROGRAM_VERSION`,版本未知时同时清除从外层终端继承的同名值 - macOS 取应用打包版本(`CFBundleShortVersionString`),与 Windows 报告的打包版本一致 - `TerminalModuleTests` 新增 3 个用例(导出、无法取得不写入、清除继承值);更新 `terminal-shell.json` 能力矩阵 ## 验证 - `TerminalModuleTests` 全绿(含 3 个新增) - 平台能力矩阵门禁通过 --- .../MacOS/Terminal/MacTerminalTransport.swift | 4 +++ .../Ports/TerminalTransport.swift | 5 +++ .../Runtime/TerminalSession.swift | 2 ++ .../TerminalModuleTests.swift | 36 ++++++++++++++++++- .../features/terminal-shell.json | 6 ++-- 5 files changed, 50 insertions(+), 3 deletions(-) diff --git a/macos/Sources/Lithe/Platform/MacOS/Terminal/MacTerminalTransport.swift b/macos/Sources/Lithe/Platform/MacOS/Terminal/MacTerminalTransport.swift index 19879363c6..61b1c526f5 100644 --- a/macos/Sources/Lithe/Platform/MacOS/Terminal/MacTerminalTransport.swift +++ b/macos/Sources/Lithe/Platform/MacOS/Terminal/MacTerminalTransport.swift @@ -441,6 +441,10 @@ final class MacTerminalTransport: NSObject, TerminalTransport, @preconcurrency L ProcessInfo.processInfo.environment["SHELL"] ?? "/bin/zsh" } + var termProgramVersion: String? { + Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String + } + func defaultEnvironment() -> [String: String] { ProcessInfo.processInfo.environment } diff --git a/macos/Sources/LitheTerminalModule/Ports/TerminalTransport.swift b/macos/Sources/LitheTerminalModule/Ports/TerminalTransport.swift index f010fcab03..d928618cb3 100644 --- a/macos/Sources/LitheTerminalModule/Ports/TerminalTransport.swift +++ b/macos/Sources/LitheTerminalModule/Ports/TerminalTransport.swift @@ -59,6 +59,10 @@ public protocol TerminalTransport: AnyObject { var processID: Int32? { get } var shellName: String { get } var nativeView: AnyObject { get } + /// Product version advertised through `TERM_PROGRAM_VERSION`. Windows + /// already reports the packaged app version; hosts that cannot expose one + /// keep the variable unset. + var termProgramVersion: String? { get } var onTermination: ((Int32?) -> Void)? { get set } /// Raw bytes received from the child process before terminal emulation. /// Hosts may mirror this output into another product surface. @@ -87,6 +91,7 @@ public protocol TerminalTransport: AnyObject { } public extension TerminalTransport { + var termProgramVersion: String? { nil } func stopAndWait() async { stop() } func runningPrograms() -> [TerminalRunningProgram] { [] } } diff --git a/macos/Sources/LitheTerminalModule/Runtime/TerminalSession.swift b/macos/Sources/LitheTerminalModule/Runtime/TerminalSession.swift index 891ae36f26..9f06b088f4 100644 --- a/macos/Sources/LitheTerminalModule/Runtime/TerminalSession.swift +++ b/macos/Sources/LitheTerminalModule/Runtime/TerminalSession.swift @@ -178,6 +178,8 @@ public final class TerminalSession: ObservableObject, Identifiable { environment["TERM"] = "xterm-256color" environment["COLORTERM"] = "truecolor" environment["TERM_PROGRAM"] = "Lithe" + // Direct assignment drops any value inherited from the outer terminal when the transport cannot report one. + environment["TERM_PROGRAM_VERSION"] = transport.termProgramVersion for change in changes { if let value = change.value { environment[change.name] = value diff --git a/macos/Tests/LitheTerminalModuleTests/TerminalModuleTests.swift b/macos/Tests/LitheTerminalModuleTests/TerminalModuleTests.swift index 3d60a919f3..002cdf1b7b 100644 --- a/macos/Tests/LitheTerminalModuleTests/TerminalModuleTests.swift +++ b/macos/Tests/LitheTerminalModuleTests/TerminalModuleTests.swift @@ -200,6 +200,36 @@ struct TerminalModuleTests { #expect(!output.joined().contains("�")) feature.stopAllSessions() } + + @Test + func interactiveShellEnvironmentReportsTermProgramVersion() { + let transport = TestTransport() + transport.termProgramVersion = "9.9.9" + let feature = TerminalFeatureModel(terminalFactory: { transport }) + defer { feature.stopAllSessions() } + feature.createSession(in: URL(fileURLWithPath: "/workspace"), shellPath: "/bin/zsh") + #expect(transport.shellEnvironments.last?["TERM_PROGRAM"] == "Lithe") + #expect(transport.shellEnvironments.last?["TERM_PROGRAM_VERSION"] == "9.9.9") + } + + @Test + func interactiveShellEnvironmentOmitsUnknownTermProgramVersion() { + let transport = TestTransport() + let feature = TerminalFeatureModel(terminalFactory: { transport }) + defer { feature.stopAllSessions() } + feature.createSession(in: URL(fileURLWithPath: "/workspace"), shellPath: "/bin/zsh") + #expect(transport.shellEnvironments.last?["TERM_PROGRAM_VERSION"] == nil) + } + + @Test + func interactiveShellEnvironmentClearsInheritedTermProgramVersionWhenUnknown() { + let transport = TestTransport() + transport.inheritedEnvironment["TERM_PROGRAM_VERSION"] = "3.5.13" + let feature = TerminalFeatureModel(terminalFactory: { transport }) + defer { feature.stopAllSessions() } + feature.createSession(in: URL(fileURLWithPath: "/workspace"), shellPath: "/bin/zsh") + #expect(transport.shellEnvironments.last?["TERM_PROGRAM_VERSION"] == nil) + } } @MainActor @@ -208,7 +238,10 @@ private final class TestTransport: TerminalTransport { var isRunning = false var processID: Int32? { isRunning ? 1234 : nil } var shellName = "Shell" + var termProgramVersion: String? var shellStarts: [String] = [] + var shellEnvironments: [[String: String]] = [] + var inheritedEnvironment: [String: String] = ["REMOVE_ME": "old"] var startError: Error? var onTermination: ((Int32?) -> Void)? var onOutput: ((Data) -> Void)? @@ -221,9 +254,10 @@ private final class TestTransport: TerminalTransport { var sentInputs: [String] = [] var outputOnStart: String? func defaultShellPath() -> String { "/bin/zsh" } - func defaultEnvironment() -> [String: String] { ["REMOVE_ME": "old"] } + func defaultEnvironment() -> [String: String] { inheritedEnvironment } func start(workingDirectory: String, shellPath: String, environment: [String: String]) throws { shellStarts.append(shellPath) + shellEnvironments.append(environment) if let startError { throw startError } isRunning = true } diff --git a/shared/platform-feature-matrix/features/terminal-shell.json b/shared/platform-feature-matrix/features/terminal-shell.json index dc4baa79e7..767c980f94 100644 --- a/shared/platform-feature-matrix/features/terminal-shell.json +++ b/shared/platform-feature-matrix/features/terminal-shell.json @@ -12,7 +12,9 @@ "macos/Sources/Lithe/Models/Settings/AppSettings.swift", "macos/Sources/Lithe/Platform/MacOS/Terminal/MacTerminalTransport.swift", "macos/Tests/LitheTests/TerminalScrollbackSettingsTests.swift", - "macos/Tests/LitheTests/SettingsSearchVocabularyTests.swift" + "macos/Tests/LitheTests/SettingsSearchVocabularyTests.swift", + "macos/Sources/LitheTerminalModule/Runtime/TerminalSession.swift", + "macos/Tests/LitheTerminalModuleTests/TerminalModuleTests.swift" ], "implementationStatus": "implemented", "verificationStatus": "pending" @@ -28,5 +30,5 @@ "verificationStatus": "pending" }, "owner": "Terminal", - "verification": "验证设置页默认 Shell 选项与终端「新建终端」菜单检测到的 Shell 一致、可选 Git Bash 并生效;macOS 新建终端下拉框复用 Project 下拉框的共享菜单样式,菜单操作与原有 Shell 选择、重新检测功能一致;验证环境变量、工作目录和不可用 Shell 的提示。 设置页终端分组新增回滚行数配置(默认 2000,范围 100–50000,步进 500),读取、写入、恢复默认均做上下限夹取,对新建会话生效(TerminalScrollbackSettingsTests);设置搜索按界面名称 Terminal scrollback/终端回滚行数/行数 均可命中终端页(SettingsSearchVocabularyTests)。" + "verification": "验证设置页默认 Shell 选项与终端「新建终端」菜单检测到的 Shell 一致、可选 Git Bash 并生效;macOS 新建终端下拉框复用 Project 下拉框的共享菜单样式,菜单操作与原有 Shell 选择、重新检测功能一致;验证环境变量、工作目录和不可用 Shell 的提示。 设置页终端分组新增回滚行数配置(默认 2000,范围 100–50000,步进 500),读取、写入、恢复默认均做上下限夹取,对新建会话生效(TerminalScrollbackSettingsTests);设置搜索按界面名称 Terminal scrollback/终端回滚行数/行数 均可命中终端页(SettingsSearchVocabularyTests)。 macOS 终端环境随 TERM_PROGRAM=Lithe 一并导出 TERM_PROGRAM_VERSION(取应用打包版本,无法取得时不写入),与 Windows 端一致,供工具按版本门控终端能力(TerminalModuleTests)。" }