terminal: explicit error sets in Screen and ScreenSet

This commit is contained in:
Mitchell Hashimoto
2026-01-21 09:52:31 -08:00
parent 3d2152f5e8
commit 82b10ae7af
10 changed files with 70 additions and 52 deletions

View File

@@ -781,7 +781,11 @@ pub fn clone(
alloc: Allocator,
opts: Clone,
) !PageList {
var it = self.pageIterator(.right_down, opts.top, opts.bot);
var it = self.pageIterator(
.right_down,
opts.top,
opts.bot,
);
// First, count our pages so our preheat is exactly what we need.
var it_copy = it;
@@ -2698,7 +2702,7 @@ fn scrollPrompt(self: *PageList, delta: isize) void {
/// Clear the screen by scrolling written contents up into the scrollback.
/// This will not update the viewport.
pub fn scrollClear(self: *PageList) !void {
pub fn scrollClear(self: *PageList) Allocator.Error!void {
defer self.assertIntegrity();
// Go through the active area backwards to find the first non-empty
@@ -4010,7 +4014,10 @@ pub fn getCell(self: *const PageList, pt: point.Point) ?Cell {
/// 1 | etc.| | 4
/// +-----+ :
/// +--------+
pub fn diagram(self: *const PageList, writer: *std.Io.Writer) !void {
pub fn diagram(
self: *const PageList,
writer: *std.Io.Writer,
) std.Io.Writer.Error!void {
const active_pin = self.getTopLeft(.active);
var active = false;
@@ -4647,7 +4654,7 @@ pub fn totalPages(self: *const PageList) usize {
/// Grow the number of rows available in the page list by n.
/// This is only used for testing so it isn't optimized in any way.
fn growRows(self: *PageList, n: usize) !void {
fn growRows(self: *PageList, n: usize) Allocator.Error!void {
for (0..n) |_| _ = try self.grow();
}

View File

@@ -241,7 +241,7 @@ pub const Options = struct {
pub fn init(
alloc: Allocator,
opts: Options,
) !Screen {
) Allocator.Error!Screen {
// Initialize our backing pages.
var pages = try PageList.init(
alloc,
@@ -2324,7 +2324,7 @@ pub fn cursorSetHyperlink(self: *Screen) PageList.IncreaseCapacityError!void {
}
/// Set the selection to the given selection. If this is a tracked selection
/// then the screen will take overnship of the selection. If this is untracked
/// then the screen will take ownership of the selection. If this is untracked
/// then the screen will convert it to tracked internally. This will automatically
/// untrack the prior selection (if any).
///
@@ -2333,7 +2333,7 @@ pub fn cursorSetHyperlink(self: *Screen) PageList.IncreaseCapacityError!void {
/// This is always recommended over setting `selection` directly. Beyond
/// managing memory for you, it also performs safety checks that the selection
/// is always tracked.
pub fn select(self: *Screen, sel_: ?Selection) !void {
pub fn select(self: *Screen, sel_: ?Selection) Allocator.Error!void {
const sel = sel_ orelse {
self.clearSelection();
return;

View File

@@ -32,7 +32,7 @@ all: std.EnumMap(Key, *Screen),
pub fn init(
alloc: Allocator,
opts: Screen.Options,
) !ScreenSet {
) Allocator.Error!ScreenSet {
// We need to initialize our initial primary screen
const screen = try alloc.create(Screen);
errdefer alloc.destroy(screen);
@@ -64,7 +64,7 @@ pub fn getInit(
alloc: Allocator,
key: Key,
opts: Screen.Options,
) !*Screen {
) Allocator.Error!*Screen {
if (self.get(key)) |screen| return screen;
const screen = try alloc.create(Screen);
errdefer alloc.destroy(screen);

View File

@@ -3,6 +3,7 @@ const Selection = @This();
const std = @import("std");
const assert = @import("../quirks.zig").inlineAssert;
const Allocator = std.mem.Allocator;
const page = @import("page.zig");
const point = @import("point.zig");
const PageList = @import("PageList.zig");
@@ -126,7 +127,7 @@ pub fn tracked(self: *const Selection) bool {
/// Convert this selection a tracked selection. It is asserted this is
/// an untracked selection. The tracked selection is returned.
pub fn track(self: *const Selection, s: *Screen) !Selection {
pub fn track(self: *const Selection, s: *Screen) Allocator.Error!Selection {
assert(!self.tracked());
// Track our pins

View File

@@ -1118,7 +1118,7 @@ pub fn cursorIsAtPrompt(self: *Terminal) bool {
/// Horizontal tab moves the cursor to the next tabstop, clearing
/// the screen to the left the tabstop.
pub fn horizontalTab(self: *Terminal) !void {
pub fn horizontalTab(self: *Terminal) void {
while (self.screens.active.cursor.x < self.scrolling_region.right) {
// Move the cursor right
self.screens.active.cursorRight(1);
@@ -1131,7 +1131,7 @@ pub fn horizontalTab(self: *Terminal) !void {
}
// Same as horizontalTab but moves to the previous tabstop instead of the next.
pub fn horizontalTabBack(self: *Terminal) !void {
pub fn horizontalTabBack(self: *Terminal) void {
// With origin mode enabled, our leftmost limit is the left margin.
const left_limit = if (self.modes.get(.origin)) self.scrolling_region.left else 0;
@@ -4736,17 +4736,17 @@ test "Terminal: horizontal tabs" {
// HT
try t.print('1');
try t.horizontalTab();
t.horizontalTab();
try testing.expectEqual(@as(usize, 8), t.screens.active.cursor.x);
// HT
try t.horizontalTab();
t.horizontalTab();
try testing.expectEqual(@as(usize, 16), t.screens.active.cursor.x);
// HT at the end
try t.horizontalTab();
t.horizontalTab();
try testing.expectEqual(@as(usize, 19), t.screens.active.cursor.x);
try t.horizontalTab();
t.horizontalTab();
try testing.expectEqual(@as(usize, 19), t.screens.active.cursor.x);
}
@@ -4758,7 +4758,7 @@ test "Terminal: horizontal tabs starting on tabstop" {
t.setCursorPos(t.screens.active.cursor.y, 9);
try t.print('X');
t.setCursorPos(t.screens.active.cursor.y, 9);
try t.horizontalTab();
t.horizontalTab();
try t.print('A');
{
@@ -4777,7 +4777,7 @@ test "Terminal: horizontal tabs with right margin" {
t.scrolling_region.right = 5;
t.setCursorPos(t.screens.active.cursor.y, 1);
try t.print('X');
try t.horizontalTab();
t.horizontalTab();
try t.print('A');
{
@@ -4796,17 +4796,17 @@ test "Terminal: horizontal tabs back" {
t.setCursorPos(t.screens.active.cursor.y, 20);
// HT
try t.horizontalTabBack();
t.horizontalTabBack();
try testing.expectEqual(@as(usize, 16), t.screens.active.cursor.x);
// HT
try t.horizontalTabBack();
t.horizontalTabBack();
try testing.expectEqual(@as(usize, 8), t.screens.active.cursor.x);
// HT
try t.horizontalTabBack();
t.horizontalTabBack();
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x);
try t.horizontalTabBack();
t.horizontalTabBack();
try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x);
}
@@ -4818,7 +4818,7 @@ test "Terminal: horizontal tabs back starting on tabstop" {
t.setCursorPos(t.screens.active.cursor.y, 9);
try t.print('X');
t.setCursorPos(t.screens.active.cursor.y, 9);
try t.horizontalTabBack();
t.horizontalTabBack();
try t.print('A');
{
@@ -4838,7 +4838,7 @@ test "Terminal: horizontal tabs with left margin in origin mode" {
t.scrolling_region.right = 5;
t.setCursorPos(1, 2);
try t.print('X');
try t.horizontalTabBack();
t.horizontalTabBack();
try t.print('A');
{
@@ -4858,7 +4858,7 @@ test "Terminal: horizontal tab back with cursor before left margin" {
t.modes.set(.enable_left_and_right_margin, true);
t.setLeftAndRightMargin(5, 0);
t.restoreCursor();
try t.horizontalTabBack();
t.horizontalTabBack();
try t.print('X');
{
@@ -10593,11 +10593,11 @@ test "Terminal: tabClear single" {
var t = try init(alloc, .{ .cols = 30, .rows = 5 });
defer t.deinit(alloc);
try t.horizontalTab();
t.horizontalTab();
t.tabClear(.current);
try testing.expect(!t.isDirty(.{ .active = .{ .x = 0, .y = 0 } }));
t.setCursorPos(1, 1);
try t.horizontalTab();
t.horizontalTab();
try testing.expectEqual(@as(usize, 16), t.screens.active.cursor.x);
}
@@ -10609,7 +10609,7 @@ test "Terminal: tabClear all" {
t.tabClear(.all);
try testing.expect(!t.isDirty(.{ .active = .{ .x = 0, .y = 0 } }));
t.setCursorPos(1, 1);
try t.horizontalTab();
t.horizontalTab();
try testing.expectEqual(@as(usize, 29), t.screens.active.cursor.x);
}

View File

@@ -168,7 +168,7 @@ pub const Name = enum(u8) {
}
/// Default colors for tagged values.
pub fn default(self: Name) !RGB {
pub fn default(self: Name) error{NoDefaultValue}!RGB {
return switch (self) {
.black => RGB{ .r = 0x1D, .g = 0x1F, .b = 0x21 },
.red => RGB{ .r = 0xCC, .g = 0x66, .b = 0x66 },
@@ -355,7 +355,7 @@ pub const RGB = packed struct(u24) {
/// Parse a color from a floating point intensity value.
///
/// The value should be between 0.0 and 1.0, inclusive.
fn fromIntensity(value: []const u8) !u8 {
fn fromIntensity(value: []const u8) error{InvalidFormat}!u8 {
const i = std.fmt.parseFloat(f64, value) catch {
@branchHint(.cold);
return error.InvalidFormat;
@@ -372,7 +372,7 @@ pub const RGB = packed struct(u24) {
///
/// The string can contain 1, 2, 3, or 4 characters and represents the color
/// value scaled in 4, 8, 12, or 16 bits, respectively.
fn fromHex(value: []const u8) !u8 {
fn fromHex(value: []const u8) error{InvalidFormat}!u8 {
if (value.len == 0 or value.len > 4) {
@branchHint(.cold);
return error.InvalidFormat;
@@ -414,7 +414,7 @@ pub const RGB = packed struct(u24) {
/// where `r`, `g`, and `b` are a single hexadecimal digit.
/// These specify a color with 4, 8, 12, and 16 bits of precision
/// per color channel.
pub fn parse(value: []const u8) !RGB {
pub fn parse(value: []const u8) error{InvalidFormat}!RGB {
if (value.len == 0) {
@branchHint(.cold);
return error.InvalidFormat;

View File

@@ -44,16 +44,23 @@ pub const OSC = struct {
return {};
}
fn update(self: *OSC, key: u8, value: []const u8) !void {
fn update(self: *OSC, key: u8, value: []const u8) error{
UnknownKey,
InvalidValue,
}!void {
// All values are numeric, so we can do a small hack here
const v = try std.fmt.parseInt(u4, value, 10);
const v = std.fmt.parseInt(
u4,
value,
10,
) catch return error.InvalidValue;
switch (key) {
's' => {
if (v == 0) return error.InvalidValue;
self.scale = std.math.cast(u3, v) orelse return error.Overflow;
self.scale = std.math.cast(u3, v) orelse return error.InvalidValue;
},
'w' => self.width = std.math.cast(u3, v) orelse return error.Overflow,
'w' => self.width = std.math.cast(u3, v) orelse return error.InvalidValue,
'n' => self.numerator = v,
'd' => self.denominator = v,
'v' => self.valign = std.enums.fromInt(VAlign, v) orelse return error.InvalidValue,
@@ -130,7 +137,7 @@ pub fn parse(parser: *Parser, _: ?u8) ?*Command {
cmd.update(k[0], value) catch |err| {
switch (err) {
error.UnknownKey => log.warn("unknown key: '{c}'", .{k[0]}),
else => log.warn("invalid value for key '{c}': {}", .{ k[0], err }),
error.InvalidValue => log.warn("invalid value for key '{c}': {}", .{ k[0], err }),
}
continue;
};

View File

@@ -102,8 +102,8 @@ pub const Handler = struct {
.delete_lines => self.terminal.deleteLines(value),
.scroll_up => try self.terminal.scrollUp(value),
.scroll_down => self.terminal.scrollDown(value),
.horizontal_tab => try self.horizontalTab(value),
.horizontal_tab_back => try self.horizontalTabBack(value),
.horizontal_tab => self.horizontalTab(value),
.horizontal_tab_back => self.horizontalTabBack(value),
.tab_clear_current => self.terminal.tabClear(.current),
.tab_clear_all => self.terminal.tabClear(.all),
.tab_set => self.terminal.tabSet(),
@@ -200,18 +200,18 @@ pub const Handler = struct {
}
}
inline fn horizontalTab(self: *Handler, count: u16) !void {
inline fn horizontalTab(self: *Handler, count: u16) void {
for (0..count) |_| {
const x = self.terminal.screens.active.cursor.x;
try self.terminal.horizontalTab();
self.terminal.horizontalTab();
if (x == self.terminal.screens.active.cursor.x) break;
}
}
inline fn horizontalTabBack(self: *Handler, count: u16) !void {
inline fn horizontalTabBack(self: *Handler, count: u16) void {
for (0..count) |_| {
const x = self.terminal.screens.active.cursor.x;
try self.terminal.horizontalTabBack();
self.terminal.horizontalTabBack();
if (x == self.terminal.screens.active.cursor.x) break;
}
}

View File

@@ -3,11 +3,14 @@ const assert = @import("../quirks.zig").inlineAssert;
const RGB = @import("color.zig").RGB;
/// The map of all available X11 colors.
pub const map = colorMap() catch @compileError("failed to parse rgb.txt");
pub const map = colorMap();
pub const ColorMap = std.StaticStringMapWithEql(RGB, std.static_string_map.eqlAsciiIgnoreCase);
pub const ColorMap = std.StaticStringMapWithEql(
RGB,
std.static_string_map.eqlAsciiIgnoreCase,
);
fn colorMap() !ColorMap {
fn colorMap() ColorMap {
@setEvalBranchQuota(500_000);
const KV = struct { []const u8, RGB };

View File

@@ -198,8 +198,8 @@ pub const StreamHandler = struct {
.print_repeat => try self.terminal.printRepeat(value),
.bell => self.bell(),
.backspace => self.terminal.backspace(),
.horizontal_tab => try self.horizontalTab(value),
.horizontal_tab_back => try self.horizontalTabBack(value),
.horizontal_tab => self.horizontalTab(value),
.horizontal_tab_back => self.horizontalTabBack(value),
.linefeed => {
@branchHint(.likely);
try self.linefeed();
@@ -560,18 +560,18 @@ pub const StreamHandler = struct {
self.surfaceMessageWriter(.ring_bell);
}
inline fn horizontalTab(self: *StreamHandler, count: u16) !void {
inline fn horizontalTab(self: *StreamHandler, count: u16) void {
for (0..count) |_| {
const x = self.terminal.screens.active.cursor.x;
try self.terminal.horizontalTab();
self.terminal.horizontalTab();
if (x == self.terminal.screens.active.cursor.x) break;
}
}
inline fn horizontalTabBack(self: *StreamHandler, count: u16) !void {
inline fn horizontalTabBack(self: *StreamHandler, count: u16) void {
for (0..count) |_| {
const x = self.terminal.screens.active.cursor.x;
try self.terminal.horizontalTabBack();
self.terminal.horizontalTabBack();
if (x == self.terminal.screens.active.cursor.x) break;
}
}