mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-07-31 04:39:01 +00:00
config: support unlimited scrollback limits
This commit is contained in:
@@ -41,6 +41,7 @@ const ClipboardCodepointMap = @import("ClipboardCodepointMap.zig");
|
||||
const KeyRemapSet = @import("../input/key_mods.zig").RemapSet;
|
||||
pub const WindowPaddingBalance = @import("../renderer/size.zig").PaddingBalance;
|
||||
const string = @import("string.zig");
|
||||
const Limit = @import("limit.zig").Limit;
|
||||
|
||||
// We do this instead of importing all of terminal/main.zig to
|
||||
// limit the dependency graph. This is important because some things
|
||||
@@ -1383,10 +1384,10 @@ input: RepeatableReadableIO = .{},
|
||||
/// are set, then the first one reached will determine when scrollback is
|
||||
/// removed.
|
||||
///
|
||||
/// It is not currently possible to set an unlimited byte limit.
|
||||
/// The default is 50 MB. Set this to `unlimited` to remove the byte limit.
|
||||
///
|
||||
/// This can be changed at runtime but will only affect new terminal surfaces.
|
||||
@"scrollback-limit-bytes": usize = 50_000_000, // 50MB
|
||||
@"scrollback-limit-bytes": Limit(usize, 50_000_000) = .default,
|
||||
|
||||
/// The maximum number of lines of scrollback to retain. This excludes
|
||||
/// the active screen. Soft-wrapped lines count as multiple lines.
|
||||
@@ -1399,12 +1400,12 @@ input: RepeatableReadableIO = .{},
|
||||
/// This means that the actual limited lines will likely be slightly
|
||||
/// higher in practice.
|
||||
///
|
||||
/// If this is unset, then no line limit will be applied. A separate maximum
|
||||
/// can be set with `scrollback-limit-bytes`; if both limits are set, then the
|
||||
/// first one reached will determine when scrollback is removed.
|
||||
/// The default is `unlimited`. A separate maximum can be set with
|
||||
/// `scrollback-limit-bytes`; if both limits are set, then the first one reached
|
||||
/// will determine when scrollback is removed.
|
||||
///
|
||||
/// This can be changed at runtime but will only affect new terminal surfaces.
|
||||
@"scrollback-limit-lines": ?usize = null,
|
||||
@"scrollback-limit-lines": Limit(usize, std.math.maxInt(usize)) = .default,
|
||||
|
||||
/// Whether to compress scrollback pages while the terminal is idle.
|
||||
///
|
||||
@@ -10856,6 +10857,15 @@ test "scrollback limits" {
|
||||
|
||||
var cfg = try Config.default(alloc);
|
||||
defer cfg.deinit();
|
||||
try testing.expectEqual(
|
||||
@as(usize, 50_000_000),
|
||||
cfg.@"scrollback-limit-bytes".value,
|
||||
);
|
||||
try testing.expectEqual(
|
||||
std.math.maxInt(usize),
|
||||
cfg.@"scrollback-limit-lines".value,
|
||||
);
|
||||
|
||||
var it: TestIterator = .{ .data = &.{
|
||||
"--scrollback-limit-bytes=1234",
|
||||
"--scrollback-limit-lines=567",
|
||||
@@ -10864,21 +10874,41 @@ test "scrollback limits" {
|
||||
|
||||
try testing.expectEqual(
|
||||
@as(usize, 1234),
|
||||
cfg.@"scrollback-limit-bytes",
|
||||
cfg.@"scrollback-limit-bytes".value,
|
||||
);
|
||||
try testing.expectEqual(
|
||||
@as(?usize, 567),
|
||||
cfg.@"scrollback-limit-lines",
|
||||
@as(usize, 567),
|
||||
cfg.@"scrollback-limit-lines".value,
|
||||
);
|
||||
|
||||
var unset_it: TestIterator = .{ .data = &.{
|
||||
var unlimited_it: TestIterator = .{ .data = &.{
|
||||
"--scrollback-limit-bytes=unlimited",
|
||||
"--scrollback-limit-lines=unlimited",
|
||||
} };
|
||||
try cfg.loadIter(alloc, &unlimited_it);
|
||||
|
||||
try testing.expectEqual(
|
||||
std.math.maxInt(usize),
|
||||
cfg.@"scrollback-limit-bytes".value,
|
||||
);
|
||||
try testing.expectEqual(
|
||||
std.math.maxInt(usize),
|
||||
cfg.@"scrollback-limit-lines".value,
|
||||
);
|
||||
|
||||
var reset_it: TestIterator = .{ .data = &.{
|
||||
"--scrollback-limit-bytes=",
|
||||
"--scrollback-limit-lines=",
|
||||
} };
|
||||
try cfg.loadIter(alloc, &unset_it);
|
||||
try cfg.loadIter(alloc, &reset_it);
|
||||
|
||||
try testing.expectEqual(
|
||||
@as(?usize, null),
|
||||
cfg.@"scrollback-limit-lines",
|
||||
@as(usize, 50_000_000),
|
||||
cfg.@"scrollback-limit-bytes".value,
|
||||
);
|
||||
try testing.expectEqual(
|
||||
std.math.maxInt(usize),
|
||||
cfg.@"scrollback-limit-lines".value,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10895,7 +10925,17 @@ test "compatibility: scrollback-limit renamed to bytes" {
|
||||
|
||||
try testing.expectEqual(
|
||||
@as(usize, 1234),
|
||||
cfg.@"scrollback-limit-bytes",
|
||||
cfg.@"scrollback-limit-bytes".value,
|
||||
);
|
||||
|
||||
var unlimited_it: TestIterator = .{ .data = &.{
|
||||
"--scrollback-limit=unlimited",
|
||||
} };
|
||||
try cfg.loadIter(alloc, &unlimited_it);
|
||||
|
||||
try testing.expectEqual(
|
||||
std.math.maxInt(usize),
|
||||
cfg.@"scrollback-limit-bytes".value,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
115
src/config/limit.zig
Normal file
115
src/config/limit.zig
Normal file
@@ -0,0 +1,115 @@
|
||||
const std = @import("std");
|
||||
const formatterpkg = @import("formatter.zig");
|
||||
|
||||
/// A configurable integer limit with a declared default and support for the
|
||||
/// special value `unlimited`.
|
||||
///
|
||||
/// Unlimited is stored as the maximum value of the integer type so the value
|
||||
/// remains directly comparable without optional unwrapping.
|
||||
pub fn Limit(comptime T: type, comptime default_value: T) type {
|
||||
comptime std.debug.assert(@typeInfo(T) == .int);
|
||||
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
value: T = default_value,
|
||||
|
||||
/// The limit initialized with its declared default value.
|
||||
pub const default: Self = .{};
|
||||
|
||||
/// Parses an integer limit or the special value "unlimited".
|
||||
pub fn parseCLI(input_: ?[]const u8) !Self {
|
||||
const input = input_ orelse return error.ValueRequired;
|
||||
if (std.mem.eql(u8, input, "unlimited")) {
|
||||
return .{ .value = std.math.maxInt(T) };
|
||||
}
|
||||
|
||||
return .{
|
||||
.value = std.fmt.parseInt(T, input, 0) catch
|
||||
return error.InvalidValue,
|
||||
};
|
||||
}
|
||||
|
||||
/// Formats the maximum integer value as "unlimited" and all other
|
||||
/// values as integers.
|
||||
pub fn formatEntry(
|
||||
self: Self,
|
||||
formatter: formatterpkg.EntryFormatter,
|
||||
) !void {
|
||||
if (self.value == std.math.maxInt(T)) {
|
||||
try formatter.formatEntry([]const u8, "unlimited");
|
||||
} else {
|
||||
try formatter.formatEntry(T, self.value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the configured value, or null for the unlimited sentinel.
|
||||
pub fn optional(self: Self) ?T {
|
||||
if (self.value == std.math.maxInt(T)) return null;
|
||||
return self.value;
|
||||
}
|
||||
|
||||
/// Returns an independent copy of this value for Config cloning.
|
||||
pub fn clone(
|
||||
self: Self,
|
||||
_: std.mem.Allocator,
|
||||
) std.mem.Allocator.Error!Self {
|
||||
return self;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test "Limit default and parsing" {
|
||||
const testing = std.testing;
|
||||
const TestLimit = Limit(usize, 42);
|
||||
|
||||
try testing.expectEqual(@as(usize, 42), TestLimit.default.value);
|
||||
try testing.expectEqual(
|
||||
@as(usize, 123),
|
||||
(try TestLimit.parseCLI("123")).value,
|
||||
);
|
||||
try testing.expectEqual(
|
||||
std.math.maxInt(usize),
|
||||
(try TestLimit.parseCLI("unlimited")).value,
|
||||
);
|
||||
try testing.expectError(error.InvalidValue, TestLimit.parseCLI("invalid"));
|
||||
}
|
||||
|
||||
test "Limit optional" {
|
||||
const testing = std.testing;
|
||||
const TestLimit = Limit(u16, 42);
|
||||
|
||||
try testing.expectEqual(@as(?u16, 42), (TestLimit{}).optional());
|
||||
try testing.expectEqual(
|
||||
@as(?u16, null),
|
||||
(TestLimit{ .value = std.math.maxInt(u16) }).optional(),
|
||||
);
|
||||
}
|
||||
|
||||
test "Limit formatting" {
|
||||
const testing = std.testing;
|
||||
const TestLimit = Limit(usize, 42);
|
||||
|
||||
{
|
||||
var buf: std.Io.Writer.Allocating = .init(testing.allocator);
|
||||
defer buf.deinit();
|
||||
|
||||
try (TestLimit{ .value = 123 }).formatEntry(
|
||||
formatterpkg.entryFormatter("limit", &buf.writer),
|
||||
);
|
||||
try testing.expectEqualStrings("limit = 123\n", buf.written());
|
||||
}
|
||||
|
||||
{
|
||||
var buf: std.Io.Writer.Allocating = .init(testing.allocator);
|
||||
defer buf.deinit();
|
||||
|
||||
try (TestLimit{ .value = std.math.maxInt(usize) }).formatEntry(
|
||||
formatterpkg.entryFormatter("limit", &buf.writer),
|
||||
);
|
||||
try testing.expectEqualStrings(
|
||||
"limit = unlimited\n",
|
||||
buf.written(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -246,8 +246,8 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void {
|
||||
break :opts .{
|
||||
.cols = grid_size.columns,
|
||||
.rows = grid_size.rows,
|
||||
.max_scrollback_bytes = opts.full_config.@"scrollback-limit-bytes",
|
||||
.max_scrollback_lines = opts.full_config.@"scrollback-limit-lines",
|
||||
.max_scrollback_bytes = opts.full_config.@"scrollback-limit-bytes".optional(),
|
||||
.max_scrollback_lines = opts.full_config.@"scrollback-limit-lines".optional(),
|
||||
.default_modes = default_modes,
|
||||
.default_cursor_style = opts.config.cursor_style,
|
||||
.default_cursor_blink = opts.config.cursor_blink,
|
||||
|
||||
Reference in New Issue
Block a user