126 lines
5.4 KiB
Swift
126 lines
5.4 KiB
Swift
// SPDX-FileCopyrightText: 2026 Kaede Akino
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
import Foundation
|
|
import GhosttyTerminal
|
|
import GhosttyTheme
|
|
import SwiftUI
|
|
import TermindDomain
|
|
|
|
nonisolated extension TermindTerminalPalette.RGB {
|
|
public var color: Color { Color(.sRGB, red: r, green: g, blue: b, opacity: 1) }
|
|
}
|
|
nonisolated extension TermindTerminalPalette {
|
|
/// The same palette expressed in the format consumed by libghostty. The
|
|
/// SwiftUI chrome and the terminal grid therefore share one definition
|
|
/// instead of merely using similarly named colors.
|
|
var ghosttyDefinition: GhosttyThemeDefinition {
|
|
GhosttyThemeDefinition(
|
|
name: name,
|
|
background: background.hex,
|
|
foreground: foreground.hex,
|
|
cursorColor: cursor.hex,
|
|
cursorText: background.hex,
|
|
selectionBackground: selection.hex,
|
|
selectionForeground: foreground.hex,
|
|
palette: Dictionary(uniqueKeysWithValues: ansi.enumerated().map { ($0, $1.hex) })
|
|
)
|
|
}
|
|
}
|
|
|
|
nonisolated extension TerminalCustomConfiguration {
|
|
func applying(to base: GhosttyTerminal.TerminalConfiguration, preferredFontFamily: String)
|
|
-> GhosttyTerminal.TerminalConfiguration
|
|
{
|
|
GhosttyTerminal.TerminalConfiguration(startingFrom: base) { builder in
|
|
if !preferredFontFamily.isEmpty { builder.withFontFamily(preferredFontFamily) }
|
|
// Command alerts are owned by the app's shared settings and dispatcher.
|
|
for entry in entries where entry.key != "notify-on-command-finish" {
|
|
builder.withCustom(entry.key, entry.value)
|
|
if !preferredFontFamily.isEmpty && entry.key == "font-family"
|
|
&& (entry.value.isEmpty || entry.value == "\"\"")
|
|
{
|
|
// Keep the theme's reset semantics without dropping the device's first choice.
|
|
builder.withFontFamily(preferredFontFamily)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public nonisolated enum TermindTerminalThemeCatalog {
|
|
public static let allThemes: [GhosttyThemeDefinition] = {
|
|
let termindThemes = TermindTerminalPalette.all.map(\.ghosttyDefinition)
|
|
let termindNames = Set(termindThemes.map(\.name))
|
|
return termindThemes + GhosttyThemeCatalog.allThemes.filter { !termindNames.contains($0.name) }
|
|
}()
|
|
|
|
public static func theme(named name: String) -> GhosttyThemeDefinition? {
|
|
allThemes.first { $0.name == name }
|
|
}
|
|
|
|
public static let defaultDark = TermindTerminalPalette.termindDark.ghosttyDefinition
|
|
public static let defaultLight = TermindTerminalPalette.termindLight.ghosttyDefinition
|
|
public static let darkThemes = allThemes.filter(\.isDark).sorted { $0.name < $1.name }
|
|
public static let lightThemes = allThemes.filter { !$0.isDark }.sorted { $0.name < $1.name }
|
|
|
|
public static func themes(isDark: Bool) -> [GhosttyThemeDefinition] {
|
|
isDark ? darkThemes : lightThemes
|
|
}
|
|
|
|
public static func fallbackTheme(isDark: Bool) -> GhosttyThemeDefinition {
|
|
isDark ? defaultDark : defaultLight
|
|
}
|
|
|
|
public static func resolvedTheme(named name: String, isDark: Bool) -> GhosttyThemeDefinition {
|
|
theme(named: name).flatMap { $0.isDark == isDark ? $0 : nil } ?? fallbackTheme(isDark: isDark)
|
|
}
|
|
|
|
public static func terminalConfiguration(
|
|
themeName: String,
|
|
customEnabled: Bool,
|
|
customText: String,
|
|
isDark: Bool,
|
|
font: TerminalFontPreferences = .init()
|
|
) -> GhosttyTerminal.TerminalConfiguration {
|
|
let selected = resolvedTheme(named: themeName, isDark: isDark)
|
|
let base = selected.toTerminalConfiguration()
|
|
let themed = TerminalCustomConfiguration(customEnabled ? customText : "")
|
|
.applying(to: base, preferredFontFamily: font.family)
|
|
// Ghostty renders theme commands after session overrides, so manual size must come last here.
|
|
return GhosttyTerminal.TerminalConfiguration(startingFrom: themed) { builder in
|
|
if !font.sizeFollowsTheme { builder.withFontSize(Float(font.size)) }
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
@MainActor
|
|
extension TerminalViewState {
|
|
public func applyPreferences(
|
|
_ value: TerminalPreferences, font: TerminalFontPreferences = .init(),
|
|
library: TerminalThemeLibrary = .init()
|
|
) {
|
|
let lightOptions = TerminalThemeOptions(preferences: value, isDark: false, library: library)
|
|
let darkOptions = TerminalThemeOptions(preferences: value, isDark: true, library: library)
|
|
let light = TermindTerminalThemeCatalog.terminalConfiguration(
|
|
themeName: lightOptions.baseTheme.name,
|
|
customEnabled: lightOptions.isCustom, customText: lightOptions.customText,
|
|
isDark: lightOptions.baseTheme.isDark,
|
|
font: font)
|
|
let dark = TermindTerminalThemeCatalog.terminalConfiguration(
|
|
themeName: darkOptions.baseTheme.name,
|
|
customEnabled: darkOptions.isCustom, customText: darkOptions.customText,
|
|
isDark: darkOptions.baseTheme.isDark,
|
|
font: font)
|
|
setTheme(GhosttyTerminal.TerminalTheme(light: light, dark: dark))
|
|
setTerminalConfiguration(
|
|
GhosttyTerminal.TerminalConfiguration { builder in
|
|
builder.withCustom("notify-on-command-finish", "never")
|
|
builder.withWindowPaddingX(8)
|
|
builder.withWindowPaddingY(8)
|
|
})
|
|
}
|
|
}
|