Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,19 @@ public protocol SettingsHostActions: AnyObject {
@discardableResult
func setSurfaceTabBarFontSize(_ points: Double) async -> Bool

/// Whether surface tabs stretch to fill their pane's available tab-bar
/// width. Backed by the Ghostty config file (`surface-tabs-fill-pane-width`).
func surfaceTabsFillPaneWidth() -> Bool

/// Persists the surface-tabs-fill-pane-width flag to the Ghostty config and
/// live-reloads open windows.
///
/// - Returns: `true` if the value was written and reloaded, `false` if
/// persistence failed. The disk write happens off the main actor, so this
/// is `async`; call it from a `Task` in the toggle action.
@discardableResult
func setSurfaceTabsFillPaneWidth(_ enabled: Bool) async -> Bool

/// Formats a point size for display next to a font-size slider
/// (e.g. `12`, `13.5`), trimming trailing zeros.
func formattedFontSize(_ points: Double) -> String
Expand Down Expand Up @@ -172,6 +185,10 @@ public extension SettingsHostActions {

func setSurfaceTabBarFontSize(_ points: Double) async -> Bool { true }

func surfaceTabsFillPaneWidth() -> Bool { false }

func setSurfaceTabsFillPaneWidth(_ enabled: Bool) async -> Bool { true }

func formattedFontSize(_ points: Double) -> String {
let scaled = (points * 100).rounded()
let whole = Int(scaled / 100)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ public struct TerminalSection: View {
@State private var surfaceTabBarFont: SettingsFontSize
@State private var fontSaveFailed = false
@State private var fontSaveTask: Task<Void, Never>?
@State private var tabsFillPaneWidth: Bool
@State private var tabsFillPaneWidthLastSaved: Bool
@State private var tabsFillSaveFailed = false
@State private var tabsFillSaveTask: Task<Void, Never>?
@State private var tabsFillSaveGeneration = 0
@State private var scrollBar: DefaultsValueModel<Bool>
@State private var copyOnSelect: DefaultsValueModel<Bool>
@State private var autoResume: DefaultsValueModel<Bool>
Expand All @@ -31,6 +36,9 @@ public struct TerminalSection: View {
self.catalog = catalog
self.hostActions = hostActions
_surfaceTabBarFont = State(initialValue: hostActions.surfaceTabBarFontSize())
let initialTabsFillPaneWidth = hostActions.surfaceTabsFillPaneWidth()
_tabsFillPaneWidth = State(initialValue: initialTabsFillPaneWidth)
_tabsFillPaneWidthLastSaved = State(initialValue: initialTabsFillPaneWidth)
_scrollBar = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.terminal.showScrollBar))
_copyOnSelect = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.terminal.copyOnSelect))
_autoResume = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.terminal.autoResumeAgentSessions))
Expand Down Expand Up @@ -58,6 +66,32 @@ public struct TerminalSection: View {
}
}

/// Persists the stretch-tabs-to-fill flag in request order while cancelling
/// superseded saves that have not started writing yet.
private func saveTabsFillPaneWidth(_ enabled: Bool) {
let previousSaveTask = tabsFillSaveTask
previousSaveTask?.cancel()
tabsFillSaveGeneration += 1
let saveGeneration = tabsFillSaveGeneration
tabsFillSaveFailed = false
tabsFillSaveTask = Task {
if let previousSaveTask {
await previousSaveTask.value
}
guard !Task.isCancelled else { return }
let saved = await hostActions.setSurfaceTabsFillPaneWidth(enabled)
if saved {
tabsFillPaneWidthLastSaved = enabled
Comment thread
austinywang marked this conversation as resolved.
if saveGeneration == tabsFillSaveGeneration {
tabsFillSaveFailed = false
}
Comment thread
austinywang marked this conversation as resolved.
} else if saveGeneration == tabsFillSaveGeneration && !Task.isCancelled {
tabsFillPaneWidth = tabsFillPaneWidthLastSaved
tabsFillSaveFailed = true
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
}

@ViewBuilder
private var resumeCommandsCard: some View {
SettingsCard {
Expand Down Expand Up @@ -129,6 +163,33 @@ public struct TerminalSection: View {
}
}
SettingsCardDivider()
SettingsCardRow(
configurationReview: .settingsOnly,
String(localized: "settings.terminal.tabsFillPaneWidth", defaultValue: "Stretch Tabs to Fill Pane Width"),
subtitle: tabsFillPaneWidth
? String(localized: "settings.terminal.tabsFillPaneWidth.subtitleOn", defaultValue: "Tabs stretch to fill each pane's tab bar. A single tab spans the full width; multiple tabs share it evenly and scroll only when they overflow.")
: String(localized: "settings.terminal.tabsFillPaneWidth.subtitleOff", defaultValue: "Tabs use a fixed width and scroll horizontally when they overflow the pane."),
controlWidth: 250
) {
VStack(alignment: .trailing, spacing: 4) {
Toggle("", isOn: Binding(get: { tabsFillPaneWidth }, set: { newValue in
tabsFillPaneWidth = newValue
saveTabsFillPaneWidth(newValue)
}))
.labelsHidden()
.controlSize(.small)
.accessibilityIdentifier("SettingsTerminalTabsFillPaneWidthToggle")

if tabsFillSaveFailed {
Text(String(localized: "settings.terminal.tabsFillPaneWidth.saveFailed", defaultValue: "Couldn't save tab stretch setting. Please try again."))
.font(.caption)
.foregroundStyle(.red)
.multilineTextAlignment(.trailing)
.fixedSize(horizontal: false, vertical: true)
}
}
}
SettingsCardDivider()
SettingsCardRow(
configurationReview: .json("terminal.showScrollBar"),
String(localized: "settings.terminal.scrollBar", defaultValue: "Show Terminal Scroll Bar"),
Expand Down
Loading
Loading