diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 7e346a71f1b8..7955f37c14c1 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -7811,8 +7811,19 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent guard let tabManager else { return } let tab = tabManager.addTab() let config = GhosttyConfig.load() - let lineCount = min(max(config.scrollbackLimit * 2, 2000), 60000) - let command = "for i in {1..\(lineCount)}; do printf \"scrollback %06d\\n\" $i; done\n" + let minimumTargetBytes = 2_000_000 + let maximumTargetBytes = 200_000_000 + let minimumLineCount = 2000 + let effectiveLimit = max(config.scrollbackLimit, 0) + let doubledLimit = min(effectiveLimit, maximumTargetBytes / 2) * 2 + let targetBytes = min(max(doubledLimit, minimumTargetBytes), maximumTargetBytes) + // `%06d` guarantees at least a 6-digit field width. Any lines beyond + // 999,999 only get wider, so this conservative floor always emits at + // least `targetBytes` without oscillating at digit-count boundaries. + let baseBytesPerLine = "scrollback 000000\n".utf8.count + let lineCount = max((targetBytes + baseBytesPerLine - 1) / baseBytesPerLine, minimumLineCount) + + let command = #"awk 'BEGIN { for (i = 1; i <= \#(lineCount); ++i) printf "scrollback %06d\n", i }'"# + "\n" sendTextWhenReady(command, to: tab) } diff --git a/Sources/GhosttyConfig.swift b/Sources/GhosttyConfig.swift index c40267b1524a..58ce3f7cd1e5 100644 --- a/Sources/GhosttyConfig.swift +++ b/Sources/GhosttyConfig.swift @@ -16,7 +16,8 @@ struct GhosttyConfig { var surfaceTabBarFontSize: CGFloat = 11 var theme: String? var workingDirectory: String? - var scrollbackLimit: Int = 10000 + // Ghostty measures scrollback-limit in bytes, not lines. + var scrollbackLimit: Int = 10_000_000 var unfocusedSplitOpacity: Double = 0.7 var unfocusedSplitFill: NSColor? var splitDividerColor: NSColor? @@ -252,7 +253,7 @@ struct GhosttyConfig { case "working-directory": workingDirectory = value case "scrollback-limit": - if let limit = Int(value) { + if let limit = Self.parseIntegerLiteral(value) { scrollbackLimit = limit } case "background": @@ -316,6 +317,16 @@ struct GhosttyConfig { } } + private static func parseIntegerLiteral(_ value: String) -> Int? { + // Strip digit-group separators (for example 10_000_000). + // Hex and float literals are intentionally unsupported here. + let normalized = value.replacingOccurrences(of: "_", with: "") + guard let parsed = Int(normalized), parsed >= 0 else { + return nil + } + return parsed + } + mutating func loadTheme(_ name: String) { loadTheme( name, diff --git a/web/app/[locale]/docs/configuration/page.tsx b/web/app/[locale]/docs/configuration/page.tsx index 19b518d8e479..cb79461c6597 100644 --- a/web/app/[locale]/docs/configuration/page.tsx +++ b/web/app/[locale]/docs/configuration/page.tsx @@ -247,7 +247,7 @@ touch ~/.config/ghostty/config`} {`font-family = SF Mono font-size = 13 theme = One Dark -scrollback-limit = 50000 +scrollback-limit = 50000000 split-divider-color = #3e4451 working-directory = ~/code`}