初始提交:terminalX 可运行态(M0/M1/M1.5 已真机验证)
- M0: libghostty SSH 终端(渲染/输入/连接)+ 白屏修复(OutputGate) + 会话状态机/自动重连 - M1: tsnet 用户态组网 + SSH-over-tsnet(fd 桥),shell 级真机验证;R5(Go+gvisor+C+++Swift 同进程) retire - M1.5: tmux -CC 原生 tab(MVP) - 结构: packages/(TXCore·TXTransport), apps/TerminalX, vendor/(libghostty-spm/libssh2/mbedtls/tsnet-bridge), artifacts/ - 文档: CLAUDE.md + docs/HANDOFF.md(新会话入口) - 环境: 认证代理→依赖 vendor 本地化;Go 在 ~/.local/go;仅模拟器/未签名 - 待续: M2 mosh, tmux 多 pane, M4 安全(host key/SE) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
86
vendor/libghostty-spm/Example/MobileGhosttyApp/TerminalSelectionViewController.swift
vendored
Normal file
86
vendor/libghostty-spm/Example/MobileGhosttyApp/TerminalSelectionViewController.swift
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
import UIKit
|
||||
|
||||
final class TerminalSelectionViewController: UIViewController {
|
||||
private let pendingText: String
|
||||
private let pendingAnchorRange: NSRange?
|
||||
|
||||
var onDone: (() -> Void)?
|
||||
|
||||
private lazy var textView: UITextView = {
|
||||
let view = UITextView()
|
||||
view.isEditable = false
|
||||
view.isSelectable = true
|
||||
view.accessibilityIdentifier = "terminal.selectionTextView"
|
||||
view.alwaysBounceVertical = true
|
||||
view.font = .monospacedSystemFont(ofSize: 14, weight: .regular)
|
||||
view.backgroundColor = .clear
|
||||
view.textColor = .label
|
||||
view.textContainerInset = .init(top: 12, left: 12, bottom: 12, right: 12)
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
return view
|
||||
}()
|
||||
|
||||
init(text: String, anchorRange: NSRange?) {
|
||||
pendingText = text
|
||||
pendingAnchorRange = anchorRange
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
view.backgroundColor = .clear
|
||||
|
||||
textView.text = pendingText
|
||||
view.addSubview(textView)
|
||||
|
||||
// Pin to view edges (not safe area) so scrolled content extends
|
||||
// under the transparent navigation bar / home indicator the way
|
||||
// iOS sheets expect. UIKit's contentInsetAdjustmentBehavior keeps
|
||||
// the initial scroll position below the nav bar via
|
||||
// adjustedContentInset.
|
||||
NSLayoutConstraint.activate([
|
||||
textView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
textView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
textView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
textView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
])
|
||||
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
||||
barButtonSystemItem: .done,
|
||||
target: self,
|
||||
action: #selector(handleDone)
|
||||
)
|
||||
navigationItem.rightBarButtonItem?.accessibilityIdentifier = "terminal.selectionDoneButton"
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
navigationController?.view.backgroundColor = .clear
|
||||
navigationController?.navigationBar.standardAppearance.configureWithTransparentBackground()
|
||||
navigationController?.navigationBar.scrollEdgeAppearance = navigationController?.navigationBar.standardAppearance
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
textView.becomeFirstResponder()
|
||||
|
||||
let nsText = textView.text as NSString
|
||||
if let range = pendingAnchorRange, NSMaxRange(range) <= nsText.length {
|
||||
textView.selectedRange = range
|
||||
textView.scrollRangeToVisible(range)
|
||||
} else {
|
||||
textView.selectAll(nil)
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func handleDone() {
|
||||
dismiss(animated: true) { [weak self] in
|
||||
self?.onDone?()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user