- 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>
361 lines
9.9 KiB
Swift
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
|
|
}
|
|
}
|