Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 132 additions & 59 deletions Sources/ContentView.swift

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions Sources/GhosttyConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ struct GhosttyConfig {
var fontFamily: String = "Menlo"
var fontSize: CGFloat = 12
var surfaceTabBarFontSize: CGFloat = 11
// Primary sidebar workspace-label font size. Secondary sidebar text scales
// proportionally from this value (see SidebarFontScale). Default matches
// the hardcoded size used prior to this setting.
static let defaultSidebarFontSize: CGFloat = 12.5
static let minSidebarFontSize: CGFloat = 10
static let maxSidebarFontSize: CGFloat = 20
var sidebarFontSize: CGFloat = GhosttyConfig.defaultSidebarFontSize
var theme: String?
var workingDirectory: String?
// Ghostty measures scrollback-limit in bytes, not lines.
Expand Down Expand Up @@ -394,6 +401,18 @@ struct GhosttyConfig {
if let size = Double(value) {
surfaceTabBarFontSize = CGFloat(size)
}
case "sidebar-font-size":
// `Double(_:)` accepts "nan" / "inf" per IEEE 754 / Apple
// docs. Reject non-finite values so they don't poison the
// clamp (min/max of NaN → NaN → NaN-valued fontScale →
// invisible sidebar text).
if let size = Double(value), size.isFinite {
let clamped = min(
Double(GhosttyConfig.maxSidebarFontSize),
max(Double(GhosttyConfig.minSidebarFontSize), size)
)
sidebarFontSize = CGFloat(clamped)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
case "theme":
theme = value
if let preferredColorScheme {
Expand Down
5 changes: 3 additions & 2 deletions Sources/ShortcutHintPill.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,12 @@ extension View {
text: String?,
emphasis: Double,
offsetX: Double,
offsetY: Double
offsetY: Double,
fontScale: CGFloat = 1.0
) -> some View {
overlay(alignment: .topTrailing) {
if let text {
ShortcutHintPill(text: text, fontSize: 10, emphasis: emphasis)
ShortcutHintPill(text: text, fontSize: 10 * fontScale, emphasis: emphasis)
.offset(
x: ShortcutHintDebugSettings.clamped(offsetX),
y: ShortcutHintDebugSettings.clamped(offsetY)
Expand Down
7 changes: 4 additions & 3 deletions Sources/Sidebar/SidebarDirectoryText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,26 @@ import SwiftUI
struct SidebarDirectoryText: View {
let candidates: [String]
let color: Color
var fontScale: CGFloat = 1

var body: some View {
if candidates.count <= 1 {
Text(candidates.first ?? "")
.font(.system(size: 10, design: .monospaced))
.font(.system(size: 10 * fontScale, design: .monospaced))
.foregroundColor(color)
.lineLimit(1)
.truncationMode(.tail)
} else {
ViewThatFits(in: .horizontal) {
ForEach(Array(candidates.dropLast().enumerated()), id: \.offset) { _, candidate in
Text(candidate)
.font(.system(size: 10, design: .monospaced))
.font(.system(size: 10 * fontScale, design: .monospaced))
.foregroundColor(color)
.lineLimit(1)
.fixedSize(horizontal: true, vertical: false)
}
Text(candidates.last ?? "")
.font(.system(size: 10, design: .monospaced))
.font(.system(size: 10 * fontScale, design: .monospaced))
.foregroundColor(color)
.lineLimit(1)
.truncationMode(.tail)
Expand Down
100 changes: 100 additions & 0 deletions cmuxTests/GhosttyConfigTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4126,6 +4126,106 @@ final class GhosttyMouseFocusTests: XCTestCase {
}
}

final class SidebarFontSizeConfigTests: XCTestCase {

func testDefaultSidebarFontSizeMatchesConstant() {
let config = GhosttyConfig()
XCTAssertEqual(config.sidebarFontSize, GhosttyConfig.defaultSidebarFontSize, accuracy: 0.0001)
}

func testParseSidebarFontSizeValid() {
var config = GhosttyConfig()
config.parse("sidebar-font-size = 14")
XCTAssertEqual(config.sidebarFontSize, 14, accuracy: 0.0001)
}

func testParseSidebarFontSizeFractional() {
var config = GhosttyConfig()
config.parse("sidebar-font-size = 13.5")
XCTAssertEqual(config.sidebarFontSize, 13.5, accuracy: 0.0001)
}

func testParseSidebarFontSizeClampedAboveMax() {
var config = GhosttyConfig()
config.parse("sidebar-font-size = 9999")
XCTAssertEqual(config.sidebarFontSize,
GhosttyConfig.maxSidebarFontSize,
accuracy: 0.0001)
}

func testParseSidebarFontSizeClampedBelowMin() {
var config = GhosttyConfig()
config.parse("sidebar-font-size = 1")
XCTAssertEqual(config.sidebarFontSize,
GhosttyConfig.minSidebarFontSize,
accuracy: 0.0001)
}

func testParseSidebarFontSizeIgnoresNonNumeric() {
var config = GhosttyConfig()
config.parse("sidebar-font-size = huge")
XCTAssertEqual(config.sidebarFontSize,
GhosttyConfig.defaultSidebarFontSize,
accuracy: 0.0001)
}

func testParseSidebarFontSizeTrailingZero() {
var config = GhosttyConfig()
config.parse("sidebar-font-size = 14.0")
XCTAssertEqual(config.sidebarFontSize, 14, accuracy: 0.0001)
}

func testLoadReadsSidebarFontSizeViaInjectedLoader() {
let loaded = GhosttyConfig.load(
preferredColorScheme: .dark,
useCache: false,
loadFromDisk: { _ in
var config = GhosttyConfig()
config.parse("sidebar-font-size = 14\n")
return config
}
)
XCTAssertEqual(loaded.sidebarFontSize, 14, accuracy: 0.0001)
}

func testParseSidebarFontSizeIgnoresNaN() {
// `Double("nan")` returns `.nan`. Without explicit rejection the
// min/max clamp would produce NaN, yielding an invisible sidebar.
var config = GhosttyConfig()
config.parse("sidebar-font-size = nan")
XCTAssertEqual(config.sidebarFontSize,
GhosttyConfig.defaultSidebarFontSize,
accuracy: 0.0001)
}

func testParseSidebarFontSizeIgnoresInfinity() {
var config = GhosttyConfig()
config.parse("sidebar-font-size = inf")
XCTAssertEqual(config.sidebarFontSize,
GhosttyConfig.defaultSidebarFontSize,
accuracy: 0.0001)
}

func testParseSidebarFontSizeIgnoresNegativeInfinity() {
var config = GhosttyConfig()
config.parse("sidebar-font-size = -infinity")
XCTAssertEqual(config.sidebarFontSize,
GhosttyConfig.defaultSidebarFontSize,
accuracy: 0.0001)
}

func testParseLaterSidebarFontSizeWins() {
// When multiple config files are loaded in order, later paths
// should override earlier values for the same key — this is the
// contract that makes ~/Library/Application Support override
// ~/.config/ghostty on macOS.
var config = GhosttyConfig()
config.parse("sidebar-font-size = 11")
config.parse("sidebar-font-size = 18")
XCTAssertEqual(config.sidebarFontSize, 18, accuracy: 0.0001)
}
}

final class SidebarBackgroundConfigTests: XCTestCase {

func testParseSidebarBackgroundSingleHex() {
Expand Down
84 changes: 84 additions & 0 deletions cmuxTests/SidebarOrderingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -999,3 +999,87 @@ final class TerminalControllerSidebarDedupeTests: XCTestCase {
)
}
}

final class SidebarTabItemSettingsSnapshotFontScaleTests: XCTestCase {

/// A UserDefaults suite isolated to this test class so we don't mutate
/// the real user defaults used by other tests / the host app.
private var defaults: UserDefaults!
private var suiteName: String!

override func setUp() {
super.setUp()
suiteName = "cmux-tests-\(UUID().uuidString)"
defaults = UserDefaults(suiteName: suiteName)
}

override func tearDown() {
defaults.removePersistentDomain(forName: suiteName)
defaults = nil
suiteName = nil
super.tearDown()
}

func testDefaultConfigYieldsUnitScale() {
let snapshot = SidebarTabItemSettingsSnapshot(
defaults: defaults,
sidebarFontSizeProvider: { GhosttyConfig.defaultSidebarFontSize }
)
XCTAssertEqual(snapshot.sidebarFontScale, 1.0, accuracy: 0.0001)
}

func testPrimaryEighteenScalesProportionally() {
let snapshot = SidebarTabItemSettingsSnapshot(
defaults: defaults,
sidebarFontSizeProvider: { 18 }
)
let expected = 18 / GhosttyConfig.defaultSidebarFontSize
XCTAssertEqual(snapshot.sidebarFontScale, expected, accuracy: 0.0001)
}

func testBelowMinClampsUp() {
let snapshot = SidebarTabItemSettingsSnapshot(
defaults: defaults,
sidebarFontSizeProvider: { 4 }
)
let expected = GhosttyConfig.minSidebarFontSize / GhosttyConfig.defaultSidebarFontSize
XCTAssertEqual(snapshot.sidebarFontScale, expected, accuracy: 0.0001)
}

func testAboveMaxClampsDown() {
let snapshot = SidebarTabItemSettingsSnapshot(
defaults: defaults,
sidebarFontSizeProvider: { 9999 }
)
let expected = GhosttyConfig.maxSidebarFontSize / GhosttyConfig.defaultSidebarFontSize
XCTAssertEqual(snapshot.sidebarFontScale, expected, accuracy: 0.0001)
}

/// If this test ever passes incorrectly (i.e. two snapshots with
/// different scales compare equal), TabItemView's
/// `.equatable()` will skip re-rendering on font-size changes and the
/// live-reload path will silently break.
func testSnapshotEqualityReflectsScaleDifference() {
let base = SidebarTabItemSettingsSnapshot(
defaults: defaults,
sidebarFontSizeProvider: { GhosttyConfig.defaultSidebarFontSize }
)
let bigger = SidebarTabItemSettingsSnapshot(
defaults: defaults,
sidebarFontSizeProvider: { 18 }
)
XCTAssertNotEqual(base, bigger)
}

func testSnapshotEqualityHoldsWhenScaleMatches() {
let a = SidebarTabItemSettingsSnapshot(
defaults: defaults,
sidebarFontSizeProvider: { 14 }
)
let b = SidebarTabItemSettingsSnapshot(
defaults: defaults,
sidebarFontSizeProvider: { 14 }
)
XCTAssertEqual(a, b)
}
}