From 5b126b9b98c01bd275cf00665e619ef4b361d90b Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Tue, 14 Apr 2026 21:26:44 -0700 Subject: [PATCH 1/3] Fix scrollback limit handling --- Sources/AppDelegate.swift | 9 +++++++-- Sources/GhosttyConfig.swift | 10 ++++++++-- web/app/[locale]/docs/configuration/page.tsx | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 7e346a71f1b8..b7dbe702f00c 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -7811,8 +7811,13 @@ 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 bytesPerLine = "scrollback 000000\n".utf8.count + let minimumTargetBytes = 2_000_000 + let maximumTargetBytes = 200_000_000 + let doubledLimit = min(config.scrollbackLimit, maximumTargetBytes / 2) * 2 + let targetBytes = min(max(doubledLimit, minimumTargetBytes), maximumTargetBytes) + let lineCount = max((targetBytes + bytesPerLine - 1) / bytesPerLine, 2000) + 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..ec77de959cdc 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,11 @@ struct GhosttyConfig { } } + private static func parseIntegerLiteral(_ value: String) -> Int? { + let normalized = value.replacingOccurrences(of: "_", with: "") + return Int(normalized) + } + 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`} From 8ec3a86a956c25248c29fa1074bf6510ab12a224 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Wed, 15 Apr 2026 17:11:36 -0700 Subject: [PATCH 2/3] Address PR review feedback --- Sources/AppDelegate.swift | 20 +++++++++++++++++--- Sources/GhosttyConfig.swift | 7 ++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index b7dbe702f00c..5e451fcbc806 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -7811,12 +7811,26 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent guard let tabManager else { return } let tab = tabManager.addTab() let config = GhosttyConfig.load() - let bytesPerLine = "scrollback 000000\n".utf8.count let minimumTargetBytes = 2_000_000 let maximumTargetBytes = 200_000_000 - let doubledLimit = min(config.scrollbackLimit, maximumTargetBytes / 2) * 2 + let effectiveLimit = max(config.scrollbackLimit, 0) + let doubledLimit = min(effectiveLimit, maximumTargetBytes / 2) * 2 let targetBytes = min(max(doubledLimit, minimumTargetBytes), maximumTargetBytes) - let lineCount = max((targetBytes + bytesPerLine - 1) / bytesPerLine, 2000) + let baseBytesPerLine = "scrollback \n".utf8.count + var digitCount = 6 + var lineCount = 2000 + + while true { + let bytesPerLine = baseBytesPerLine + digitCount + let nextLineCount = max((targetBytes + bytesPerLine - 1) / bytesPerLine, 2000) + let nextDigitCount = max(6, String(nextLineCount).count) + lineCount = nextLineCount + if nextDigitCount == digitCount { + break + } + digitCount = nextDigitCount + } + 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 ec77de959cdc..1bf4349709c8 100644 --- a/Sources/GhosttyConfig.swift +++ b/Sources/GhosttyConfig.swift @@ -318,8 +318,13 @@ 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: "") - return Int(normalized) + guard let parsed = Int(normalized), parsed > 0 else { + return nil + } + return parsed } mutating func loadTheme(_ name: String) { From 3856ffaa66b6c817ffc180fd20506cf5de9a7118 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Wed, 15 Apr 2026 19:05:33 -0700 Subject: [PATCH 3/3] Address remaining scrollback review feedback --- Sources/AppDelegate.swift | 20 ++++++-------------- Sources/GhosttyConfig.swift | 2 +- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 5e451fcbc806..7955f37c14c1 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -7813,23 +7813,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent let config = GhosttyConfig.load() 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) - let baseBytesPerLine = "scrollback \n".utf8.count - var digitCount = 6 - var lineCount = 2000 - - while true { - let bytesPerLine = baseBytesPerLine + digitCount - let nextLineCount = max((targetBytes + bytesPerLine - 1) / bytesPerLine, 2000) - let nextDigitCount = max(6, String(nextLineCount).count) - lineCount = nextLineCount - if nextDigitCount == digitCount { - break - } - digitCount = nextDigitCount - } + // `%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 1bf4349709c8..58ce3f7cd1e5 100644 --- a/Sources/GhosttyConfig.swift +++ b/Sources/GhosttyConfig.swift @@ -321,7 +321,7 @@ struct GhosttyConfig { // 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 { + guard let parsed = Int(normalized), parsed >= 0 else { return nil } return parsed