// // SpikeTerminalScreen.swift — T-iOS-2 Day-1 spike ②【临时文件,W4/T-iOS-15 删除】 // // 目的:SwiftTerm TerminalView 的真机键盘/IME smoke 载体(plan §7 T-iOS-2): // - 键盘弹出 / first-responder 行为 // - 中文 IME 组合输入(不自加 keydown 监听,组合事件全交系统栈) // - inputAccessoryView 上 Esc/Ctrl-C 可发 —— SwiftTerm iOS 端自带 // TerminalAccessory(含 esc/ctrl/tab 键,iOSAccessoryView.swift), // spike 直接用它验证;正式 key-bar(逐字节复刻 public/keybar.ts)归 T-iOS-11 // - 文本选择不崩(SwiftTerm 自绘选择是历史坑点) // // 接线为【纯本地回显】(无服务器依赖):typed bytes → send() → feed() 原样喂回, // CR 扩成 CRLF。真实 SessionEngine 接线是 W2/W3 的事。 // // 2026-07-04:无可用真机 —— 真机清单各项记 DEFERRED(真机不可用)(见 PROGRESS_LOG // 条目)。本文件当下的验收是:作为 App target 源码在模拟器 xcodebuild 构建通过。 import SwiftTerm import SwiftUI /// Day-1 spike 的最小 SwiftUI 壳。不接入 WebTermApp 导航(WebTermApp.swift 归 /// T-iOS-1/T-iOS-15 所有);存在本身即证明 App target 在 Swift 6 strict /// concurrency 下能链接并承载 SwiftTerm 的 TerminalView。 struct SpikeTerminalScreen: View { var body: some View { LocalEchoTerminal() .ignoresSafeArea(.container, edges: .bottom) .navigationTitle("SwiftTerm spike") } } /// 承载 SwiftTerm UIKit `TerminalView` 的 UIViewRepresentable,接本地回显。 /// 形状即 T-iOS-11 正式 TerminalScreen 的雏形(去掉 SessionEngine)。 private struct LocalEchoTerminal: UIViewRepresentable { func makeCoordinator() -> LocalEchoCoordinator { LocalEchoCoordinator() } func makeUIView(context: Context) -> TerminalView { let view = TerminalView(frame: .zero) view.terminalDelegate = context.coordinator view.feed(text: SpikeGreeting.text) return view } func updateUIView(_ uiView: TerminalView, context: Context) { // 本地回显无外部状态可同步。 } } private enum SpikeGreeting { /// 行尾用 CRLF:PTY 语义下裸 LF 只下移一行不回车。 static let text = "SwiftTerm local-echo spike (T-iOS-2)\r\n" + "Typed bytes are echoed locally; CR -> CRLF.\r\n" + "真机清单: 键盘弹出 / 中文 IME / accessory esc·ctrl / 文本选择\r\n\r\n> " } /// 本地回显 delegate:SwiftTerm 要"发给宿主"的每个字节原样喂回终端。 /// Enter 产生的 CR(0x0D)补一个 LF,光标才会回行首并换行。 @MainActor private final class LocalEchoCoordinator: NSObject, @preconcurrency TerminalViewDelegate { private static let carriageReturn: UInt8 = 0x0D private static let lineFeed: UInt8 = 0x0A func send(source: TerminalView, data: ArraySlice) { var echoed: [UInt8] = [] echoed.reserveCapacity(data.count + 1) for byte in data { echoed.append(byte) if byte == Self.carriageReturn { echoed.append(Self.lineFeed) } } source.feed(byteArray: echoed[...]) } func sizeChanged(source: TerminalView, newCols: Int, newRows: Int) {} func setTerminalTitle(source: TerminalView, title: String) {} func hostCurrentDirectoryUpdate(source: TerminalView, directory: String?) {} func scrolled(source: TerminalView, position: Double) {} func requestOpenLink(source: TerminalView, link: String, params: [String: String]) {} func clipboardCopy(source: TerminalView, content: Data) {} func rangeChanged(source: TerminalView, startY: Int, endY: Int) {} // bell / iTermContent 用 SwiftTerm 的默认实现。 } #Preview { SpikeTerminalScreen() }