Files
kid aa92d0e676 初始提交: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>
2026-07-24 10:20:46 +08:00

361 lines
9.9 KiB
Swift

//
// TerminalConfiguration.swift
// libghostty-spm
//
// Created by Lakr233 on 2026/3/17.
//
public enum TerminalCursorStyle: String, Sendable, Hashable {
case block
case bar
case underline
}
public enum TerminalConfigCommand: Sendable, Hashable {
// Font
case fontFamily(String)
case fontSize(Float)
case fontThicken(Bool)
case fontThickenStrength(Int)
// Cursor
case cursorStyle(TerminalCursorStyle)
case cursorStyleBlink(Bool)
case cursorColor(String)
case cursorText(String)
case cursorOpacity(Double)
// Colors
case background(String)
case foreground(String)
case selectionBackground(String)
case selectionForeground(String)
case boldColor(String)
case palette(index: Int, color: String)
case minimumContrast(Double)
// Background
case backgroundOpacity(Double)
case backgroundBlur(Int)
// Layout
case windowPaddingX(Int)
case windowPaddingY(Int)
/// Escape hatch
case custom(key: String, value: String)
var renderedLine: String {
switch self {
case let .fontFamily(value):
"font-family = \(value)"
case let .fontSize(value):
"font-size = \(value.formatted(.number.precision(.fractionLength(0 ... 2))))"
case let .fontThicken(enabled):
"font-thicken = \(enabled)"
case let .fontThickenStrength(value):
"font-thicken-strength = \(value)"
case let .cursorStyle(style):
"cursor-style = \(style.rawValue)"
case let .cursorStyleBlink(enabled):
"cursor-style-blink = \(enabled)"
case let .cursorColor(value):
"cursor-color = \(value)"
case let .cursorText(value):
"cursor-text = \(value)"
case let .cursorOpacity(value):
"cursor-opacity = \(value.formatted(.number.precision(.fractionLength(0 ... 3))))"
case let .background(value):
"background = \(value)"
case let .foreground(value):
"foreground = \(value)"
case let .selectionBackground(value):
"selection-background = \(value)"
case let .selectionForeground(value):
"selection-foreground = \(value)"
case let .boldColor(value):
"bold-color = \(value)"
case let .palette(index, color):
"palette = \(index)=\(color)"
case let .minimumContrast(value):
"minimum-contrast = \(value.formatted(.number.precision(.fractionLength(0 ... 2))))"
case let .backgroundOpacity(value):
"background-opacity = \(value.formatted(.number.precision(.fractionLength(0 ... 3))))"
case let .backgroundBlur(value):
"background-blur = \(value)"
case let .windowPaddingX(value):
"window-padding-x = \(value)"
case let .windowPaddingY(value):
"window-padding-y = \(value)"
case let .custom(key, value):
"\(key) = \(value)"
}
}
}
public struct TerminalConfiguration: Sendable, Hashable {
public struct Builder {
var commands: [TerminalConfigCommand] = []
public init() {}
init(commands: [TerminalConfigCommand]) {
self.commands = commands
}
/// Font
public mutating func withFontFamily(_ value: String) {
commands.append(.fontFamily(value))
}
public mutating func withFontSize(_ value: Float) {
commands.append(.fontSize(value))
}
public mutating func withFontThicken(_ enabled: Bool) {
commands.append(.fontThicken(enabled))
}
public mutating func withFontThickenStrength(_ value: Int) {
commands.append(.fontThickenStrength(value))
}
/// Cursor
public mutating func withCursorStyle(_ style: TerminalCursorStyle) {
commands.append(.cursorStyle(style))
}
public mutating func withCursorStyleBlink(_ enabled: Bool) {
commands.append(.cursorStyleBlink(enabled))
}
public mutating func withCursorColor(_ value: String) {
commands.append(.cursorColor(value))
}
public mutating func withCursorText(_ value: String) {
commands.append(.cursorText(value))
}
public mutating func withCursorOpacity(_ value: Double) {
commands.append(.cursorOpacity(value))
}
/// Colors
public mutating func withBackground(_ value: String) {
commands.append(.background(value))
}
public mutating func withForeground(_ value: String) {
commands.append(.foreground(value))
}
public mutating func withSelectionBackground(_ value: String) {
commands.append(.selectionBackground(value))
}
public mutating func withSelectionForeground(_ value: String) {
commands.append(.selectionForeground(value))
}
public mutating func withBoldColor(_ value: String) {
commands.append(.boldColor(value))
}
public mutating func withPalette(_ index: Int, color: String) {
commands.append(.palette(index: index, color: color))
}
public mutating func withMinimumContrast(_ value: Double) {
commands.append(.minimumContrast(value))
}
/// Background
public mutating func withBackgroundOpacity(_ value: Double) {
commands.append(.backgroundOpacity(value))
}
public mutating func withBackgroundBlur(_ value: Int) {
commands.append(.backgroundBlur(value))
}
/// Layout
public mutating func withWindowPaddingX(_ value: Int) {
commands.append(.windowPaddingX(value))
}
public mutating func withWindowPaddingY(_ value: Int) {
commands.append(.windowPaddingY(value))
}
/// Escape hatch
public mutating func withCustom(_ key: String, _ value: String) {
commands.append(.custom(key: key, value: value))
}
}
let commands: [TerminalConfigCommand]
public init() {
commands = []
}
public init(configure: (inout Builder) -> Void) {
self.init(startingFrom: .init(), configure: configure)
}
public init(
startingFrom base: TerminalConfiguration,
configure: (inout Builder) -> Void
) {
var builder = Builder(commands: base.commands)
configure(&builder)
commands = builder.commands
}
public func appending(_ command: TerminalConfigCommand) -> TerminalConfiguration {
TerminalConfiguration(commands: commands + [command])
}
// MARK: - Font
public func fontFamily(_ value: String) -> TerminalConfiguration {
appending(.fontFamily(value))
}
public func fontSize(_ value: Float) -> TerminalConfiguration {
appending(.fontSize(value))
}
public func fontThicken(_ enabled: Bool) -> TerminalConfiguration {
appending(.fontThicken(enabled))
}
public func fontThickenStrength(_ value: Int) -> TerminalConfiguration {
appending(.fontThickenStrength(value))
}
// MARK: - Cursor
public func cursorStyle(_ style: TerminalCursorStyle) -> TerminalConfiguration {
appending(.cursorStyle(style))
}
public func cursorStyleBlink(_ enabled: Bool) -> TerminalConfiguration {
appending(.cursorStyleBlink(enabled))
}
public func cursorColor(_ value: String) -> TerminalConfiguration {
appending(.cursorColor(value))
}
public func cursorText(_ value: String) -> TerminalConfiguration {
appending(.cursorText(value))
}
public func cursorOpacity(_ value: Double) -> TerminalConfiguration {
appending(.cursorOpacity(value))
}
// MARK: - Colors
public func background(_ value: String) -> TerminalConfiguration {
appending(.background(value))
}
public func foreground(_ value: String) -> TerminalConfiguration {
appending(.foreground(value))
}
public func selectionBackground(_ value: String) -> TerminalConfiguration {
appending(.selectionBackground(value))
}
public func selectionForeground(_ value: String) -> TerminalConfiguration {
appending(.selectionForeground(value))
}
public func boldColor(_ value: String) -> TerminalConfiguration {
appending(.boldColor(value))
}
public func palette(_ index: Int, color: String) -> TerminalConfiguration {
appending(.palette(index: index, color: color))
}
public func minimumContrast(_ value: Double) -> TerminalConfiguration {
appending(.minimumContrast(value))
}
// MARK: - Background
public func backgroundOpacity(_ value: Double) -> TerminalConfiguration {
appending(.backgroundOpacity(value))
}
public func backgroundBlur(_ value: Int) -> TerminalConfiguration {
appending(.backgroundBlur(value))
}
// MARK: - Layout
public func windowPaddingX(_ value: Int) -> TerminalConfiguration {
appending(.windowPaddingX(value))
}
public func windowPaddingY(_ value: Int) -> TerminalConfiguration {
appending(.windowPaddingY(value))
}
// MARK: - Escape Hatch
public func custom(_ key: String, _ value: String) -> TerminalConfiguration {
appending(.custom(key: key, value: value))
}
// MARK: - Defaults
public static let `default` = TerminalConfiguration { builder in
builder.withCursorStyle(.block)
builder.withCursorStyleBlink(true)
#if os(iOS)
builder.withFontSize(10)
#else
builder.withFontSize(14)
#endif
builder.withFontThicken(true)
}
public var rendered: String {
commands.map(\.renderedLine).joined(separator: "\n")
}
var isEmpty: Bool {
commands.isEmpty
}
init(commands: [TerminalConfigCommand]) {
self.commands = commands
}
}