T-iOS-1: ios/ XcodeGen project (iOS 17, Swift 6 strict concurrency, ATS per PLAN §5.2), 5 SPM package shells, CI skeleton T-iOS-2: Origin spike vs real server — URLSessionWebSocketTask custom Origin CONFIRMED (no Starscream); 16MiB replay + EMSGSIZE(40) errno correction written back to plan T-iOS-3: WireProtocol frozen contract, 59 tests, 100% line coverage, cross-impl vectors vs src/protocol.ts via tsx T-iOS-4: FakeTransport/FakeClock/FakeHTTPTransport doubles Verify: independent agent re-ran all acceptance — 6/6 PASS
90 lines
3.8 KiB
Swift
90 lines
3.8 KiB
Swift
//
|
||
// 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<UInt8>) {
|
||
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()
|
||
}
|