From 1e003b2e0fa794a1430be958a324afa227896c6a Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Tue, 5 Nov 2024 23:16:01 +0100 Subject: [PATCH 01/27] gtk: implement toggle_split_zoom --- src/apprt/gtk/App.zig | 9 ++++- src/apprt/gtk/Split.zig | 17 +++++++-- src/apprt/gtk/Surface.zig | 77 +++++++++++++++++++++++++++++++++++++-- src/input/Binding.zig | 3 +- 4 files changed, 95 insertions(+), 11 deletions(-) diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index e6bf24bd1..fa73c2436 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -471,12 +471,12 @@ pub fn performAction( .mouse_shape => try self.setMouseShape(target, value), .mouse_over_link => self.setMouseOverLink(target, value), .toggle_tab_overview => self.toggleTabOverview(target), + .toggle_split_zoom => self.toggleSplitZoom(target), .toggle_window_decorations => self.toggleWindowDecorations(target), .quit_timer => self.quitTimer(value), // Unimplemented .close_all_windows, - .toggle_split_zoom, .toggle_quick_terminal, .toggle_visibility, .size_limit, @@ -671,6 +671,13 @@ fn toggleTabOverview(_: *App, target: apprt.Target) void { } } +fn toggleSplitZoom(_: *App, target: apprt.Target) void { + switch (target) { + .app => {}, + .surface => |surface| surface.rt_surface.toggleSplitZoom(), + } +} + fn toggleWindowDecorations( _: *App, target: apprt.Target, diff --git a/src/apprt/gtk/Split.zig b/src/apprt/gtk/Split.zig index 5afac6f5b..54fa30e1c 100644 --- a/src/apprt/gtk/Split.zig +++ b/src/apprt/gtk/Split.zig @@ -77,6 +77,7 @@ pub fn init( }); errdefer surface.destroy(alloc); sibling.dimSurface(); + sibling.setSplitZoom(false); // Create the actual GTKPaned, attach the proper children. const orientation: c_uint = switch (direction) { @@ -258,7 +259,7 @@ pub fn grabFocus(self: *Split) void { /// Update the paned children to represent the current state. /// This should be called anytime the top/left or bottom/right /// element is changed. -fn updateChildren(self: *const Split) void { +pub fn updateChildren(self: *const Split) void { // We have to set both to null. If we overwrite the pane with // the same value, then GTK bugs out (the GL area unrealizes // and never rerealizes). @@ -372,7 +373,15 @@ fn directionNext(self: *const Split, from: Side) ?struct { } } -fn removeChildren(self: *const Split) void { - c.gtk_paned_set_start_child(@ptrCast(self.paned), null); - c.gtk_paned_set_end_child(@ptrCast(self.paned), null); +pub fn detachTopLeft(self: *const Split) void { + c.gtk_paned_set_start_child(self.paned, null); +} + +pub fn detachBottomRight(self: *const Split) void { + c.gtk_paned_set_end_child(self.paned, null); +} + +fn removeChildren(self: *const Split) void { + self.detachTopLeft(); + self.detachBottomRight(); } diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 9c7a394f4..8172b7490 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -330,6 +330,9 @@ url_widget: ?URLWidget = null, /// The overlay that shows resizing information. resize_overlay: ResizeOverlay = .{}, +/// Whether or not the current surface is zoomed in (see `toggle_split_zoom`). +zoomed_in: bool = false, + /// If non-null this is the widget on the overlay which dims the surface when it is unfocused unfocused_widget: ?*c.GtkWidget = null, @@ -643,6 +646,8 @@ pub fn redraw(self: *Surface) void { /// Close this surface. pub fn close(self: *Surface, processActive: bool) void { + self.setSplitZoom(false); + // If we're not part of a window hierarchy, we never confirm // so we can just directly remove ourselves and exit. const window = self.container.window() orelse { @@ -791,7 +796,16 @@ pub fn setInitialWindowSize(self: *const Surface, width: u32, height: u32) !void } pub fn grabFocus(self: *Surface) void { - if (self.container.tab()) |tab| tab.focus_child = self; + if (self.container.tab()) |tab| { + // If any other surface was focused and zoomed in, set it to non zoomed in + // so that self can grab focus. + if (tab.focus_child) |focus_child| { + if (focus_child.zoomed_in and focus_child != self) { + focus_child.setSplitZoom(false); + } + } + tab.focus_child = self; + } const widget = @as(*c.GtkWidget, @ptrCast(self.gl_area)); _ = c.gtk_widget_grab_focus(widget); @@ -801,7 +815,7 @@ pub fn grabFocus(self: *Surface) void { fn updateTitleLabels(self: *Surface) void { // If we have no title, then we have nothing to update. - const title = self.title_text orelse return; + const title = self.getTitle() orelse return; // If we have a tab and are the focused child, then we have to update the tab if (self.container.tab()) |tab| { @@ -822,9 +836,19 @@ fn updateTitleLabels(self: *Surface) void { } } +const zoom_title_prefix = "🔍 "; + pub fn setTitle(self: *Surface, slice: [:0]const u8) !void { const alloc = self.app.core_app.alloc; - const copy = try alloc.dupeZ(u8, slice); + + // Always allocate with the "🔍 " at the beginning and slice accordingly + // is the surface is zoomed in or not. + const copy: [:0]const u8 = copy: { + const new_title = try alloc.allocSentinel(u8, zoom_title_prefix.len + slice.len, 0); + @memcpy(new_title[0..zoom_title_prefix.len], zoom_title_prefix); + @memcpy(new_title[zoom_title_prefix.len..], slice); + break :copy new_title; + }; errdefer alloc.free(copy); if (self.title_text) |old| alloc.free(old); @@ -834,7 +858,14 @@ pub fn setTitle(self: *Surface, slice: [:0]const u8) !void { } pub fn getTitle(self: *Surface) ?[:0]const u8 { - return self.title_text; + if (self.title_text) |title_text| { + return if (self.zoomed_in) + title_text + else + title_text[zoom_title_prefix.len..]; + } + + return null; } pub fn setMouseShape( @@ -1875,3 +1906,41 @@ pub fn present(self: *Surface) void { self.grabFocus(); } + +fn detachFromSplit(self: *Surface) void { + const split = self.container.split() orelse return; + switch (self.container.splitSide() orelse unreachable) { + .top_left => split.detachTopLeft(), + .bottom_right => split.detachBottomRight(), + } +} + +fn attachToSplit(self: *Surface) void { + const split = self.container.split() orelse return; + split.updateChildren(); +} + +pub fn setSplitZoom(self: *Surface, new_split_zoom: bool) void { + if (new_split_zoom == self.zoomed_in) return; + const tab = self.container.tab() orelse return; + + const tab_widget = tab.elem.widget(); + const surface_widget = self.primaryWidget(); + + if (new_split_zoom) { + self.detachFromSplit(); + c.gtk_box_remove(tab.box, tab_widget); + c.gtk_box_append(tab.box, surface_widget); + } else { + c.gtk_box_remove(tab.box, surface_widget); + self.attachToSplit(); + c.gtk_box_append(tab.box, tab_widget); + } + + self.zoomed_in = new_split_zoom; + self.grabFocus(); +} + +pub fn toggleSplitZoom(self: *Surface) void { + self.setSplitZoom(!self.zoomed_in); +} diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 5a4cd3f3e..347bc56d2 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -317,8 +317,7 @@ pub const Action = union(enum) { /// Focus on a split in a given direction. goto_split: SplitFocusDirection, - /// zoom/unzoom the current split. This is currently only supported - /// on macOS. Contributions welcome for other platforms. + /// zoom/unzoom the current split. toggle_split_zoom: void, /// Resize the current split by moving the split divider in the given From 04419873462c9e78dc6da5e8fba9646b9fad53a3 Mon Sep 17 00:00:00 2001 From: Meili C Date: Fri, 1 Nov 2024 14:54:24 -0800 Subject: [PATCH 02/27] font feature: add git branch characters addresses #2561 - adds support for most Git branch drawing characters as specified in ![kitty/7681](https://github.com/kovidgoyal/kitty/pull/7681) except for fading vertical and horizontal lines. Adds git_draw_node function and a new Git node type. Add this range (0xf5d0...0xf60d) for Git branch characters, to tests. adds vline_middle_xy and hline_middle_xy for node connections. add git characters to Face.zig. --- src/font/sprite/Box.zig | 398 +++++++++++++++++++++++++++++++++++++++ src/font/sprite/Face.zig | 7 + 2 files changed, 405 insertions(+) diff --git a/src/font/sprite/Box.zig b/src/font/sprite/Box.zig index 477708c99..80369d8fd 100644 --- a/src/font/sprite/Box.zig +++ b/src/font/sprite/Box.zig @@ -79,6 +79,15 @@ const Quads = packed struct(u4) { br: bool = false, }; +/// Specification of a git branch node, which can have any of +/// 4 lines connecting to it in vertical or horizontal alignment +const NodeAlign = packed struct(u4) { + up: bool = false, + down: bool = false, + left: bool = false, + right: bool = false, +}; + /// Alignment of a figure within a cell const Alignment = struct { horizontal: enum { @@ -1302,6 +1311,320 @@ fn draw(self: Box, alloc: Allocator, canvas: *font.sprite.Canvas, cp: u32) !void // '🯯' 0x1fbef => self.draw_circle(canvas, Alignment.top_left, true), + // '' + 0x0f5d0 => self.hline_middle(canvas, Thickness.light), + + // '' + 0x0f5d1 => self.vline_middle(canvas, Thickness.light), + + // '' + // 0x0f5d2 => self.draw_dash_fading(canvas, 4, Direction.LEFT, Thickness.light.height(self.thickness)), + + // '' + // 0x0f5d3 => self.draw_dash_fading(canvas, 4, Direction.RIGHT, Thickness.light.height(self.thickness)), + + // '' + // 0x0f5d4 => self.draw_dash_fading(canvas, 5, Direction.UP, Thickness.light.height(self.thickness)), + + // '' + // 0x0f5d5 => self.draw_dash_fading(canvas, 5, Direction.DOWN, Thickness.light.height(self.thickness)), + + // '' + 0x0f5d6 => try self.draw_light_arc(canvas, .br), + + // '' + 0x0f5d7 => try self.draw_light_arc(canvas, .bl), + + // '' + 0x0f5d8 => try self.draw_light_arc(canvas, .tr), + + // '' + 0x0f5d9 => try self.draw_light_arc(canvas, .tl), + + // '' + 0x0f5da => { + self.vline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .tr); + }, + + // '' + 0x0f5db => { + self.vline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .br); + }, + + // '' + 0x0f5dc => { + try self.draw_light_arc(canvas, .tr); + try self.draw_light_arc(canvas, .br); + }, + + // '' + 0x0f5dd => { + self.vline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .tl); + }, + + // '' + 0x0f5de => { + self.vline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .bl); + }, + + // '' + 0x0f5df => { + try self.draw_light_arc(canvas, .tl); + try self.draw_light_arc(canvas, .bl); + }, + + // '' + 0x0f5e0 => { + try self.draw_light_arc(canvas, .bl); + self.hline_middle(canvas, Thickness.light); + }, + + // '' + 0x0f5e1 => { + try self.draw_light_arc(canvas, .br); + self.hline_middle(canvas, Thickness.light); + }, + + // '' + 0x0f5e2 => { + try self.draw_light_arc(canvas, .br); + try self.draw_light_arc(canvas, .bl); + }, + + // '' + 0x0f5e3 => { + try self.draw_light_arc(canvas, .tl); + self.hline_middle(canvas, Thickness.light); + }, + + // '' + 0x0f5e4 => { + try self.draw_light_arc(canvas, .tr); + self.hline_middle(canvas, Thickness.light); + }, + + // '' + 0x0f5e5 => { + try self.draw_light_arc(canvas, .tr); + try self.draw_light_arc(canvas, .tl); + }, + + // '' + 0x0f5e6 => { + self.vline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .tl); + try self.draw_light_arc(canvas, .tr); + }, + + // '' + 0x0f5e7 => { + self.vline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .bl); + try self.draw_light_arc(canvas, .br); + }, + + // '' + 0x0f5e8 => { + self.hline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .bl); + try self.draw_light_arc(canvas, .tl); + }, + + // '' + 0x0f5e9 => { + self.hline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .tr); + try self.draw_light_arc(canvas, .br); + }, + + // '' + 0x0f5ea => { + self.vline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .tl); + try self.draw_light_arc(canvas, .br); + }, + + // '' + 0x0f5eb => { + self.vline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .tr); + try self.draw_light_arc(canvas, .bl); + }, + + // '' + 0x0f5ec => { + self.hline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .tl); + try self.draw_light_arc(canvas, .br); + }, + + // '' + 0x0f5ed => { + self.hline_middle(canvas, Thickness.light); + try self.draw_light_arc(canvas, .tr); + try self.draw_light_arc(canvas, .bl); + }, + + // '' + 0x0f5ee => self.draw_git_node(canvas, .{}, Thickness.light, true), + + // '' + 0x0f5ef => self.draw_git_node(canvas, .{}, Thickness.light, false), + + // '' + 0x0f5f0 => { + self.draw_git_node(canvas, .{ .right = true }, Thickness.light, true); + }, + + // '' + 0x0f5f1 => { + self.draw_git_node(canvas, .{ .right = true }, Thickness.light, false); + }, + + // '' + 0x0f5f2 => { + self.draw_git_node(canvas, .{ .left = true }, Thickness.light, true); + }, + + // '' + 0x0f5f3 => { + self.draw_git_node(canvas, .{ .left = true }, Thickness.light, false); + }, + + // '' + 0x0f5f4 => { + self.draw_git_node(canvas, .{ .left = true, .right = true }, Thickness.light, true); + }, + + // '' + 0x0f5f5 => { + self.draw_git_node(canvas, .{ .left = true, .right = true }, Thickness.light, false); + }, + + // '' + 0x0f5f6 => { + self.draw_git_node(canvas, .{ .down = true }, Thickness.light, true); + }, + + // '' + 0x0f5f7 => { + self.draw_git_node(canvas, .{ .down = true }, Thickness.light, false); + }, + + // '' + 0x0f5f8 => { + self.draw_git_node(canvas, .{ .up = true }, Thickness.light, true); + }, + + // '' + 0x0f5f9 => { + self.draw_git_node(canvas, .{ .up = true }, Thickness.light, false); + }, + + // '' + 0x0f5fa => { + self.draw_git_node(canvas, .{ .up = true, .down = true }, Thickness.light, true); + }, + + // '' + 0x0f5fb => { + self.draw_git_node(canvas, .{ .up = true, .down = true }, Thickness.light, false); + }, + + // '' + 0x0f5fc => { + self.draw_git_node(canvas, .{ .right = true, .down = true }, Thickness.light, true); + }, + + // '' + 0x0f5fd => { + self.draw_git_node(canvas, .{ .right = true, .down = true }, Thickness.light, false); + }, + + // '' + 0x0f5fe => { + self.draw_git_node(canvas, .{ .left = true, .down = true }, Thickness.light, true); + }, + + // '' + 0x0f5ff => { + self.draw_git_node(canvas, .{ .left = true, .down = true }, Thickness.light, false); + }, + + // '' + 0x0f600 => { + self.draw_git_node(canvas, .{ .up = true, .right = true }, Thickness.light, true); + }, + + // '' + 0x0f601 => { + self.draw_git_node(canvas, .{ .up = true, .right = true }, Thickness.light, false); + }, + + // '' + 0x0f602 => { + self.draw_git_node(canvas, .{ .up = true, .left = true }, Thickness.light, true); + }, + + // '' + 0x0f603 => { + self.draw_git_node(canvas, .{ .up = true, .left = true }, Thickness.light, false); + }, + + // '' + 0x0f604 => { + self.draw_git_node(canvas, .{ .up = true, .down = true, .right = true }, Thickness.light, true); + }, + + // '' + 0x0f605 => { + self.draw_git_node(canvas, .{ .up = true, .down = true, .right = true }, Thickness.light, false); + }, + + // '' + 0x0f606 => { + self.draw_git_node(canvas, .{ .up = true, .down = true, .left = true }, Thickness.light, true); + }, + + // '' + 0x0f607 => { + self.draw_git_node(canvas, .{ .up = true, .down = true, .left = true }, Thickness.light, false); + }, + + // '' + 0x0f608 => { + self.draw_git_node(canvas, .{ .down = true, .left = true, .right = true }, Thickness.light, true); + }, + + // '' + 0x0f609 => { + self.draw_git_node(canvas, .{ .down = true, .left = true, .right = true }, Thickness.light, false); + }, + + // '' + 0x0f60a => { + self.draw_git_node(canvas, .{ .up = true, .left = true, .right = true }, Thickness.light, true); + }, + + // '' + 0x0f60b => { + self.draw_git_node(canvas, .{ .up = true, .left = true, .right = true }, Thickness.light, false); + }, + + // '' + 0x0f60c => { + self.draw_git_node(canvas, .{ .up = true, .down = true, .left = true, .right = true }, Thickness.light, true); + }, + + // '' + 0x0f60d => { + self.draw_git_node(canvas, .{ .up = true, .down = true, .left = true, .right = true }, Thickness.light, false); + }, + // Not official box characters but special characters we hide // in the high bits of a unicode codepoint. @intFromEnum(Sprite.cursor_rect) => self.draw_cursor_rect(canvas), @@ -1793,6 +2116,57 @@ fn draw_cell_diagonal( ) catch {}; } +fn draw_git_node( + self: Box, + canvas: *font.sprite.Canvas, + comptime nodes: NodeAlign, + comptime thickness: Thickness, + comptime filled: bool, +) void { + const float_width: f64 = @floatFromInt(self.width); + const float_height: f64 = @floatFromInt(self.height); + + const x: f64 = float_width / 2; + const y: f64 = float_height / 2; + + // we need at least 1px leeway to see node + right/left line + const r: f64 = 0.47 * @min(float_width, float_height); + + var ctx: z2d.Context = .{ + .surface = canvas.sfc, + .pattern = .{ + .opaque_pattern = .{ + .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, + }, + }, + .line_width = @floatFromInt(thickness.height(self.thickness)), + }; + + var path = z2d.Path.init(canvas.alloc); + defer path.deinit(); + + const int_radius: u32 = @intFromFloat(r); + + if (nodes.up) + self.vline_middle_xy(canvas, 0, (self.height / 2) - int_radius, self.width, thickness); + if (nodes.down) + self.vline_middle_xy(canvas, (self.height / 2) + int_radius, self.height, self.width, thickness); + if (nodes.left) + self.hline_middle_xy(canvas, 0, (self.width / 2) - int_radius, self.height, thickness); + if (nodes.right) + self.hline_middle_xy(canvas, (self.width / 2) + int_radius, self.width, self.height, thickness); + + if (filled) { + path.arc(x, y, r, 0, std.math.pi * 2, false, null) catch return; + path.close() catch return; + ctx.fill(canvas.alloc, path) catch return; + } else { + path.arc(x, y, r - ctx.line_width / 2, 0, std.math.pi * 2, false, null) catch return; + path.close() catch return; + ctx.stroke(canvas.alloc, path) catch return; + } +} + fn draw_circle( self: Box, canvas: *font.sprite.Canvas, @@ -2377,6 +2751,16 @@ fn draw_cursor_bar(self: Box, canvas: *font.sprite.Canvas) void { self.vline(canvas, 0, self.height, 0, thick_px); } +fn vline_middle_xy(self: Box, canvas: *font.sprite.Canvas, y: u32, y2: u32, x: u32, thickness: Thickness) void { + const thick_px = thickness.height(self.thickness); + self.vline(canvas, y, y2, (x -| thick_px) / 2, thick_px); +} + +fn hline_middle_xy(self: Box, canvas: *font.sprite.Canvas, x: u32, x2: u32, y: u32, thickness: Thickness) void { + const thick_px = thickness.height(self.thickness); + self.hline(canvas, x, x2, (y -| thick_px) / 2, thick_px); +} + fn vline_middle(self: Box, canvas: *font.sprite.Canvas, thickness: Thickness) void { const thick_px = thickness.height(self.thickness); self.vline(canvas, 0, self.height, (self.width -| thick_px) / 2, thick_px); @@ -2480,6 +2864,20 @@ fn testRenderAll(self: Box, alloc: Allocator, atlas: *font.Atlas) !void { ); } + // Git Branch characters + cp = 0xf5d0; + while (cp <= 0xf60d) : (cp += 1) { + switch (cp) { + 0xf5d0...0xf60d, + => _ = try self.renderGlyph( + alloc, + atlas, + cp, + ), + else => {}, + } + } + // Symbols for Legacy Computing. cp = 0x1fb00; while (cp <= 0x1fbef) : (cp += 1) { diff --git a/src/font/sprite/Face.zig b/src/font/sprite/Face.zig index af82bb731..650bd2a5f 100644 --- a/src/font/sprite/Face.zig +++ b/src/font/sprite/Face.zig @@ -276,6 +276,13 @@ const Kind = enum { 0xE0D4, => .powerline, + // (Git Branch) + //           + //                     + //                     + //             + 0xF5D0...0xF60D => .box, + else => null, }; } From 4dbf404dc330d6af610519abeecfc7eb2d8b4617 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Wed, 6 Nov 2024 18:27:22 -0500 Subject: [PATCH 03/27] font/sprite: cleanup branch drawing character impl, implement fade-out lines --- src/font/sprite/Box.zig | 619 ++++++++++++++++++------------- src/font/sprite/Face.zig | 22 +- src/font/sprite/testdata/Box.ppm | Bin 1048593 -> 1048593 bytes 3 files changed, 371 insertions(+), 270 deletions(-) diff --git a/src/font/sprite/Box.zig b/src/font/sprite/Box.zig index 80369d8fd..382aa4206 100644 --- a/src/font/sprite/Box.zig +++ b/src/font/sprite/Box.zig @@ -79,13 +79,15 @@ const Quads = packed struct(u4) { br: bool = false, }; -/// Specification of a git branch node, which can have any of -/// 4 lines connecting to it in vertical or horizontal alignment -const NodeAlign = packed struct(u4) { +/// Specification of a branch drawing node, which consists of a +/// circle which is either empty or filled, and lines connecting +/// optionally between the circle and each of the 4 edges. +const BranchNode = packed struct(u5) { up: bool = false, + right: bool = false, down: bool = false, left: bool = false, - right: bool = false, + filled: bool = false, }; /// Alignment of a figure within a cell @@ -483,14 +485,14 @@ fn draw(self: Box, alloc: Allocator, canvas: *font.sprite.Canvas, cp: u32) !void // '╬' 0x256c => self.draw_lines(canvas, .{ .up = .double, .down = .double, .left = .double, .right = .double }), // '╭' - 0x256d => try self.draw_light_arc(canvas, .br), + 0x256d => try self.draw_arc(canvas, .br, .light), // '╮' - 0x256e => try self.draw_light_arc(canvas, .bl), + 0x256e => try self.draw_arc(canvas, .bl, .light), // '╯' - 0x256f => try self.draw_light_arc(canvas, .tl), + 0x256f => try self.draw_arc(canvas, .tl, .light), // '╰' - 0x2570 => try self.draw_light_arc(canvas, .tr), + 0x2570 => try self.draw_arc(canvas, .tr, .light), // '╱' 0x2571 => self.draw_light_diagonal_upper_right_to_lower_left(canvas), // '╲' @@ -1311,319 +1313,329 @@ fn draw(self: Box, alloc: Allocator, canvas: *font.sprite.Canvas, cp: u32) !void // '🯯' 0x1fbef => self.draw_circle(canvas, Alignment.top_left, true), + // (Below:) + // Branch drawing character set, used for drawing git-like + // graphs in the terminal. Originally implemented in Kitty. + // Ref: + // - https://github.com/kovidgoyal/kitty/pull/7681 + // - https://github.com/kovidgoyal/kitty/pull/7805 + // NOTE: Kitty is GPL licensed, and its code was not referenced + // for these characters, only the loose specification of + // the character set in the pull request descriptions. + // + // TODO(qwerasd): This should be in another file, but really the + // general organization of the sprite font code + // needs to be reworked eventually. + // + //           + //                     + //                     + //             + // '' - 0x0f5d0 => self.hline_middle(canvas, Thickness.light), - + 0x0f5d0 => self.hline_middle(canvas, .light), // '' - 0x0f5d1 => self.vline_middle(canvas, Thickness.light), - + 0x0f5d1 => self.vline_middle(canvas, .light), // '' - // 0x0f5d2 => self.draw_dash_fading(canvas, 4, Direction.LEFT, Thickness.light.height(self.thickness)), - + 0x0f5d2 => self.draw_fading_line(canvas, .right, .light), // '' - // 0x0f5d3 => self.draw_dash_fading(canvas, 4, Direction.RIGHT, Thickness.light.height(self.thickness)), - + 0x0f5d3 => self.draw_fading_line(canvas, .left, .light), // '' - // 0x0f5d4 => self.draw_dash_fading(canvas, 5, Direction.UP, Thickness.light.height(self.thickness)), - + 0x0f5d4 => self.draw_fading_line(canvas, .bottom, .light), // '' - // 0x0f5d5 => self.draw_dash_fading(canvas, 5, Direction.DOWN, Thickness.light.height(self.thickness)), - + 0x0f5d5 => self.draw_fading_line(canvas, .top, .light), // '' - 0x0f5d6 => try self.draw_light_arc(canvas, .br), - + 0x0f5d6 => try self.draw_arc(canvas, .br, .light), // '' - 0x0f5d7 => try self.draw_light_arc(canvas, .bl), - + 0x0f5d7 => try self.draw_arc(canvas, .bl, .light), // '' - 0x0f5d8 => try self.draw_light_arc(canvas, .tr), - + 0x0f5d8 => try self.draw_arc(canvas, .tr, .light), // '' - 0x0f5d9 => try self.draw_light_arc(canvas, .tl), - + 0x0f5d9 => try self.draw_arc(canvas, .tl, .light), // '' 0x0f5da => { - self.vline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .tr); + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .tr, .light); }, - // '' 0x0f5db => { - self.vline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .br); + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .br, .light); }, - // '' 0x0f5dc => { - try self.draw_light_arc(canvas, .tr); - try self.draw_light_arc(canvas, .br); + try self.draw_arc(canvas, .tr, .light); + try self.draw_arc(canvas, .br, .light); }, - // '' 0x0f5dd => { - self.vline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .tl); + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .tl, .light); }, - // '' 0x0f5de => { - self.vline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .bl); + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .bl, .light); }, - // '' 0x0f5df => { - try self.draw_light_arc(canvas, .tl); - try self.draw_light_arc(canvas, .bl); + try self.draw_arc(canvas, .tl, .light); + try self.draw_arc(canvas, .bl, .light); }, // '' 0x0f5e0 => { - try self.draw_light_arc(canvas, .bl); - self.hline_middle(canvas, Thickness.light); + try self.draw_arc(canvas, .bl, .light); + self.hline_middle(canvas, .light); }, - // '' 0x0f5e1 => { - try self.draw_light_arc(canvas, .br); - self.hline_middle(canvas, Thickness.light); + try self.draw_arc(canvas, .br, .light); + self.hline_middle(canvas, .light); }, - // '' 0x0f5e2 => { - try self.draw_light_arc(canvas, .br); - try self.draw_light_arc(canvas, .bl); + try self.draw_arc(canvas, .br, .light); + try self.draw_arc(canvas, .bl, .light); }, - // '' 0x0f5e3 => { - try self.draw_light_arc(canvas, .tl); - self.hline_middle(canvas, Thickness.light); + try self.draw_arc(canvas, .tl, .light); + self.hline_middle(canvas, .light); }, - // '' 0x0f5e4 => { - try self.draw_light_arc(canvas, .tr); - self.hline_middle(canvas, Thickness.light); + try self.draw_arc(canvas, .tr, .light); + self.hline_middle(canvas, .light); }, - // '' 0x0f5e5 => { - try self.draw_light_arc(canvas, .tr); - try self.draw_light_arc(canvas, .tl); + try self.draw_arc(canvas, .tr, .light); + try self.draw_arc(canvas, .tl, .light); }, - // '' 0x0f5e6 => { - self.vline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .tl); - try self.draw_light_arc(canvas, .tr); + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .tl, .light); + try self.draw_arc(canvas, .tr, .light); }, - // '' 0x0f5e7 => { - self.vline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .bl); - try self.draw_light_arc(canvas, .br); + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .bl, .light); + try self.draw_arc(canvas, .br, .light); }, - // '' 0x0f5e8 => { - self.hline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .bl); - try self.draw_light_arc(canvas, .tl); + self.hline_middle(canvas, .light); + try self.draw_arc(canvas, .bl, .light); + try self.draw_arc(canvas, .tl, .light); }, - // '' 0x0f5e9 => { - self.hline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .tr); - try self.draw_light_arc(canvas, .br); + self.hline_middle(canvas, .light); + try self.draw_arc(canvas, .tr, .light); + try self.draw_arc(canvas, .br, .light); }, - // '' 0x0f5ea => { - self.vline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .tl); - try self.draw_light_arc(canvas, .br); + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .tl, .light); + try self.draw_arc(canvas, .br, .light); }, - // '' 0x0f5eb => { - self.vline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .tr); - try self.draw_light_arc(canvas, .bl); + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .tr, .light); + try self.draw_arc(canvas, .bl, .light); }, - // '' 0x0f5ec => { - self.hline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .tl); - try self.draw_light_arc(canvas, .br); + self.hline_middle(canvas, .light); + try self.draw_arc(canvas, .tl, .light); + try self.draw_arc(canvas, .br, .light); }, - // '' 0x0f5ed => { - self.hline_middle(canvas, Thickness.light); - try self.draw_light_arc(canvas, .tr); - try self.draw_light_arc(canvas, .bl); + self.hline_middle(canvas, .light); + try self.draw_arc(canvas, .tr, .light); + try self.draw_arc(canvas, .bl, .light); }, - // '' - 0x0f5ee => self.draw_git_node(canvas, .{}, Thickness.light, true), - + 0x0f5ee => self.draw_branch_node(canvas, .{ .filled = true }, .light), // '' - 0x0f5ef => self.draw_git_node(canvas, .{}, Thickness.light, false), + 0x0f5ef => self.draw_branch_node(canvas, .{}, .light), // '' - 0x0f5f0 => { - self.draw_git_node(canvas, .{ .right = true }, Thickness.light, true); - }, - + 0x0f5f0 => self.draw_branch_node(canvas, .{ + .right = true, + .filled = true, + }, .light), // '' - 0x0f5f1 => { - self.draw_git_node(canvas, .{ .right = true }, Thickness.light, false); - }, - + 0x0f5f1 => self.draw_branch_node(canvas, .{ + .right = true, + }, .light), // '' - 0x0f5f2 => { - self.draw_git_node(canvas, .{ .left = true }, Thickness.light, true); - }, - + 0x0f5f2 => self.draw_branch_node(canvas, .{ + .left = true, + .filled = true, + }, .light), // '' - 0x0f5f3 => { - self.draw_git_node(canvas, .{ .left = true }, Thickness.light, false); - }, - + 0x0f5f3 => self.draw_branch_node(canvas, .{ + .left = true, + }, .light), // '' - 0x0f5f4 => { - self.draw_git_node(canvas, .{ .left = true, .right = true }, Thickness.light, true); - }, - + 0x0f5f4 => self.draw_branch_node(canvas, .{ + .left = true, + .right = true, + .filled = true, + }, .light), // '' - 0x0f5f5 => { - self.draw_git_node(canvas, .{ .left = true, .right = true }, Thickness.light, false); - }, - + 0x0f5f5 => self.draw_branch_node(canvas, .{ + .left = true, + .right = true, + }, .light), // '' - 0x0f5f6 => { - self.draw_git_node(canvas, .{ .down = true }, Thickness.light, true); - }, - + 0x0f5f6 => self.draw_branch_node(canvas, .{ + .down = true, + .filled = true, + }, .light), // '' - 0x0f5f7 => { - self.draw_git_node(canvas, .{ .down = true }, Thickness.light, false); - }, - + 0x0f5f7 => self.draw_branch_node(canvas, .{ + .down = true, + }, .light), // '' - 0x0f5f8 => { - self.draw_git_node(canvas, .{ .up = true }, Thickness.light, true); - }, - + 0x0f5f8 => self.draw_branch_node(canvas, .{ + .up = true, + .filled = true, + }, .light), // '' - 0x0f5f9 => { - self.draw_git_node(canvas, .{ .up = true }, Thickness.light, false); - }, - + 0x0f5f9 => self.draw_branch_node(canvas, .{ + .up = true, + }, .light), // '' - 0x0f5fa => { - self.draw_git_node(canvas, .{ .up = true, .down = true }, Thickness.light, true); - }, - + 0x0f5fa => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + .filled = true, + }, .light), // '' - 0x0f5fb => { - self.draw_git_node(canvas, .{ .up = true, .down = true }, Thickness.light, false); - }, - + 0x0f5fb => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + }, .light), // '' - 0x0f5fc => { - self.draw_git_node(canvas, .{ .right = true, .down = true }, Thickness.light, true); - }, - + 0x0f5fc => self.draw_branch_node(canvas, .{ + .right = true, + .down = true, + .filled = true, + }, .light), // '' - 0x0f5fd => { - self.draw_git_node(canvas, .{ .right = true, .down = true }, Thickness.light, false); - }, - + 0x0f5fd => self.draw_branch_node(canvas, .{ + .right = true, + .down = true, + }, .light), // '' - 0x0f5fe => { - self.draw_git_node(canvas, .{ .left = true, .down = true }, Thickness.light, true); - }, - + 0x0f5fe => self.draw_branch_node(canvas, .{ + .left = true, + .down = true, + .filled = true, + }, .light), // '' - 0x0f5ff => { - self.draw_git_node(canvas, .{ .left = true, .down = true }, Thickness.light, false); - }, + 0x0f5ff => self.draw_branch_node(canvas, .{ + .left = true, + .down = true, + }, .light), // '' - 0x0f600 => { - self.draw_git_node(canvas, .{ .up = true, .right = true }, Thickness.light, true); - }, - + 0x0f600 => self.draw_branch_node(canvas, .{ + .up = true, + .right = true, + .filled = true, + }, .light), // '' - 0x0f601 => { - self.draw_git_node(canvas, .{ .up = true, .right = true }, Thickness.light, false); - }, - + 0x0f601 => self.draw_branch_node(canvas, .{ + .up = true, + .right = true, + }, .light), // '' - 0x0f602 => { - self.draw_git_node(canvas, .{ .up = true, .left = true }, Thickness.light, true); - }, - + 0x0f602 => self.draw_branch_node(canvas, .{ + .up = true, + .left = true, + .filled = true, + }, .light), // '' - 0x0f603 => { - self.draw_git_node(canvas, .{ .up = true, .left = true }, Thickness.light, false); - }, - + 0x0f603 => self.draw_branch_node(canvas, .{ + .up = true, + .left = true, + }, .light), // '' - 0x0f604 => { - self.draw_git_node(canvas, .{ .up = true, .down = true, .right = true }, Thickness.light, true); - }, - + 0x0f604 => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + .right = true, + .filled = true, + }, .light), // '' - 0x0f605 => { - self.draw_git_node(canvas, .{ .up = true, .down = true, .right = true }, Thickness.light, false); - }, - + 0x0f605 => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + .right = true, + }, .light), // '' - 0x0f606 => { - self.draw_git_node(canvas, .{ .up = true, .down = true, .left = true }, Thickness.light, true); - }, - + 0x0f606 => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + .left = true, + .filled = true, + }, .light), // '' - 0x0f607 => { - self.draw_git_node(canvas, .{ .up = true, .down = true, .left = true }, Thickness.light, false); - }, - + 0x0f607 => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + .left = true, + }, .light), // '' - 0x0f608 => { - self.draw_git_node(canvas, .{ .down = true, .left = true, .right = true }, Thickness.light, true); - }, - + 0x0f608 => self.draw_branch_node(canvas, .{ + .down = true, + .left = true, + .right = true, + .filled = true, + }, .light), // '' - 0x0f609 => { - self.draw_git_node(canvas, .{ .down = true, .left = true, .right = true }, Thickness.light, false); - }, - + 0x0f609 => self.draw_branch_node(canvas, .{ + .down = true, + .left = true, + .right = true, + }, .light), // '' - 0x0f60a => { - self.draw_git_node(canvas, .{ .up = true, .left = true, .right = true }, Thickness.light, true); - }, - + 0x0f60a => self.draw_branch_node(canvas, .{ + .up = true, + .left = true, + .right = true, + .filled = true, + }, .light), // '' - 0x0f60b => { - self.draw_git_node(canvas, .{ .up = true, .left = true, .right = true }, Thickness.light, false); - }, - + 0x0f60b => self.draw_branch_node(canvas, .{ + .up = true, + .left = true, + .right = true, + }, .light), // '' - 0x0f60c => { - self.draw_git_node(canvas, .{ .up = true, .down = true, .left = true, .right = true }, Thickness.light, true); - }, - + 0x0f60c => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + .left = true, + .right = true, + .filled = true, + }, .light), // '' - 0x0f60d => { - self.draw_git_node(canvas, .{ .up = true, .down = true, .left = true, .right = true }, Thickness.light, false); - }, + 0x0f60d => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + .left = true, + .right = true, + }, .light), // Not official box characters but special characters we hide // in the high bits of a unicode codepoint. @@ -2116,21 +2128,98 @@ fn draw_cell_diagonal( ) catch {}; } -fn draw_git_node( +fn draw_fading_line( self: Box, canvas: *font.sprite.Canvas, - comptime nodes: NodeAlign, + comptime to: Edge, comptime thickness: Thickness, - comptime filled: bool, ) void { + const thick_px = thickness.height(self.thickness); const float_width: f64 = @floatFromInt(self.width); const float_height: f64 = @floatFromInt(self.height); - const x: f64 = float_width / 2; - const y: f64 = float_height / 2; + // Top of horizontal strokes + const h_top = (self.height -| thick_px) / 2; + // Bottom of horizontal strokes + const h_bottom = h_top +| thick_px; + // Left of vertical strokes + const v_left = (self.width -| thick_px) / 2; + // Right of vertical strokes + const v_right = v_left +| thick_px; - // we need at least 1px leeway to see node + right/left line - const r: f64 = 0.47 * @min(float_width, float_height); + // If we're fading to the top or left, we start with 0.0 + // and increment up as we progress, otherwise we start + // at 255.0 and increment down (negative). + var color: f64 = switch (to) { + .top, .left => 0.0, + .bottom, .right => 255.0, + }; + const inc: f64 = 255.0 / switch (to) { + .top => float_height, + .bottom => -float_height, + .left => float_width, + .right => -float_width, + }; + + switch (to) { + .top, .bottom => { + for (0..self.height) |y| { + for (v_left..v_right) |x| { + canvas.pixel( + @intCast(x), + @intCast(y), + @enumFromInt(@as(u8, @intFromFloat(@round(color)))), + ); + } + color += inc; + } + }, + .left, .right => { + for (0..self.width) |x| { + for (h_top..h_bottom) |y| { + canvas.pixel( + @intCast(x), + @intCast(y), + @enumFromInt(@as(u8, @intFromFloat(@round(color)))), + ); + } + color += inc; + } + }, + } +} + +fn draw_branch_node( + self: Box, + canvas: *font.sprite.Canvas, + node: BranchNode, + comptime thickness: Thickness, +) void { + const thick_px = thickness.height(self.thickness); + const float_width: f64 = @floatFromInt(self.width); + const float_height: f64 = @floatFromInt(self.height); + const float_thick: f64 = @floatFromInt(thick_px); + + // Top of horizontal strokes + const h_top = (self.height -| thick_px) / 2; + // Bottom of horizontal strokes + const h_bottom = h_top +| thick_px; + // Left of vertical strokes + const v_left = (self.width -| thick_px) / 2; + // Right of vertical strokes + const v_right = v_left +| thick_px; + + // We calculate the center of the circle this way + // to ensure it aligns with box drawing characters + // since the lines are sometimes off center to + // make sure they aren't split between pixels. + const cx: f64 = @as(f64, @floatFromInt(v_left)) + float_thick / 2; + const cy: f64 = @as(f64, @floatFromInt(h_top)) + float_thick / 2; + // The radius needs to be the smallest distance from the center to an edge. + const r: f64 = @min( + @min(cx, cy), + @min(float_width - cx, float_height - cy), + ); var ctx: z2d.Context = .{ .surface = canvas.sfc, @@ -2139,29 +2228,30 @@ fn draw_git_node( .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, }, }, - .line_width = @floatFromInt(thickness.height(self.thickness)), + .line_width = float_thick, }; var path = z2d.Path.init(canvas.alloc); defer path.deinit(); - const int_radius: u32 = @intFromFloat(r); + // These @intFromFloat casts shouldn't ever fail since r can never + // be greater than cx or cy, so when subtracting it from them the + // result can never be negative. + if (node.up) + self.rect(canvas, v_left, 0, v_right, @intFromFloat(@ceil(cy - r))); + if (node.right) + self.rect(canvas, @intFromFloat(@floor(cx + r)), h_top, self.width, h_bottom); + if (node.down) + self.rect(canvas, v_left, @intFromFloat(@floor(cy + r)), v_right, self.height); + if (node.left) + self.rect(canvas, 0, h_top, @intFromFloat(@ceil(cx - r)), h_bottom); - if (nodes.up) - self.vline_middle_xy(canvas, 0, (self.height / 2) - int_radius, self.width, thickness); - if (nodes.down) - self.vline_middle_xy(canvas, (self.height / 2) + int_radius, self.height, self.width, thickness); - if (nodes.left) - self.hline_middle_xy(canvas, 0, (self.width / 2) - int_radius, self.height, thickness); - if (nodes.right) - self.hline_middle_xy(canvas, (self.width / 2) + int_radius, self.width, self.height, thickness); - - if (filled) { - path.arc(x, y, r, 0, std.math.pi * 2, false, null) catch return; + if (node.filled) { + path.arc(cx, cy, r, 0, std.math.pi * 2, false, null) catch return; path.close() catch return; ctx.fill(canvas.alloc, path) catch return; } else { - path.arc(x, y, r - ctx.line_width / 2, 0, std.math.pi * 2, false, null) catch return; + path.arc(cx, cy, r - float_thick / 2, 0, std.math.pi * 2, false, null) catch return; path.close() catch return; ctx.stroke(canvas.alloc, path) catch return; } @@ -2492,12 +2582,13 @@ fn draw_edge_triangle( try ctx.fill(canvas.alloc, path); } -fn draw_light_arc( +fn draw_arc( self: Box, canvas: *font.sprite.Canvas, comptime corner: Corner, + comptime thickness: Thickness, ) !void { - const thick_px = Thickness.light.height(self.thickness); + const thick_px = thickness.height(self.thickness); const float_width: f64 = @floatFromInt(self.width); const float_height: f64 = @floatFromInt(self.height); const float_thick: f64 = @floatFromInt(thick_px); @@ -2751,16 +2842,6 @@ fn draw_cursor_bar(self: Box, canvas: *font.sprite.Canvas) void { self.vline(canvas, 0, self.height, 0, thick_px); } -fn vline_middle_xy(self: Box, canvas: *font.sprite.Canvas, y: u32, y2: u32, x: u32, thickness: Thickness) void { - const thick_px = thickness.height(self.thickness); - self.vline(canvas, y, y2, (x -| thick_px) / 2, thick_px); -} - -fn hline_middle_xy(self: Box, canvas: *font.sprite.Canvas, x: u32, x2: u32, y: u32, thickness: Thickness) void { - const thick_px = thickness.height(self.thickness); - self.hline(canvas, x, x2, (y -| thick_px) / 2, thick_px); -} - fn vline_middle(self: Box, canvas: *font.sprite.Canvas, thickness: Thickness) void { const thick_px = thickness.height(self.thickness); self.vline(canvas, 0, self.height, (self.width -| thick_px) / 2, thick_px); @@ -2864,20 +2945,6 @@ fn testRenderAll(self: Box, alloc: Allocator, atlas: *font.Atlas) !void { ); } - // Git Branch characters - cp = 0xf5d0; - while (cp <= 0xf60d) : (cp += 1) { - switch (cp) { - 0xf5d0...0xf60d, - => _ = try self.renderGlyph( - alloc, - atlas, - cp, - ), - else => {}, - } - } - // Symbols for Legacy Computing. cp = 0x1fb00; while (cp <= 0x1fbef) : (cp += 1) { @@ -2932,6 +2999,32 @@ fn testRenderAll(self: Box, alloc: Allocator, atlas: *font.Atlas) !void { else => {}, } } + + // Branch drawing character set, used for drawing git-like + // graphs in the terminal. Originally implemented in Kitty. + // Ref: + // - https://github.com/kovidgoyal/kitty/pull/7681 + // - https://github.com/kovidgoyal/kitty/pull/7805 + // NOTE: Kitty is GPL licensed, and its code was not referenced + // for these characters, only the loose specification of + // the character set in the pull request descriptions. + // + // TODO(qwerasd): This should be in another file, but really the + // general organization of the sprite font code + // needs to be reworked eventually. + // + //           + //                     + //                     + //             + cp = 0xf5d0; + while (cp <= 0xf60d) : (cp += 1) { + _ = try self.renderGlyph( + alloc, + atlas, + cp, + ); + } } test "render all sprites" { diff --git a/src/font/sprite/Face.zig b/src/font/sprite/Face.zig index 650bd2a5f..ca0ed96e8 100644 --- a/src/font/sprite/Face.zig +++ b/src/font/sprite/Face.zig @@ -263,6 +263,21 @@ const Kind = enum { 0x1FBCE...0x1FBEF, => .box, + // Branch drawing character set, used for drawing git-like + // graphs in the terminal. Originally implemented in Kitty. + // Ref: + // - https://github.com/kovidgoyal/kitty/pull/7681 + // - https://github.com/kovidgoyal/kitty/pull/7805 + // NOTE: Kitty is GPL licensed, and its code was not referenced + // for these characters, only the loose specification of + // the character set in the pull request descriptions. + // + //           + //                     + //                     + //             + 0xF5D0...0xF60D => .box, + // Powerline fonts 0xE0B0, 0xE0B4, @@ -276,13 +291,6 @@ const Kind = enum { 0xE0D4, => .powerline, - // (Git Branch) - //           - //                     - //                     - //             - 0xF5D0...0xF60D => .box, - else => null, }; } diff --git a/src/font/sprite/testdata/Box.ppm b/src/font/sprite/testdata/Box.ppm index 29ae539e709deba5b723a5e3fba111c1914a0cd7..1301a4299816fec222672eb24f327db221007b21 100644 GIT binary patch delta 21898 zcmeHOdwdktz2B2$_ID=x%44&6kg&4}U|zW4O?XHYN&tmGYb;X4c;PA^t8BQ$mfret zV*x>pguRRTQAHC!QlV6v>=hj-An~gB3T`N1%LPif_-LUva6i=xwcfp-b7p5}HoIW3 zEalV7{4qOo=A7^Edw%Eq9_P%Cud&M4Sk<_atV~*&yfS5_u+q2mxGW?cYd#5!i6Xi!dxg&EjvX zb^4@%*u4lu+*JXAh&ml-&&JR+SglutXB}8^`W6@-%d<)_+Hlrag2N|`#O}IiE&p7n zCx?;fB77l@yAdjiM7 zgRvaT$`a3UJlehmMmc{9j3HPt1@ifz?%Nm3X87>Lvzdrh>tTFfjTP}yX7g3+Aq6Ks zsZ7P*TcCoo-McrIZDnQR+0KjbamY2dz(5k2a=5eGXvPUcjWnsMDv?B($4zH6?3gpH z?`Z@Z+k9rSDLCM#V8=7fWDvV8U~!-<`f0vTs^i++zCBi()zyjagbf{2z#iu`k{J<| z$K47eH2pdPfmo8WW-*eRH;`fy!eNUcQwc|#H;^pY-lGI=fH_Rl%(AZ~nZ5gCD9s|0gWuN!SukcY2SfgQ8h>6I!Ni4)quBu|(N!*m?C zZtW|;V5qI-9M_ZLKCHvm@yU81Q4)>7zMPC1v?5B zu*JEx`=oTaq7EkN2yfgNOL*ZzPPmB_r-VYiPCPq+*s=S6s1bY43#qudNmXq=bo1O0HO}Qe3+>mg3T- zoZ?Dy9Swzoq2AUSH=Z~zxNyr#lIfpwx)9%8P6qg-QS7(4&|TBo8w%QNN=*e$DN|ef zPn^o*b&X0nQz!m>CAkj5jj+8Oaxk-;F=Z$9YE(-b3{l*i=DyA}lVa??4^}X5Ci*L; zIj4rso;O+Oc}IFFlhP)2xxU(C~UNITQ+FdO34xQC28dEZ22u=~AsL>alh$ z@6sAFQ%!?1Qn`cT74VrgWB~3yFW3d{xNvs^DdJuW!p;rJGGU-kx}MuQjd-`t1~Mj7 z!wDaXr*kl;>BNnv1ilh9#dmZMYQ%Hyy;nuh4(6f}o5+F)ZzYak6Dh>d0Ah>`4^3eq z0(aMwK|Cx7JYs~2yBYBeV>pQHsV8IY@j{G$&S^V~NfI8}!rUB>8T#lkIvlARAy=J3 zISUtRbk&1}Ld13C4u4NaF79}gWfAL0X4HHVBQY3QxVEzskJWkDF_X+tqV@;(krE|B zYXN)nI^x2kn^-Dw@d%v}PJLEFHt|V(^^h}vzRFQMSf+uvUSXS5!>@~?x$-?j)(jG4%nOZcL+T1d56@!Rm4JXd(&JA=^%rr*DBF$|52^Ulpt!Cr>7r z!XmKAGhT$j3WcmJO|*KPI+b7wiy4Iu$R;fdNSd-wW@Tx8pX2P=#PaAoFWBT6+oE^? z^dVs|7|xwb91pqf0op`;QcfQaq!?{KTz1$^MY%M-w5srf3A;{3 zWx`ZHlaKg6;y7osNNSVszY+EjT-QX4eNxTkR3pPOW6x?3u;X<|$L7gk!Ugw(N%npV zUP_3_C|gxWZaUma{1u;?@P_r^h_eG6Z&x^RWhD_062p(ZWMD~;HO>xiyj|bKm6b(E z#yY&PI#bzNfp6?;TfBdVtMWKZ$8hH%pP3Gm*uaZy_0?ymH{R}Vg3E@+VXA0XR&^?} zRqX}0dM~~ad^J7Qy4q)sQ=YV?*m^^gFtFa}h*{ZdYy5;?T^|{bo!H|67hCL)#n}hI z$-<6{oyxIo28{7nFy4#6mh26+VxR&Xakfz6?H?zk?ACy^iSYj`B)daF46t1&bsJdQ z5WZqp3Z2gLLfVD)O8ad7UCp=tJtE+3^)MK#mXIno7fRVQaA5F$$Yyy+7S6s|$v(;> zc}O{rEmiNOSTM~4sVqy(!u%!dwfi-##L)^{uv`^WH~TUU^;P2@E^DXi;41Oem#`ks`Zf#Jm4cl`;$0~9sJjI1KG+oAhe_kB*f@JCygc&oVp}s&jcxwVPMIU& z|C-Q<1~q;5x%#>dxBja4xxdGlCPpahazHvqE~D}i133gMo`y0U_cXY|Cl3t8##_NG zr+L80J3yHO7MRofPUL-X)R=zb38C!ZT#&%BSONj*&*UPMe|X6)ejM9^b>G3#CFqE(WVd7h^SW(F|a)1`f2j7C-1hYMmi#9*Fu)93DkS&F7#0n3L;WLNbN;t<4 zdvypsy9684X^DJrcUZm&+m?X&>tIj^GXkY|Q|vAOLCY=)rL^T1aN^N})Pk#@hIJ8E zdbs`lC&l0fqBl=WXD0!hTVYKY^E54vPz(>{p-TmRuo=v_>UsFL%ZOQK6s_oa!)U}O zHp63K;3KpkimXFE_y_n!a%@>!beW6Qi&yrJpdd@LX2?wkT@b10 z@JSgQ1uGo%J(PaJ{$t0oCaDKHv|1`g9q*w-t|+933DzHgG3t|BfT43;JQbljn1uV> zeGpd4jWg-(geiwcx;BP&=p(g5pKuJFa#{+h3nfOU@frFAc8bYXwu|vAmeaYg@dzEj zg)-!{q~|0W?nxo-U}93R<^bDJEu^1f<7ryLpUP4Xj3wCd480?Q_g%FU)mr!bL=n91 z9y&Kx4-2&3^06(~4N=-;`k)Y<`LAT@|IEmWpyeUj6_9F@zOK?9{AbnyuDqUE^hF~7 zaTR@u$kHV7J73$lUlO`}lBlSq!a-jmk&deK$44`gFl`#pfV51Ln1=OJp#r7JkcsVn zIycgfG*)J2oi$;bmx`EH4LNbD6b7V*%Ltc?-)*2@q0~T+MtTsQWAERiOm_WL%A%um zjIXY)6b7VoQJUFIzmI2Lpo4Wvt=mke@fTbHX=m7`zt=!D<9GO^NA(@NIK*!*L0rG< z0@go7O)b?*08eLkb0a4Qc8nIwvDqisu=Q0zz^*3x2wMI^Ykkre{UqKm0Z$56+`Eo8 zq2&u&8<75&XAgWmZeqD!d|G8j(SnctyI{tqvy4$DRz68{(emH@huFS>K7|jRphld# zj}BpbsS=)HmeBhqwaGhPr-ukmYNC#d>u38$hVn5(*&mJ4Nv-nsx9Eq2y@M3+nHOk& zUn7f})h;H@gqA{SjNv1Kxg!=Hd09&}<$P!z_1Ei!8rOe?wSKBeMMq zZrHS6LQc@@@#zE9j)ylzgdAo<{!1q$7td^`Ci&HU^tazg$YE;6u4A+aHy)swcz8=h z(lI9KchQoz(Ny`B{qz{YjW5uGes$t79fr>ypy_z{MFpDTyc0ARum4YKM%+d%a^oT9 z8ZQCRjL&z_cBXZT^FC%+A2O`%)QaP(A(Kfe!qYd1S#sB5x>uX3X;{CRj%2LMBik@8 zY}!WmvfBbI{WUdXcR8dW?xI%w%P~bEgYb_xh<3T_*w449^NhyAL zh@rI6;_%jsQGWm9_&|fOxs$$woz;+wp3ej`c0Vrw)^`(-ANm8e_ioIxcc+#euI1M(kQg=V9}cg5BRa&nSQSISc(A z)i8z`4Pe(BbdsFiO~(`bwjd7j@0e#{j7;c!LGfJkm?I0fJs+>_LEcsr2VuzxV`)J8 z3sLjWBE5*;@@f{b2#Y^9mR+#gQC=-wEW$A*MhlkgrzZJfLHwn_15({zkP@@$~4lxKZ3c~y+?VnERIA>Xsi{)KeEUSWur%>@e;&{2D z3v&-qtNegP{FRQ4`IrV(sDo-e!ZP4@Q#dt?UK79Pcm<*h^G;BU+>kEr)Z=w&cq96S z_eYNB5?y%DSAs=u$co0>so_oWrAs&mL0RH3okbBl!+{2iu2ijwyfjyQO;1nv0az`E z^Ft}ivUpc}R)H8Eg+Ft!648ZepHuKjg1oFi+@(kQsV3!aj?@fgK8eO4o#RMD#X>9- zM3cPCCBDR`yNz!(j|!dKyLIi-+Di6+!u-nGrE9nD4S&A7MicNu?b6_G4TkWp#~~*< z6l_J;6d1{Zaz>cz2X$rdLP zymgf5^5vys@1H5i|5_rxqU*ZlZjH`Q4LHpYBYYCyo=e3K-=L%9>=f|^g0+>R3r~Me zQ}N6>D#$+?D!!@*(f!kVs{yBB3<%{Q6WI`aOaVXpE2TK|D@x^uhecb%6phYKJmCh3 z?->N7Jp`u{(JVhaD!Tp%t$vrjekZ{hY=|DNjILj()jt-lAGTw@eO(?l=J0Oz!tkHO zM)J&^E!>u{rIXjt3SXtnE1Fl=uH8si6n*GIOhP^O~J^w18#h-6ta@sL0@_) z-t7g4)%}I`FyzL&N+C1+K=(CT$c+oUFu-f$-wcL=>{F>=tDBiOzvQ1eCBD>qWySTC zX28_n3$_bu+oNc^)uY;qhijX7ko=f^nq)!mK+&9|dwxW#8uEm7zXBXqk6!m4T(ANL zc*EZkhFT4V=4LK|{amq9@0AtTSDFFmKWMz5?p>kd9(5Y)ROb$tx;@9)NMt6n3)d7V z!9}aVk>~atzfG&5hrl~lLqUeyb6f|;o@3o=$o2LrpY?@$4TkV1z)p5~rAob5R$O0c zhNw^A66#~J;&YLK#Yn3DHc`X2MVlK*h~t%szK?RvQI}fiwj$9f(*x-DBynLdSfgn zzO&FDm(#JQ$HWG7enoQv(pF`>&5_t+XjNj5MHhBFt$4=@a3*<=JuZ=!(viCYj8q4Z;ia~?E0?utL&m$5XEt}GYLW;OU3 z3_&G_v#1V{nOqR>@PaeR{RJ~s{s;EdWR~nL@Unr7wAD2+=u*a@GLQ+%{k(|+?kDcR zm^3dcvu*jkkj382(SE!AoC9plvPN6y%#2=gP5S@7X8FG6@h+n+#COZ8h42NZejua@ zI+eYmyqUkX%Aq&0cYEdR-5!5QX5#M_@6g+HIK8`k>35Vn{$>R4jGC*Le-##$A$%i> s;({xZAnM8n~VQA0ybood5s; delta 7484 zcmeHLeQ;FO75A;Xd(X++B%6e6Q1Wp%u!%sFEq(+ckeK3h2!!m6t)Qqxu#!Rwj4+BV z>UPMCbYM*0h4!IQQ6drn_1oEh_Q>0O=6Y?l{KM3+Sh)fz~gWIX&7g%OU zNj(h$>HM!9PC647*Bi2cDMf}HLT6WKKt&OeOI6#h&NlHV_`0aI{D@(iaOE_nQ`$8o7_JBZY_lt-H`AtHv7a25Vs~F3%#iOrsI5TN_zr$n5 zo2aKgIq=`42L7=qH)Ip%`0~UHRIyQ1CVcTymF667$Xoa?(8Nt*6z_nb!c8KNjH1dZ;sz@;&VH=PTvq4k+)ZYAyQuXuN17q0 zTb-81#a_b$GXC}rJTBaE+#t-drl0Yv(4HF zUKVg0j85g|8}dHOujH*hBUWkL<6aJQKx8Gnv!KTRP~4aZDp~pCizxfJ=5$ptQ+#i5 z<%=QvxQo)&qdk|j4Vht5nc_|T z$)i32-c)WL`KQ7k40)~P*Uel|Zd$ci9Qa>&Q)hak{7XftwKHZ5U-3NL5kp?~ts~Ef zAus>dk!LaTuvqH_#Wmk@%4f&m&9TuW@-(i!4CN0;qo~WYC#XZaBUmy*vkU=*c;dK`nQqxJjeHhhU!e zxzsY=nfvt4_XO2HiNkqL7OEvp@YK~T{S{n8f`xh;Yu#ouAwwrIlQKI+F5eS#IC2lk z$@mZjPJ;-Qg4ZJX$v+2l>e-I@sg|PniGZgioJt|KzyXJn&w@KGBDa}73pTEkJPm$M z@&q&Uy6SremZnAXzn~hkNp1u^BtYkUxNWNC6m)A5`I+DVvQ7V#YzMb3;W#aRAKkGW zp5q)w%Oo4jm{q`q4{0P__z@@DZROtd$210-^1lW9(_*XxP|go>yO}BFKMzhV9?200 z<78STIbH=$!%2+-Wmutlim=@tkq7L9MstpR?l}jKv29?HT(0p%d5`B^I1!QmG(#;j z_#Md~4T7&iI1y~pF115{ETTFPB*&ro0uIN*f7>(@4?mcWVQTq4{*rVb=+I;_*^qZyq9|RcO#O#3(~1>AJ=lXr&o+KSNvHZn+=cSPX`X_t;Ny-c{%?uo}wE@p_BevjUn=Hh7I4;;?FLl zcaL9^E%PHu4uy+h7ReRZWoG;9B`aa#9=dZW&Y_y6=%VH)aYoEoo6Q>D^JvCmyq|`> z??76;3h(~1L<7xi#Cob}#0;8LX3L=U%{Yx-YQ<8^$WT?&fIRMsp_~l}YFvkv^vS;+ ziSylZYYmnpyT6pt;s^2owJa50hqq|7y#>oGnW3s?8J^ajZk-}R3&2Ai zw_<@6b(RXhXlkrRugY1DMYapdZ8qgrvRn|0Y{_?q^i=P&RQR{1a1#qReWmaYQ#i=N zE7^nK)WkvjWz#R2(#u(TwdwDV3d~qTC5Xv!z07jEilIR3R_+!owuRQ&yi|3_?j9)l zN=&kl#`&EtmA?ij+4w1toow#4xR6$-vYy2$UzT`1EC-Lfp)a z!@T@A)&bJL6lT)rin(vZxgYH1ojicswTK*RqYw7+@-Vh*5jOfkCwl4KPSjOl4@R^I zdvNMa^wRM+QCCH8W7wpLF}-81x~jREUO0q9X#F8{((ywq@E23SXLByV+O{;e*o8W?9l)K From a420496e952900c02d0f61f794997ca27a566511 Mon Sep 17 00:00:00 2001 From: phillip-hirsch Date: Wed, 6 Nov 2024 19:12:58 -0500 Subject: [PATCH 04/27] feat: Add bat syntax highlighting to macOS package contents --- macos/Ghostty.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index 414719f10..bc7f468e0 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 29C15B1D2CDC3B2900520DD4 /* bat in Resources */ = {isa = PBXBuildFile; fileRef = 29C15B1C2CDC3B2000520DD4 /* bat */; }; 55154BE02B33911F001622DC /* ghostty in Resources */ = {isa = PBXBuildFile; fileRef = 55154BDF2B33911F001622DC /* ghostty */; }; 552964E62B34A9B400030505 /* vim in Resources */ = {isa = PBXBuildFile; fileRef = 552964E52B34A9B400030505 /* vim */; }; 857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 857F63802A5E64F200CA4815 /* MainMenu.xib */; }; @@ -95,6 +96,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 29C15B1C2CDC3B2000520DD4 /* bat */ = {isa = PBXFileReference; lastKnownFileType = folder; name = bat; path = "../zig-out/share/bat"; sourceTree = ""; }; 3B39CAA42B33949B00DABEB8 /* GhosttyReleaseLocal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = GhosttyReleaseLocal.entitlements; sourceTree = ""; }; 55154BDF2B33911F001622DC /* ghostty */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ghostty; path = "../zig-out/share/ghostty"; sourceTree = ""; }; 552964E52B34A9B400030505 /* vim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = vim; path = "../zig-out/share/vim"; sourceTree = ""; }; @@ -366,6 +368,7 @@ A5A1F8862A489D7400D1E8BC /* Resources */ = { isa = PBXGroup; children = ( + 29C15B1C2CDC3B2000520DD4 /* bat */, 55154BDF2B33911F001622DC /* ghostty */, 552964E52B34A9B400030505 /* vim */, A586167B2B7703CC009BDB1D /* fish */, @@ -538,6 +541,7 @@ A5E112932AF73E6E00C6E0C2 /* ClipboardConfirmation.xib in Resources */, A5CDF1912AAF9A5800513312 /* ConfigurationErrors.xib in Resources */, 857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */, + 29C15B1D2CDC3B2900520DD4 /* bat in Resources */, A596309A2AEE1C6400D64628 /* Terminal.xib in Resources */, A586167C2B7703CC009BDB1D /* fish in Resources */, 55154BE02B33911F001622DC /* ghostty in Resources */, From aed51fd0b00018eeea71ba1afcb48d18792ae46c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 6 Nov 2024 14:15:31 -0800 Subject: [PATCH 05/27] terminal: PageList rename "page" to "node" everywhere This is more correct: a pagelist is a linked list of nodes, not pages. The nodes themselves contain pages but we were previously calling the nodes "pages" which was confusing, especially as I plan some future changes to the way pages are stored. --- src/Surface.zig | 2 +- src/renderer/Metal.zig | 11 +- src/renderer/OpenGL.zig | 4 +- src/renderer/cell.zig | 2 +- src/renderer/link.zig | 8 +- src/terminal/PageList.zig | 706 +++++++++++++++++++------------------ src/terminal/Screen.zig | 232 ++++++------ src/terminal/Selection.zig | 6 +- src/terminal/Terminal.zig | 208 +++++------ 9 files changed, 591 insertions(+), 588 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index d0c199010..10ecfd8f1 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3115,7 +3115,7 @@ fn processLinks(self: *Surface, pos: apprt.CursorPos) !bool { /// if there is no hyperlink. fn osc8URI(self: *Surface, pin: terminal.Pin) ?[]const u8 { _ = self; - const page = &pin.page.data; + const page = &pin.node.data; const cell = pin.rowAndCell().cell; const link_id = page.lookupHyperlink(cell) orelse return null; const entry = page.hyperlink_set.get(page.memory, link_id); diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index cb0f5a3de..742dfbcd4 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -1040,7 +1040,7 @@ pub fn updateFrame( null, ); while (it.next()) |chunk| { - var dirty_set = chunk.page.data.dirtyBitSet(); + var dirty_set = chunk.node.data.dirtyBitSet(); dirty_set.unsetAll(); } } @@ -2364,7 +2364,7 @@ fn rebuildCells( // True if this cell is selected const selected: bool = if (screen.selection) |sel| sel.contains(screen, .{ - .page = row.page, + .node = row.node, .y = row.y, .x = @intCast( // Spacer tails should show the selection @@ -2512,12 +2512,7 @@ fn rebuildCells( ); }; - if (style.flags.overline) self.addOverline( - @intCast(x), - @intCast(y), - fg, - alpha - ) catch |err| { + if (style.flags.overline) self.addOverline(@intCast(x), @intCast(y), fg, alpha) catch |err| { log.warn( "error adding overline to cell, will be invalid x={} y={}, err={}", .{ x, y, err }, diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 324fe14b3..5313315b1 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -844,7 +844,7 @@ pub fn updateFrame( null, ); while (it.next()) |chunk| { - var dirty_set = chunk.page.data.dirtyBitSet(); + var dirty_set = chunk.node.data.dirtyBitSet(); dirty_set.unsetAll(); } } @@ -1411,7 +1411,7 @@ pub fn rebuildCells( // True if this cell is selected const selected: bool = if (screen.selection) |sel| sel.contains(screen, .{ - .page = row.page, + .node = row.node, .y = row.y, .x = @intCast( // Spacer tails should show the selection diff --git a/src/renderer/cell.zig b/src/renderer/cell.zig index 1a315a0d8..c84fbcc6f 100644 --- a/src/renderer/cell.zig +++ b/src/renderer/cell.zig @@ -70,7 +70,7 @@ pub fn fgMode( } // If we are at the end of the screen its definitely constrained - if (cell_pin.x == cell_pin.page.data.size.cols - 1) break :text .constrained; + if (cell_pin.x == cell_pin.node.data.size.cols - 1) break :text .constrained; // If we have a previous cell and it was PUA then we need to // also constrain. This is so that multiple PUA glyphs align. diff --git a/src/renderer/link.zig b/src/renderer/link.zig index aa2db2b8d..994190ec8 100644 --- a/src/renderer/link.zig +++ b/src/renderer/link.zig @@ -122,7 +122,7 @@ pub const Set = struct { if (!mouse_cell.hyperlink) return; // Get our hyperlink entry - const page = &mouse_pin.page.data; + const page: *terminal.Page = &mouse_pin.node.data; const link_id = page.lookupHyperlink(mouse_cell) orelse { log.warn("failed to find hyperlink for cell", .{}); return; @@ -165,7 +165,7 @@ pub const Set = struct { for (row_pin.cells(.right), 0..) |*cell, x| { const match = match: { if (cell.hyperlink) { - if (row_pin.page.data.lookupHyperlink(cell)) |cell_link_id| { + if (row_pin.node.data.lookupHyperlink(cell)) |cell_link_id| { break :match cell_link_id == link_id; } } @@ -215,7 +215,7 @@ pub const Set = struct { // Expand it to the left. var it = mouse_pin.cellIterator(.left_up, null); while (it.next()) |cell_pin| { - const page = &cell_pin.page.data; + const page: *terminal.Page = &cell_pin.node.data; const rac = cell_pin.rowAndCell(); const cell = rac.cell; @@ -241,7 +241,7 @@ pub const Set = struct { // Expand it to the right it = mouse_pin.cellIterator(.right_down, null); while (it.next()) |cell_pin| { - const page = &cell_pin.page.data; + const page: *terminal.Page = &cell_pin.node.data; const rac = cell_pin.rowAndCell(); const cell = rac.cell; diff --git a/src/terminal/PageList.zig b/src/terminal/PageList.zig index d86ebc765..f1dbc2561 100644 --- a/src/terminal/PageList.zig +++ b/src/terminal/PageList.zig @@ -69,15 +69,15 @@ pub const MemoryPool = struct { page_alloc: Allocator, preheat: usize, ) !MemoryPool { - var pool = try NodePool.initPreheated(gen_alloc, preheat); - errdefer pool.deinit(); + var node_pool = try NodePool.initPreheated(gen_alloc, preheat); + errdefer node_pool.deinit(); var page_pool = try PagePool.initPreheated(page_alloc, preheat); errdefer page_pool.deinit(); var pin_pool = try PinPool.initPreheated(gen_alloc, 8); errdefer pin_pool.deinit(); return .{ .alloc = gen_alloc, - .nodes = pool, + .nodes = node_pool, .pages = page_pool, .pins = pin_pool, }; @@ -265,7 +265,7 @@ fn initPages( const cap = try std_capacity.adjust(.{ .cols = cols }); var rem = rows; while (rem > 0) { - const page = try pool.nodes.create(); + const node = try pool.nodes.create(); const page_buf = try pool.pages.create(); // no errdefer because the pool deinit will clean these up @@ -276,17 +276,17 @@ fn initPages( // Initialize the first set of pages to contain our viewport so that // the top of the first page is always the active area. - page.* = .{ + node.* = .{ .data = Page.initBuf( OffsetBuf.init(page_buf), Page.layout(cap), ), }; - page.data.size.rows = @min(rem, page.data.capacity.rows); - rem -= page.data.size.rows; + node.data.size.rows = @min(rem, node.data.capacity.rows); + rem -= node.data.size.rows; // Add the page to the list - page_list.append(page); + page_list.append(node); page_size += page_buf.len; } @@ -412,23 +412,23 @@ pub fn clone( while (it.next()) |chunk| { // Clone the page. We have to use createPageExt here because // we don't know if the source page has a standard size. - const page = try createPageExt( + const node = try createPageExt( pool, - chunk.page.data.capacity, + chunk.node.data.capacity, &page_size, ); - assert(page.data.capacity.rows >= chunk.end - chunk.start); - defer page.data.assertIntegrity(); - page.data.size.rows = chunk.end - chunk.start; - try page.data.cloneFrom( - &chunk.page.data, + assert(node.data.capacity.rows >= chunk.end - chunk.start); + defer node.data.assertIntegrity(); + node.data.size.rows = chunk.end - chunk.start; + try node.data.cloneFrom( + &chunk.node.data, chunk.start, chunk.end, ); - page_list.append(page); + page_list.append(node); - total_rows += page.data.size.rows; + total_rows += node.data.size.rows; // Remap our tracked pins by changing the page and // offsetting the Y position based on the chunk start. @@ -436,12 +436,12 @@ pub fn clone( const pin_keys = self.tracked_pins.keys(); for (pin_keys) |p| { // We're only interested in pins that were within the chunk. - if (p.page != chunk.page or + if (p.node != chunk.node or p.y < chunk.start or p.y >= chunk.end) continue; const new_p = try pool.pins.create(); new_p.* = p.*; - new_p.page = page; + new_p.node = node; new_p.y -= chunk.start; try remap.putNoClobber(p, new_p); try tracked_pins.putNoClobber(pool.alloc, new_p, {}); @@ -626,8 +626,8 @@ fn resizeCols( try dst_cursor.reflowRow(self, row); // Once we're done reflowing a page, destroy it. - if (row.y == row.page.data.size.rows - 1) { - self.destroyPage(row.page); + if (row.y == row.node.data.size.rows - 1) { + self.destroyNode(row.node); } } @@ -716,7 +716,7 @@ const ReflowCursor = struct { list: *PageList, row: Pin, ) !void { - const src_page = &row.page.data; + const src_page: *Page = &row.node.data; const src_row = row.rowAndCell().row; const src_y = row.y; @@ -744,7 +744,7 @@ const ReflowCursor = struct { { const pin_keys = list.tracked_pins.keys(); for (pin_keys) |p| { - if (&p.page.data != src_page or + if (&p.node.data != src_page or p.y != src_y) continue; // If this pin is in the blanks on the right and past the end @@ -794,11 +794,11 @@ const ReflowCursor = struct { { const pin_keys = list.tracked_pins.keys(); for (pin_keys) |p| { - if (&p.page.data != src_page or + if (&p.node.data != src_page or p.y != src_y or p.x != x) continue; - p.page = self.node; + p.node = self.node; p.x = self.x; p.y = self.y; } @@ -1036,7 +1036,7 @@ const ReflowCursor = struct { // then we should remove it from the list. if (old_page.size.rows == 0) { list.pages.remove(old_node); - list.destroyPage(old_node); + list.destroyNode(old_node); } } @@ -1187,7 +1187,7 @@ fn resizeWithoutReflow(self: *PageList, opts: Resize) !void { .lt => { var it = self.pageIterator(.right_down, .{ .screen = .{} }, null); while (it.next()) |chunk| { - const page = &chunk.page.data; + const page = &chunk.node.data; defer page.assertIntegrity(); const rows = page.rows.ptr(page.memory); for (0..page.size.rows) |i| { @@ -1307,7 +1307,7 @@ fn resizeWithoutReflowGrowCols( chunk: PageIterator.Chunk, ) !void { assert(cols > self.cols); - const page = &chunk.page.data; + const page = &chunk.node.data; const cap = try page.capacity.adjust(.{ .cols = cols }); // Update our col count @@ -1326,14 +1326,14 @@ fn resizeWithoutReflowGrowCols( // to allocate a page, and copy the old data into it. // On error, we need to undo all the pages we've added. - const prev = chunk.page.prev; + const prev = chunk.node.prev; errdefer { - var current = chunk.page.prev; + var current = chunk.node.prev; while (current) |p| { if (current == prev) break; current = p.prev; self.pages.remove(p); - self.destroyPage(p); + self.destroyNode(p); } } @@ -1391,8 +1391,8 @@ fn resizeWithoutReflowGrowCols( // We need to loop because our col growth may force us // to split pages. while (copied < page.size.rows) { - const new_page = try self.createPage(cap); - defer new_page.data.assertIntegrity(); + const new_node = try self.createPage(cap); + defer new_node.data.assertIntegrity(); // The length we can copy into the new page is at most the number // of rows in our cap. But if we can finish our source page we use that. @@ -1402,11 +1402,11 @@ fn resizeWithoutReflowGrowCols( const y_start = copied; const y_end = copied + len; const src_rows = page.rows.ptr(page.memory)[y_start..y_end]; - const dst_rows = new_page.data.rows.ptr(new_page.data.memory)[0..len]; + const dst_rows = new_node.data.rows.ptr(new_node.data.memory)[0..len]; for (dst_rows, src_rows) |*dst_row, *src_row| { - new_page.data.size.rows += 1; - errdefer new_page.data.size.rows -= 1; - try new_page.data.cloneRowFrom( + new_node.data.size.rows += 1; + errdefer new_node.data.size.rows -= 1; + try new_node.data.cloneRowFrom( page, dst_row, src_row, @@ -1415,15 +1415,15 @@ fn resizeWithoutReflowGrowCols( copied = y_end; // Insert our new page - self.pages.insertBefore(chunk.page, new_page); + self.pages.insertBefore(chunk.node, new_node); // Update our tracked pins that pointed to this previous page. const pin_keys = self.tracked_pins.keys(); for (pin_keys) |p| { - if (p.page != chunk.page or + if (p.node != chunk.node or p.y < y_start or p.y >= y_end) continue; - p.page = new_page; + p.node = new_node; p.y -= y_start; } } @@ -1431,8 +1431,8 @@ fn resizeWithoutReflowGrowCols( // Remove the old page. // Deallocate the old page. - self.pages.remove(chunk.page); - self.destroyPage(chunk.page); + self.pages.remove(chunk.node); + self.destroyNode(chunk.node); } /// Returns the number of trailing blank lines, not to exceed max. Max @@ -1485,7 +1485,7 @@ fn trimTrailingBlankRows( // we'd invalidate this pin, as well. const pin_keys = self.tracked_pins.keys(); for (pin_keys) |p| { - if (p.page != row_pin.page or + if (p.node != row_pin.node or p.y != row_pin.y) continue; return trimmed; } @@ -1493,11 +1493,11 @@ fn trimTrailingBlankRows( // No text, we can trim this row. Because it has // no text we can also be sure it has no styling // so we don't need to worry about memory. - row_pin.page.data.size.rows -= 1; - if (row_pin.page.data.size.rows == 0) { - self.erasePage(row_pin.page); + row_pin.node.data.size.rows -= 1; + if (row_pin.node.data.size.rows == 0) { + self.erasePage(row_pin.node); } else { - row_pin.page.data.assertIntegrity(); + row_pin.node.data.assertIntegrity(); } trimmed += 1; @@ -1723,8 +1723,8 @@ pub fn grow(self: *PageList) !?*List.Node { // new first page to the top-left. const pin_keys = self.tracked_pins.keys(); for (pin_keys) |p| { - if (p.page != first) continue; - p.page = self.pages.first.?; + if (p.node != first) continue; + p.node = self.pages.first.?; p.y = 0; p.x = 0; } @@ -1737,17 +1737,17 @@ pub fn grow(self: *PageList) !?*List.Node { } // We need to allocate a new memory buffer. - const next_page = try self.createPage(try std_capacity.adjust(.{ .cols = self.cols })); + const next_node = try self.createPage(try std_capacity.adjust(.{ .cols = self.cols })); // we don't errdefer this because we've added it to the linked // list and its fine to have dangling unused pages. - self.pages.append(next_page); - next_page.data.size.rows = 1; + self.pages.append(next_node); + next_node.data.size.rows = 1; // We should never be more than our max size here because we've // verified the case above. - next_page.data.assertIntegrity(); + next_node.data.assertIntegrity(); - return next_page; + return next_node; } /// Adjust the capacity of the given page in the list. @@ -1787,12 +1787,14 @@ pub const AdjustCapacityError = Allocator.Error || Page.CloneFromError; /// any requests to decrease will be ignored. pub fn adjustCapacity( self: *PageList, - page: *List.Node, + node: *List.Node, adjustment: AdjustCapacity, ) AdjustCapacityError!*List.Node { + const page: *Page = &node.data; + // We always start with the base capacity of the existing page. This // ensures we never shrink from what we need. - var cap = page.data.capacity; + var cap = page.capacity; // All ceilPowerOfTwo is unreachable because we're always same or less // bit width so maxInt is always possible. @@ -1820,26 +1822,27 @@ pub fn adjustCapacity( log.info("adjusting page capacity={}", .{cap}); // Create our new page and clone the old page into it. - const new_page = try self.createPage(cap); - errdefer self.destroyPage(new_page); - assert(new_page.data.capacity.rows >= page.data.capacity.rows); - new_page.data.size.rows = page.data.size.rows; - try new_page.data.cloneFrom(&page.data, 0, page.data.size.rows); + const new_node = try self.createPage(cap); + errdefer self.destroyNode(new_node); + const new_page: *Page = &new_node.data; + assert(new_page.capacity.rows >= page.capacity.rows); + new_page.size.rows = page.size.rows; + try new_page.cloneFrom(page, 0, page.size.rows); // Fix up all our tracked pins to point to the new page. const pin_keys = self.tracked_pins.keys(); for (pin_keys) |p| { - if (p.page != page) continue; - p.page = new_page; + if (p.node != node) continue; + p.node = new_node; } // Insert this page and destroy the old page - self.pages.insertBefore(page, new_page); - self.pages.remove(page); - self.destroyPage(page); + self.pages.insertBefore(node, new_node); + self.pages.remove(node); + self.destroyNode(node); - new_page.data.assertIntegrity(); - return new_page; + new_page.assertIntegrity(); + return new_node; } /// Create a new page node. This does not add it to the list and this @@ -1897,30 +1900,33 @@ fn createPageExt( return page; } -/// Destroy the memory of the given page and return it to the pool. The -/// page is assumed to already be removed from the linked list. -fn destroyPage(self: *PageList, page: *List.Node) void { - destroyPageExt(&self.pool, page, &self.page_size); +/// Destroy the memory of the given node in the PageList linked list +/// and return it to the pool. The node is assumed to already be removed +/// from the linked list. +fn destroyNode(self: *PageList, node: *List.Node) void { + destroyNodeExt(&self.pool, node, &self.page_size); } -fn destroyPageExt( +fn destroyNodeExt( pool: *MemoryPool, - page: *List.Node, + node: *List.Node, total_size: ?*usize, ) void { - // Update our accounting for page size - if (total_size) |v| v.* -= page.data.memory.len; + const page: *Page = &node.data; - if (page.data.memory.len <= std_size) { + // Update our accounting for page size + if (total_size) |v| v.* -= page.memory.len; + + if (page.memory.len <= std_size) { // Reset the memory to zero so it can be reused - @memset(page.data.memory, 0); - pool.pages.destroy(@ptrCast(page.data.memory.ptr)); + @memset(page.memory, 0); + pool.pages.destroy(@ptrCast(page.memory.ptr)); } else { const page_alloc = pool.pages.arena.child_allocator; - page_alloc.free(page.data.memory); + page_alloc.free(page.memory); } - pool.nodes.destroy(page); + pool.nodes.destroy(node); } /// Fast-path function to erase exactly 1 row. Erasing means that the row @@ -1936,32 +1942,32 @@ pub fn eraseRow( ) !void { const pn = self.pin(pt).?; - var page = pn.page; - var rows = page.data.rows.ptr(page.data.memory.ptr); + var node = pn.node; + var rows = node.data.rows.ptr(node.data.memory.ptr); // In order to move the following rows up we rotate the rows array by 1. // The rotate operation turns e.g. [ 0 1 2 3 ] in to [ 1 2 3 0 ], which // works perfectly to move all of our elements where they belong. - fastmem.rotateOnce(Row, rows[pn.y..page.data.size.rows]); + fastmem.rotateOnce(Row, rows[pn.y..node.data.size.rows]); // We adjust the tracked pins in this page, moving up any that were below // the removed row. { const pin_keys = self.tracked_pins.keys(); for (pin_keys) |p| { - if (p.page == page and p.y > pn.y) p.y -= 1; + if (p.node == node and p.y > pn.y) p.y -= 1; } } { // Set all the rows as dirty in this page - var dirty = page.data.dirtyBitSet(); - dirty.setRangeValue(.{ .start = pn.y, .end = page.data.size.rows }, true); + var dirty = node.data.dirtyBitSet(); + dirty.setRangeValue(.{ .start = pn.y, .end = node.data.size.rows }, true); } // We iterate through all of the following pages in order to move their // rows up by 1 as well. - while (page.next) |next| { + while (node.next) |next| { const next_rows = next.data.rows.ptr(next.data.memory.ptr); // We take the top row of the page and clone it in to the bottom @@ -1979,30 +1985,30 @@ pub fn eraseRow( // 5 5 5 | 6 // 6 6 6 | 7 // 7 7 7 <' 4 - try page.data.cloneRowFrom( + try node.data.cloneRowFrom( &next.data, - &rows[page.data.size.rows - 1], + &rows[node.data.size.rows - 1], &next_rows[0], ); - page = next; + node = next; rows = next_rows; - fastmem.rotateOnce(Row, rows[0..page.data.size.rows]); + fastmem.rotateOnce(Row, rows[0..node.data.size.rows]); // Set all the rows as dirty - var dirty = page.data.dirtyBitSet(); - dirty.setRangeValue(.{ .start = 0, .end = page.data.size.rows }, true); + var dirty = node.data.dirtyBitSet(); + dirty.setRangeValue(.{ .start = 0, .end = node.data.size.rows }, true); // Our tracked pins for this page need to be updated. // If the pin is in row 0 that means the corresponding row has // been moved to the previous page. Otherwise, move it up by 1. const pin_keys = self.tracked_pins.keys(); for (pin_keys) |p| { - if (p.page != page) continue; + if (p.node != node) continue; if (p.y == 0) { - p.page = page.prev.?; - p.y = p.page.data.size.rows - 1; + p.node = node.prev.?; + p.y = p.node.data.size.rows - 1; continue; } p.y -= 1; @@ -2010,7 +2016,7 @@ pub fn eraseRow( } // Clear the final row which was rotated from the top of the page. - page.data.clearCells(&rows[page.data.size.rows - 1], 0, page.data.size.cols); + node.data.clearCells(&rows[node.data.size.rows - 1], 0, node.data.size.cols); } /// A variant of eraseRow that shifts only a bounded number of following @@ -2031,24 +2037,24 @@ pub fn eraseRowBounded( const pn = self.pin(pt).?; - var page = pn.page; - var rows = page.data.rows.ptr(page.data.memory.ptr); + var node: *List.Node = pn.node; + var rows = node.data.rows.ptr(node.data.memory.ptr); // If the row limit is less than the remaining rows before the end of the // page, then we clear the row, rotate it to the end of the boundary limit // and update our pins. - if (page.data.size.rows - pn.y > limit) { - page.data.clearCells(&rows[pn.y], 0, page.data.size.cols); + if (node.data.size.rows - pn.y > limit) { + node.data.clearCells(&rows[pn.y], 0, node.data.size.cols); fastmem.rotateOnce(Row, rows[pn.y..][0 .. limit + 1]); // Set all the rows as dirty - var dirty = page.data.dirtyBitSet(); + var dirty = node.data.dirtyBitSet(); dirty.setRangeValue(.{ .start = pn.y, .end = pn.y + limit }, true); // Update pins in the shifted region. const pin_keys = self.tracked_pins.keys(); for (pin_keys) |p| { - if (p.page == page and + if (p.node == node and p.y >= pn.y and p.y <= pn.y + limit) { @@ -2063,24 +2069,24 @@ pub fn eraseRowBounded( return; } - fastmem.rotateOnce(Row, rows[pn.y..page.data.size.rows]); + fastmem.rotateOnce(Row, rows[pn.y..node.data.size.rows]); // All the rows in the page are dirty below the erased row. { - var dirty = page.data.dirtyBitSet(); - dirty.setRangeValue(.{ .start = pn.y, .end = page.data.size.rows }, true); + var dirty = node.data.dirtyBitSet(); + dirty.setRangeValue(.{ .start = pn.y, .end = node.data.size.rows }, true); } // We need to keep track of how many rows we've shifted so that we can // determine at what point we need to do a partial shift on subsequent // pages. - var shifted: usize = page.data.size.rows - pn.y; + var shifted: usize = node.data.size.rows - pn.y; // Update tracked pins. { const pin_keys = self.tracked_pins.keys(); for (pin_keys) |p| { - if (p.page == page and p.y >= pn.y) { + if (p.node == node and p.y >= pn.y) { if (p.y == 0) { p.x = 0; } else { @@ -2090,16 +2096,16 @@ pub fn eraseRowBounded( } } - while (page.next) |next| { + while (node.next) |next| { const next_rows = next.data.rows.ptr(next.data.memory.ptr); - try page.data.cloneRowFrom( + try node.data.cloneRowFrom( &next.data, - &rows[page.data.size.rows - 1], + &rows[node.data.size.rows - 1], &next_rows[0], ); - page = next; + node = next; rows = next_rows; // We check to see if this page contains enough rows to satisfy the @@ -2108,21 +2114,21 @@ pub fn eraseRowBounded( // // The logic here is very similar to the one before the loop. const shifted_limit = limit - shifted; - if (page.data.size.rows > shifted_limit) { - page.data.clearCells(&rows[0], 0, page.data.size.cols); + if (node.data.size.rows > shifted_limit) { + node.data.clearCells(&rows[0], 0, node.data.size.cols); fastmem.rotateOnce(Row, rows[0 .. shifted_limit + 1]); // Set all the rows as dirty - var dirty = page.data.dirtyBitSet(); + var dirty = node.data.dirtyBitSet(); dirty.setRangeValue(.{ .start = 0, .end = shifted_limit }, true); // Update pins in the shifted region. const pin_keys = self.tracked_pins.keys(); for (pin_keys) |p| { - if (p.page != page or p.y > shifted_limit) continue; + if (p.node != node or p.y > shifted_limit) continue; if (p.y == 0) { - p.page = page.prev.?; - p.y = p.page.data.size.rows - 1; + p.node = node.prev.?; + p.y = p.node.data.size.rows - 1; continue; } p.y -= 1; @@ -2131,22 +2137,22 @@ pub fn eraseRowBounded( return; } - fastmem.rotateOnce(Row, rows[0..page.data.size.rows]); + fastmem.rotateOnce(Row, rows[0..node.data.size.rows]); // Set all the rows as dirty - var dirty = page.data.dirtyBitSet(); - dirty.setRangeValue(.{ .start = 0, .end = page.data.size.rows }, true); + var dirty = node.data.dirtyBitSet(); + dirty.setRangeValue(.{ .start = 0, .end = node.data.size.rows }, true); - // Account for the rows shifted in this page. - shifted += page.data.size.rows; + // Account for the rows shifted in this node. + shifted += node.data.size.rows; // Update tracked pins. const pin_keys = self.tracked_pins.keys(); for (pin_keys) |p| { - if (p.page != page) continue; + if (p.node != node) continue; if (p.y == 0) { - p.page = page.prev.?; - p.y = p.page.data.size.rows - 1; + p.node = node.prev.?; + p.y = p.node.data.size.rows - 1; continue; } p.y -= 1; @@ -2155,7 +2161,7 @@ pub fn eraseRowBounded( // We reached the end of the page list before the limit, so we clear // the final row since it was rotated down from the top of this page. - page.data.clearCells(&rows[page.data.size.rows - 1], 0, page.data.size.cols); + node.data.clearCells(&rows[node.data.size.rows - 1], 0, node.data.size.cols); } /// Erase the rows from the given top to bottom (inclusive). Erasing @@ -2183,28 +2189,28 @@ pub fn eraseRows( // in our linked list. erasePage requires at least one other // page so to handle this we reinit this page, set it to zero // size which will let us grow our active area back. - if (chunk.page.next == null and chunk.page.prev == null) { - const page = &chunk.page.data; + if (chunk.node.next == null and chunk.node.prev == null) { + const page = &chunk.node.data; erased += page.size.rows; page.reinit(); page.size.rows = 0; break; } - self.erasePage(chunk.page); - erased += chunk.page.data.size.rows; + self.erasePage(chunk.node); + erased += chunk.node.data.size.rows; continue; } // We are modifying our chunk so make sure it is in a good state. - defer chunk.page.data.assertIntegrity(); + defer chunk.node.data.assertIntegrity(); // The chunk is not a full page so we need to move the rows. // This is a cheap operation because we're just moving cell offsets, // not the actual cell contents. assert(chunk.start == 0); - const rows = chunk.page.data.rows.ptr(chunk.page.data.memory); - const scroll_amount = chunk.page.data.size.rows - chunk.end; + const rows = chunk.node.data.rows.ptr(chunk.node.data.memory); + const scroll_amount = chunk.node.data.size.rows - chunk.end; for (0..scroll_amount) |i| { const src: *Row = &rows[i + chunk.end]; const dst: *Row = &rows[i]; @@ -2215,12 +2221,12 @@ pub fn eraseRows( // Clear our remaining cells that we didn't shift or swapped // in case we grow back into them. - for (scroll_amount..chunk.page.data.size.rows) |i| { + for (scroll_amount..chunk.node.data.size.rows) |i| { const row: *Row = &rows[i]; - chunk.page.data.clearCells( + chunk.node.data.clearCells( row, 0, - chunk.page.data.size.cols, + chunk.node.data.size.cols, ); } @@ -2228,7 +2234,7 @@ pub fn eraseRows( // row then we move it to the top of this page. const pin_keys = self.tracked_pins.keys(); for (pin_keys) |p| { - if (p.page != chunk.page) continue; + if (p.node != chunk.node) continue; if (p.y >= chunk.end) { p.y -= chunk.end; } else { @@ -2238,12 +2244,12 @@ pub fn eraseRows( } // Our new size is the amount we scrolled - chunk.page.data.size.rows = @intCast(scroll_amount); + chunk.node.data.size.rows = @intCast(scroll_amount); erased += chunk.end; // Set all the rows as dirty - var dirty = chunk.page.data.dirtyBitSet(); - dirty.setRangeValue(.{ .start = 0, .end = chunk.page.data.size.rows }, true); + var dirty = chunk.node.data.dirtyBitSet(); + dirty.setRangeValue(.{ .start = 0, .end = chunk.node.data.size.rows }, true); } // If we deleted active, we need to regrow because one of our invariants @@ -2271,7 +2277,7 @@ pub fn eraseRows( // For top, we move back to active if our erasing moved our // top page into the active area. - .top => if (self.pinIsActive(.{ .page = self.pages.first.? })) { + .top => if (self.pinIsActive(.{ .node = self.pages.first.? })) { self.viewport = .{ .active = {} }; }, } @@ -2280,21 +2286,21 @@ pub fn eraseRows( /// Erase a single page, freeing all its resources. The page can be /// anywhere in the linked list but must NOT be the final page in the /// entire list (i.e. must not make the list empty). -fn erasePage(self: *PageList, page: *List.Node) void { - assert(page.next != null or page.prev != null); +fn erasePage(self: *PageList, node: *List.Node) void { + assert(node.next != null or node.prev != null); // Update any tracked pins to move to the next page. const pin_keys = self.tracked_pins.keys(); for (pin_keys) |p| { - if (p.page != page) continue; - p.page = page.next orelse page.prev orelse unreachable; + if (p.node != node) continue; + p.node = node.next orelse node.prev orelse unreachable; p.y = 0; p.x = 0; } // Remove the page from the linked list - self.pages.remove(page); - self.destroyPage(page); + self.pages.remove(node); + self.destroyNode(node); } /// Returns the pin for the given point. The pin is NOT tracked so it @@ -2341,10 +2347,10 @@ pub fn countTrackedPins(self: *const PageList) usize { /// worst case. Only for runtime safety/debug. fn pinIsValid(self: *const PageList, p: Pin) bool { var it = self.pages.first; - while (it) |page| : (it = page.next) { - if (page != p.page) continue; - return p.y < page.data.size.rows and - p.x < page.data.size.cols; + while (it) |node| : (it = node.next) { + if (node != p.node) continue; + return p.y < node.data.size.rows and + p.x < node.data.size.cols; } return false; @@ -2356,19 +2362,19 @@ fn pinIsActive(self: *const PageList, p: Pin) bool { // If the pin is in the active page, then we can quickly determine // if we're beyond the end. const active = self.getTopLeft(.active); - if (p.page == active.page) return p.y >= active.y; + if (p.node == active.node) return p.y >= active.y; - var page_ = active.page.next; - while (page_) |page| { + var node_ = active.node.next; + while (node_) |node| { // This loop is pretty fast because the active area is - // never that large so this is at most one, two pages for + // never that large so this is at most one, two nodes for // reasonable terminals (including very large real world // ones). - // A page forward in the active area is our page, so we're + // A node forward in the active area is our node, so we're // definitely in the active area. - if (page == p.page) return true; - page_ = page.next; + if (node == p.node) return true; + node_ = node.next; } return false; @@ -2388,22 +2394,22 @@ pub fn pointFromPin(self: *const PageList, tag: point.Tag, p: Pin) ?point.Point // Count our first page which is special because it may be partial. var coord: point.Coordinate = .{ .x = p.x }; - if (p.page == tl.page) { + if (p.node == tl.node) { // If our top-left is after our y then we're outside the range. if (tl.y > p.y) return null; coord.y = p.y - tl.y; } else { - coord.y += tl.page.data.size.rows - tl.y; - var page_ = tl.page.next; - while (page_) |page| : (page_ = page.next) { - if (page == p.page) { + coord.y += tl.node.data.size.rows - tl.y; + var node_ = tl.node.next; + while (node_) |node| : (node_ = node.next) { + if (node == p.node) { coord.y += p.y; break; } - coord.y += page.data.size.rows; + coord.y += node.data.size.rows; } else { - // We never saw our page, meaning we're outside the range. + // We never saw our node, meaning we're outside the range. return null; } } @@ -2423,9 +2429,9 @@ pub fn pointFromPin(self: *const PageList, tag: point.Tag, p: Pin) ?point.Point /// Warning: this is slow and should not be used in performance critical paths pub fn getCell(self: *const PageList, pt: point.Point) ?Cell { const pt_pin = self.pin(pt) orelse return null; - const rac = pt_pin.page.data.getRowAndCell(pt_pin.x, pt_pin.y); + const rac = pt_pin.node.data.getRowAndCell(pt_pin.x, pt_pin.y); return .{ - .page = pt_pin.page, + .node = pt_pin.node, .row = rac.row, .cell = rac.cell, .row_idx = pt_pin.y, @@ -2463,16 +2469,16 @@ pub fn diagram(self: *const PageList, writer: anytype) !void { var it = self.pageIterator(.right_down, .{ .screen = .{} }, null); while (it.next()) |chunk| : (page_index += 1) { - cols = chunk.page.data.size.cols; + cols = chunk.node.data.size.cols; // Whether we've just skipped some number of rows and drawn // an ellipsis row (this is reset when a row is not skipped). var skipped = false; - for (0..chunk.page.data.size.rows) |y| { + for (0..chunk.node.data.size.rows) |y| { // Active header if (!active and - chunk.page == active_pin.page and + chunk.node == active_pin.node and active_pin.y == y) { active = true; @@ -2494,8 +2500,8 @@ pub fn diagram(self: *const PageList, writer: anytype) !void { // Row contents { - const row = chunk.page.data.getRow(y); - const cells = chunk.page.data.getCells(row)[0..cols]; + const row = chunk.node.data.getRow(y); + const cells = chunk.node.data.getCells(row)[0..cols]; var row_has_content = false; @@ -2544,7 +2550,7 @@ pub fn diagram(self: *const PageList, writer: anytype) !void { } try writer.print("{u}", .{cell.codepoint()}); if (cell.hasGrapheme()) { - const grapheme = chunk.page.data.lookupGrapheme(cell).?; + const grapheme = chunk.node.data.lookupGrapheme(cell).?; for (grapheme) |cp| { try writer.print("{u}", .{cp}); } @@ -2572,7 +2578,7 @@ pub fn diagram(self: *const PageList, writer: anytype) !void { var pin_count: usize = 0; const pin_keys = self.tracked_pins.keys(); for (pin_keys) |p| { - if (p.page != chunk.page) continue; + if (p.node != chunk.node) continue; if (p.y != y) continue; pin_buf[pin_count] = p; pin_count += 1; @@ -2651,7 +2657,7 @@ pub const CellIterator = struct { switch (self.row_it.page_it.direction) { .right_down => { - if (cell.x + 1 < cell.page.data.size.cols) { + if (cell.x + 1 < cell.node.data.size.cols) { // We still have cells in this row, increase x. var copy = cell; copy.x += 1; @@ -2672,7 +2678,7 @@ pub const CellIterator = struct { // We need to move to the previous row and last col if (self.row_it.next()) |next_cell| { var copy = next_cell; - copy.x = next_cell.page.data.size.cols - 1; + copy.x = next_cell.node.data.size.cols - 1; self.cell = copy; } else { self.cell = null; @@ -2711,7 +2717,7 @@ pub const RowIterator = struct { pub fn next(self: *RowIterator) ?Pin { const chunk = self.chunk orelse return null; - const row: Pin = .{ .page = chunk.page, .y = self.offset }; + const row: Pin = .{ .node = chunk.node, .y = self.offset }; switch (self.page_it.direction) { .right_down => { @@ -2798,20 +2804,20 @@ pub const PageIterator = struct { // If we have no limit, then we consume this entire page. Our // next row is the next page. self.row = next: { - const next_page = row.page.next orelse break :next null; - break :next .{ .page = next_page }; + const next_page = row.node.next orelse break :next null; + break :next .{ .node = next_page }; }; break :none .{ - .page = row.page, + .node = row.node, .start = row.y, - .end = row.page.data.size.rows, + .end = row.node.data.size.rows, }; }, .count => |*limit| count: { assert(limit.* > 0); // should be handled already - const len = @min(row.page.data.size.rows - row.y, limit.*); + const len = @min(row.node.data.size.rows - row.y, limit.*); if (len > limit.*) { self.row = row.down(len); limit.* -= len; @@ -2820,7 +2826,7 @@ pub const PageIterator = struct { } break :count .{ - .page = row.page, + .node = row.node, .start = row.y, .end = row.y + len, }; @@ -2829,16 +2835,16 @@ pub const PageIterator = struct { .row => |limit_row| row: { // If this is not the same page as our limit then we // can consume the entire page. - if (limit_row.page != row.page) { + if (limit_row.node != row.node) { self.row = next: { - const next_page = row.page.next orelse break :next null; - break :next .{ .page = next_page }; + const next_page = row.node.next orelse break :next null; + break :next .{ .node = next_page }; }; break :row .{ - .page = row.page, + .node = row.node, .start = row.y, - .end = row.page.data.size.rows, + .end = row.node.data.size.rows, }; } @@ -2847,7 +2853,7 @@ pub const PageIterator = struct { self.row = null; if (row.y > limit_row.y) return null; break :row .{ - .page = row.page, + .node = row.node, .start = row.y, .end = limit_row.y + 1, }; @@ -2864,15 +2870,15 @@ pub const PageIterator = struct { // If we have no limit, then we consume this entire page. Our // next row is the next page. self.row = next: { - const next_page = row.page.prev orelse break :next null; + const next_page = row.node.prev orelse break :next null; break :next .{ - .page = next_page, + .node = next_page, .y = next_page.data.size.rows - 1, }; }; break :none .{ - .page = row.page, + .node = row.node, .start = 0, .end = row.y + 1, }; @@ -2889,7 +2895,7 @@ pub const PageIterator = struct { } break :count .{ - .page = row.page, + .node = row.node, .start = row.y - len, .end = row.y - 1, }; @@ -2898,17 +2904,17 @@ pub const PageIterator = struct { .row => |limit_row| row: { // If this is not the same page as our limit then we // can consume the entire page. - if (limit_row.page != row.page) { + if (limit_row.node != row.node) { self.row = next: { - const next_page = row.page.prev orelse break :next null; + const next_page = row.node.prev orelse break :next null; break :next .{ - .page = next_page, + .node = next_page, .y = next_page.data.size.rows - 1, }; }; break :row .{ - .page = row.page, + .node = row.node, .start = 0, .end = row.y + 1, }; @@ -2919,7 +2925,7 @@ pub const PageIterator = struct { self.row = null; if (row.y < limit_row.y) return null; break :row .{ - .page = row.page, + .node = row.node, .start = limit_row.y, .end = row.y + 1, }; @@ -2928,18 +2934,18 @@ pub const PageIterator = struct { } pub const Chunk = struct { - page: *List.Node, + node: *List.Node, start: size.CellCountInt, end: size.CellCountInt, pub fn rows(self: Chunk) []Row { - const rows_ptr = self.page.data.rows.ptr(self.page.data.memory); + const rows_ptr = self.node.data.rows.ptr(self.node.data.memory); return rows_ptr[self.start..self.end]; } /// Returns true if this chunk represents every row in the page. pub fn fullPage(self: Chunk) bool { - return self.start == 0 and self.end == self.page.data.size.rows; + return self.start == 0 and self.end == self.node.data.size.rows; } }; }; @@ -2986,7 +2992,7 @@ pub fn pageIterator( pub fn getTopLeft(self: *const PageList, tag: point.Tag) Pin { return switch (tag) { // The full screen or history is always just the first page. - .screen, .history => .{ .page = self.pages.first.? }, + .screen, .history => .{ .node = self.pages.first.? }, .viewport => switch (self.viewport) { .active => self.getTopLeft(.active), @@ -3001,13 +3007,13 @@ pub fn getTopLeft(self: *const PageList, tag: point.Tag) Pin { .active => active: { var rem = self.rows; var it = self.pages.last; - while (it) |page| : (it = page.prev) { - if (rem <= page.data.size.rows) break :active .{ - .page = page, - .y = page.data.size.rows - rem, + while (it) |node| : (it = node.prev) { + if (rem <= node.data.size.rows) break :active .{ + .node = node, + .y = node.data.size.rows - rem, }; - rem -= page.data.size.rows; + rem -= node.data.size.rows; } unreachable; // assertion: we always have enough rows for active @@ -3021,11 +3027,11 @@ pub fn getTopLeft(self: *const PageList, tag: point.Tag) Pin { pub fn getBottomRight(self: *const PageList, tag: point.Tag) ?Pin { return switch (tag) { .screen, .active => last: { - const page = self.pages.last.?; + const node = self.pages.last.?; break :last .{ - .page = page, - .y = page.data.size.rows - 1, - .x = page.data.size.cols - 1, + .node = node, + .y = node.data.size.rows - 1, + .x = node.data.size.cols - 1, }; }, @@ -3048,10 +3054,10 @@ pub fn getBottomRight(self: *const PageList, tag: point.Tag) ?Pin { /// rows, so it is not pub. This is only used for testing/debugging. fn totalRows(self: *const PageList) usize { var rows: usize = 0; - var page = self.pages.first; - while (page) |p| { - rows += p.data.size.rows; - page = p.next; + var node_ = self.pages.first; + while (node_) |node| { + rows += node.data.size.rows; + node_ = node.next; } return rows; @@ -3060,10 +3066,10 @@ fn totalRows(self: *const PageList) usize { /// The total number of pages in this list. fn totalPages(self: *const PageList) usize { var pages: usize = 0; - var page = self.pages.first; - while (page) |p| { + var node_ = self.pages.first; + while (node_) |node| { pages += 1; - page = p.next; + node_ = node.next; } return pages; @@ -3126,7 +3132,7 @@ fn markDirty(self: *PageList, pt: point.Point) void { /// all up to date as the pagelist is modified. This isn't cheap so callers /// should limit the number of active pins as much as possible. pub const Pin = struct { - page: *List.Node, + node: *List.Node, y: size.CellCountInt = 0, x: size.CellCountInt = 0, @@ -3134,7 +3140,7 @@ pub const Pin = struct { row: *pagepkg.Row, cell: *pagepkg.Cell, } { - const rac = self.page.data.getRowAndCell(self.x, self.y); + const rac = self.node.data.getRowAndCell(self.x, self.y); return .{ .row = rac.row, .cell = rac.cell }; } @@ -3145,7 +3151,7 @@ pub const Pin = struct { /// inclusive of the x coordinate of the pin. pub fn cells(self: Pin, subset: CellSubset) []pagepkg.Cell { const rac = self.rowAndCell(); - const all = self.page.data.getCells(rac.row); + const all = self.node.data.getCells(rac.row); return switch (subset) { .all => all, .left => all[0 .. self.x + 1], @@ -3156,26 +3162,26 @@ pub const Pin = struct { /// Returns the grapheme codepoints for the given cell. These are only /// the EXTRA codepoints and not the first codepoint. pub fn grapheme(self: Pin, cell: *const pagepkg.Cell) ?[]u21 { - return self.page.data.lookupGrapheme(cell); + return self.node.data.lookupGrapheme(cell); } /// Returns the style for the given cell in this pin. pub fn style(self: Pin, cell: *const pagepkg.Cell) stylepkg.Style { if (cell.style_id == stylepkg.default_id) return .{}; - return self.page.data.styles.get( - self.page.data.memory, + return self.node.data.styles.get( + self.node.data.memory, cell.style_id, ).*; } /// Check if this pin is dirty. pub fn isDirty(self: Pin) bool { - return self.page.data.isRowDirty(self.y); + return self.node.data.isRowDirty(self.y); } /// Mark this pin location as dirty. pub fn markDirty(self: Pin) void { - var set = self.page.data.dirtyBitSet(); + var set = self.node.data.dirtyBitSet(); set.set(self.y); } @@ -3289,7 +3295,7 @@ pub const Pin = struct { // graphics deletion code. pub fn isBetween(self: Pin, top: Pin, bottom: Pin) bool { if (build_config.slow_runtime_safety) { - if (top.page == bottom.page) { + if (top.node == bottom.node) { // If top is bottom, must be ordered. assert(top.y <= bottom.y); if (top.y == bottom.y) { @@ -3297,14 +3303,14 @@ pub const Pin = struct { } } else { // If top is not bottom, top must be before bottom. - var page = top.page.next; - while (page) |p| : (page = p.next) { - if (p == bottom.page) break; + var node_ = top.node.next; + while (node_) |node| : (node_ = node.next) { + if (node == bottom.node) break; } else assert(false); } } - if (self.page == top.page) { + if (self.node == top.node) { // If our pin is the top page and our y is less than the top y // then we can't possibly be between the top and bottom. if (self.y < top.y) return false; @@ -3316,7 +3322,7 @@ pub const Pin = struct { // at least the full top page and since we're the same page // we're in the range. if (self.y > top.y) { - return if (self.page == bottom.page) + return if (self.node == bottom.node) self.y <= bottom.y else true; @@ -3327,7 +3333,7 @@ pub const Pin = struct { assert(self.y == top.y); if (self.x < top.x) return false; } - if (self.page == bottom.page) { + if (self.node == bottom.node) { // Our page is the bottom page so we're between the top and // bottom if our y is less than the bottom y. if (self.y > bottom.y) return false; @@ -3345,11 +3351,11 @@ pub const Pin = struct { // Since our loop starts at top.page.next we need to check that // top != bottom because if they're the same then we can't possibly // be between them. - if (top.page == bottom.page) return false; - var page = top.page.next; - while (page) |p| : (page = p.next) { - if (p == bottom.page) break; - if (p == self.page) return true; + if (top.node == bottom.node) return false; + var node_ = top.node.next; + while (node_) |node| : (node_ = node.next) { + if (node == bottom.node) break; + if (node == self.node) return true; } return false; @@ -3359,22 +3365,22 @@ pub const Pin = struct { /// it requires traversing the linked list of pages. This should not /// be called in performance critical paths. pub fn before(self: Pin, other: Pin) bool { - if (self.page == other.page) { + if (self.node == other.node) { if (self.y < other.y) return true; if (self.y > other.y) return false; return self.x < other.x; } - var page = self.page.next; - while (page) |p| : (page = p.next) { - if (p == other.page) return true; + var node_ = self.node.next; + while (node_) |node| : (node_ = node.next) { + if (node == other.node) return true; } return false; } pub fn eql(self: Pin, other: Pin) bool { - return self.page == other.page and + return self.node == other.node and self.y == other.y and self.x == other.x; } @@ -3389,7 +3395,7 @@ pub const Pin = struct { /// Move the pin right n columns. n must fit within the size. pub fn right(self: Pin, n: usize) Pin { - assert(self.x + n < self.page.data.size.cols); + assert(self.x + n < self.node.data.size.cols); var result = self; result.x +|= std.math.cast(size.CellCountInt, n) orelse std.math.maxInt(size.CellCountInt); @@ -3424,33 +3430,33 @@ pub const Pin = struct { }, } { // Index fits within this page - const rows = self.page.data.size.rows - (self.y + 1); + const rows = self.node.data.size.rows - (self.y + 1); if (n <= rows) return .{ .offset = .{ - .page = self.page, + .node = self.node, .y = std.math.cast(size.CellCountInt, self.y + n) orelse std.math.maxInt(size.CellCountInt), .x = self.x, } }; // Need to traverse page links to find the page - var page: *List.Node = self.page; + var node: *List.Node = self.node; var n_left: usize = n - rows; while (true) { - page = page.next orelse return .{ .overflow = .{ + node = node.next orelse return .{ .overflow = .{ .end = .{ - .page = page, - .y = page.data.size.rows - 1, + .node = node, + .y = node.data.size.rows - 1, .x = self.x, }, .remaining = n_left, } }; - if (n_left <= page.data.size.rows) return .{ .offset = .{ - .page = page, + if (n_left <= node.data.size.rows) return .{ .offset = .{ + .node = node, .y = std.math.cast(size.CellCountInt, n_left - 1) orelse std.math.maxInt(size.CellCountInt), .x = self.x, } }; - n_left -= page.data.size.rows; + n_left -= node.data.size.rows; } } @@ -3465,33 +3471,33 @@ pub const Pin = struct { } { // Index fits within this page if (n <= self.y) return .{ .offset = .{ - .page = self.page, + .node = self.node, .y = std.math.cast(size.CellCountInt, self.y - n) orelse std.math.maxInt(size.CellCountInt), .x = self.x, } }; // Need to traverse page links to find the page - var page: *List.Node = self.page; + var node: *List.Node = self.node; var n_left: usize = n - self.y; while (true) { - page = page.prev orelse return .{ .overflow = .{ - .end = .{ .page = page, .y = 0, .x = self.x }, + node = node.prev orelse return .{ .overflow = .{ + .end = .{ .node = node, .y = 0, .x = self.x }, .remaining = n_left, } }; - if (n_left <= page.data.size.rows) return .{ .offset = .{ - .page = page, - .y = std.math.cast(size.CellCountInt, page.data.size.rows - n_left) orelse + if (n_left <= node.data.size.rows) return .{ .offset = .{ + .node = node, + .y = std.math.cast(size.CellCountInt, node.data.size.rows - n_left) orelse std.math.maxInt(size.CellCountInt), .x = self.x, } }; - n_left -= page.data.size.rows; + n_left -= node.data.size.rows; } } }; const Cell = struct { - page: *List.Node, + node: *List.Node, row: *pagepkg.Row, cell: *pagepkg.Cell, row_idx: size.CellCountInt, @@ -3502,7 +3508,7 @@ const Cell = struct { /// This is not very performant this is primarily used for assertions /// and testing. pub fn isDirty(self: Cell) bool { - return self.page.data.isRowDirty(self.row_idx); + return self.node.data.isRowDirty(self.row_idx); } /// Get the cell style. @@ -3510,8 +3516,8 @@ const Cell = struct { /// Not meant for non-test usage since this is inefficient. pub fn style(self: Cell) stylepkg.Style { if (self.cell.style_id == stylepkg.default_id) return .{}; - return self.page.data.styles.get( - self.page.data.memory, + return self.node.data.styles.get( + self.node.data.memory, self.cell.style_id, ).*; } @@ -3524,10 +3530,10 @@ const Cell = struct { /// carefully if you really need this. pub fn screenPoint(self: Cell) point.Point { var y: size.CellCountInt = self.row_idx; - var page = self.page; - while (page.prev) |prev| { - y += prev.data.size.rows; - page = prev; + var node_ = self.node; + while (node_.prev) |node| { + y += node.data.size.rows; + node_ = node; } return .{ .screen = .{ @@ -3549,7 +3555,7 @@ test "PageList" { // Active area should be the top try testing.expectEqual(Pin{ - .page = s.pages.first.?, + .node = s.pages.first.?, .y = 0, .x = 0, }, s.getTopLeft(.active)); @@ -3592,7 +3598,7 @@ test "PageList pointFromPin active no history" { .x = 0, }, }, s.pointFromPin(.active, .{ - .page = s.pages.first.?, + .node = s.pages.first.?, .y = 0, .x = 0, }).?); @@ -3604,7 +3610,7 @@ test "PageList pointFromPin active no history" { .x = 4, }, }, s.pointFromPin(.active, .{ - .page = s.pages.first.?, + .node = s.pages.first.?, .y = 2, .x = 4, }).?); @@ -3626,7 +3632,7 @@ test "PageList pointFromPin active with history" { .x = 2, }, }, s.pointFromPin(.active, .{ - .page = s.pages.first.?, + .node = s.pages.first.?, .y = 30, .x = 2, }).?); @@ -3635,7 +3641,7 @@ test "PageList pointFromPin active with history" { // In history, invalid { try testing.expect(s.pointFromPin(.active, .{ - .page = s.pages.first.?, + .node = s.pages.first.?, .y = 21, .x = 2, }) == null); @@ -3660,7 +3666,7 @@ test "PageList pointFromPin active from prior page" { .x = 2, }, }, s.pointFromPin(.active, .{ - .page = s.pages.last.?, + .node = s.pages.last.?, .y = 0, .x = 2, }).?); @@ -3669,7 +3675,7 @@ test "PageList pointFromPin active from prior page" { // Prior page { try testing.expect(s.pointFromPin(.active, .{ - .page = s.pages.first.?, + .node = s.pages.first.?, .y = 0, .x = 0, }) == null); @@ -3698,7 +3704,7 @@ test "PageList pointFromPin traverse pages" { .x = 2, }, }, s.pointFromPin(.screen, .{ - .page = s.pages.last.?.prev.?, + .node = s.pages.last.?.prev.?, .y = 5, .x = 2, }).?); @@ -3707,7 +3713,7 @@ test "PageList pointFromPin traverse pages" { // Prior page { try testing.expect(s.pointFromPin(.active, .{ - .page = s.pages.first.?, + .node = s.pages.first.?, .y = 0, .x = 0, }) == null); @@ -4159,7 +4165,7 @@ test "PageList grow allocate" { try testing.expect(last_node.next.? == new); { const cell = s.getCell(.{ .active = .{ .y = s.rows - 1 } }).?; - try testing.expect(cell.page == new); + try testing.expect(cell.node == new); try testing.expectEqual(point.Point{ .screen = .{ .x = 0, .y = last.capacity.rows, @@ -4195,7 +4201,7 @@ test "PageList grow prune scrollback" { // Create a tracked pin in the first page const p = try s.trackPin(s.pin(.{ .screen = .{} }).?); defer s.untrackPin(p); - try testing.expect(p.page == s.pages.first.?); + try testing.expect(p.node == s.pages.first.?); // Next should create a new page, but it should reuse our first // page since we're at max size. @@ -4208,7 +4214,7 @@ test "PageList grow prune scrollback" { try testing.expectEqual(page1_node, s.pages.last.?); // Our tracked pin should point to the top-left of the first page - try testing.expect(p.page == s.pages.first.?); + try testing.expect(p.node == s.pages.first.?); try testing.expect(p.x == 0); try testing.expect(p.y == 0); } @@ -4356,7 +4362,7 @@ test "PageList pageIterator single page" { var it = s.pageIterator(.right_down, .{ .active = .{} }, null); { const chunk = it.next().?; - try testing.expect(chunk.page == s.pages.first.?); + try testing.expect(chunk.node == s.pages.first.?); try testing.expectEqual(@as(usize, 0), chunk.start); try testing.expectEqual(@as(usize, s.rows), chunk.end); } @@ -4384,14 +4390,14 @@ test "PageList pageIterator two pages" { var it = s.pageIterator(.right_down, .{ .active = .{} }, null); { const chunk = it.next().?; - try testing.expect(chunk.page == s.pages.first.?); - const start = chunk.page.data.size.rows - s.rows + 1; + try testing.expect(chunk.node == s.pages.first.?); + const start = chunk.node.data.size.rows - s.rows + 1; try testing.expectEqual(start, chunk.start); - try testing.expectEqual(chunk.page.data.size.rows, chunk.end); + try testing.expectEqual(chunk.node.data.size.rows, chunk.end); } { const chunk = it.next().?; - try testing.expect(chunk.page == s.pages.last.?); + try testing.expect(chunk.node == s.pages.last.?); const start: usize = 0; try testing.expectEqual(start, chunk.start); try testing.expectEqual(start + 1, chunk.end); @@ -4419,7 +4425,7 @@ test "PageList pageIterator history two pages" { { const active_tl = s.getTopLeft(.active); const chunk = it.next().?; - try testing.expect(chunk.page == s.pages.first.?); + try testing.expect(chunk.node == s.pages.first.?); const start: usize = 0; try testing.expectEqual(start, chunk.start); try testing.expectEqual(active_tl.y, chunk.end); @@ -4441,7 +4447,7 @@ test "PageList pageIterator reverse single page" { var it = s.pageIterator(.left_up, .{ .active = .{} }, null); { const chunk = it.next().?; - try testing.expect(chunk.page == s.pages.first.?); + try testing.expect(chunk.node == s.pages.first.?); try testing.expectEqual(@as(usize, 0), chunk.start); try testing.expectEqual(@as(usize, s.rows), chunk.end); } @@ -4470,7 +4476,7 @@ test "PageList pageIterator reverse two pages" { var count: usize = 0; { const chunk = it.next().?; - try testing.expect(chunk.page == s.pages.last.?); + try testing.expect(chunk.node == s.pages.last.?); const start: usize = 0; try testing.expectEqual(start, chunk.start); try testing.expectEqual(start + 1, chunk.end); @@ -4478,10 +4484,10 @@ test "PageList pageIterator reverse two pages" { } { const chunk = it.next().?; - try testing.expect(chunk.page == s.pages.first.?); - const start = chunk.page.data.size.rows - s.rows + 1; + try testing.expect(chunk.node == s.pages.first.?); + const start = chunk.node.data.size.rows - s.rows + 1; try testing.expectEqual(start, chunk.start); - try testing.expectEqual(chunk.page.data.size.rows, chunk.end); + try testing.expectEqual(chunk.node.data.size.rows, chunk.end); count += chunk.end - chunk.start; } try testing.expect(it.next() == null); @@ -4508,7 +4514,7 @@ test "PageList pageIterator reverse history two pages" { { const active_tl = s.getTopLeft(.active); const chunk = it.next().?; - try testing.expect(chunk.page == s.pages.first.?); + try testing.expect(chunk.node == s.pages.first.?); const start: usize = 0; try testing.expectEqual(start, chunk.start); try testing.expectEqual(active_tl.y, chunk.end); @@ -4688,7 +4694,7 @@ test "PageList erase row with tracked pin resets to top-left" { try testing.expectEqual(s.rows, s.totalRows()); // Our pin should move to the first page - try testing.expectEqual(s.pages.first.?, p.page); + try testing.expectEqual(s.pages.first.?, p.node); try testing.expectEqual(@as(usize, 0), p.y); try testing.expectEqual(@as(usize, 0), p.x); } @@ -4709,7 +4715,7 @@ test "PageList erase row with tracked pin shifts" { try testing.expectEqual(s.rows, s.totalRows()); // Our pin should move to the first page - try testing.expectEqual(s.pages.first.?, p.page); + try testing.expectEqual(s.pages.first.?, p.node); try testing.expectEqual(@as(usize, 0), p.y); try testing.expectEqual(@as(usize, 2), p.x); } @@ -4730,7 +4736,7 @@ test "PageList erase row with tracked pin is erased" { try testing.expectEqual(s.rows, s.totalRows()); // Our pin should move to the first page - try testing.expectEqual(s.pages.first.?, p.page); + try testing.expectEqual(s.pages.first.?, p.node); try testing.expectEqual(@as(usize, 0), p.y); try testing.expectEqual(@as(usize, 0), p.x); } @@ -4751,7 +4757,7 @@ test "PageList erase resets viewport to active if moves within active" { // Move our viewport to the top s.scroll(.{ .delta_row = -@as(isize, @intCast(s.totalRows())) }); try testing.expect(s.viewport == .pin); - try testing.expect(s.viewport_pin.page == s.pages.first.?); + try testing.expect(s.viewport_pin.node == s.pages.first.?); // Erase the entire history, we should be back to just our active set. s.eraseRows(.{ .history = .{} }, null); @@ -4774,12 +4780,12 @@ test "PageList erase resets viewport if inside erased page but not active" { // Move our viewport to the top s.scroll(.{ .delta_row = -@as(isize, @intCast(s.totalRows())) }); try testing.expect(s.viewport == .pin); - try testing.expect(s.viewport_pin.page == s.pages.first.?); + try testing.expect(s.viewport_pin.node == s.pages.first.?); // Erase the entire history, we should be back to just our active set. s.eraseRows(.{ .history = .{} }, .{ .history = .{ .y = 2 } }); try testing.expect(s.viewport == .pin); - try testing.expect(s.viewport_pin.page == s.pages.first.?); + try testing.expect(s.viewport_pin.node == s.pages.first.?); } test "PageList erase resets viewport to active if top is inside active" { @@ -4868,15 +4874,15 @@ test "PageList eraseRowBounded less than full row" { try testing.expect(s.isDirty(.{ .active = .{ .x = 0, .y = 7 } })); try testing.expect(!s.isDirty(.{ .active = .{ .x = 0, .y = 8 } })); - try testing.expectEqual(s.pages.first.?, p_top.page); + try testing.expectEqual(s.pages.first.?, p_top.node); try testing.expectEqual(@as(usize, 4), p_top.y); try testing.expectEqual(@as(usize, 0), p_top.x); - try testing.expectEqual(s.pages.first.?, p_bot.page); + try testing.expectEqual(s.pages.first.?, p_bot.node); try testing.expectEqual(@as(usize, 7), p_bot.y); try testing.expectEqual(@as(usize, 0), p_bot.x); - try testing.expectEqual(s.pages.first.?, p_out.page); + try testing.expectEqual(s.pages.first.?, p_out.node); try testing.expectEqual(@as(usize, 9), p_out.y); try testing.expectEqual(@as(usize, 0), p_out.x); } @@ -4902,7 +4908,7 @@ test "PageList eraseRowBounded with pin at top" { try testing.expect(s.isDirty(.{ .active = .{ .x = 0, .y = 2 } })); try testing.expect(!s.isDirty(.{ .active = .{ .x = 0, .y = 3 } })); - try testing.expectEqual(s.pages.first.?, p_top.page); + try testing.expectEqual(s.pages.first.?, p_top.node); try testing.expectEqual(@as(usize, 0), p_top.y); try testing.expectEqual(@as(usize, 0), p_top.x); } @@ -4932,11 +4938,11 @@ test "PageList eraseRowBounded full rows single page" { } })); // Our pin should move to the first page - try testing.expectEqual(s.pages.first.?, p_in.page); + try testing.expectEqual(s.pages.first.?, p_in.node); try testing.expectEqual(@as(usize, 6), p_in.y); try testing.expectEqual(@as(usize, 0), p_in.x); - try testing.expectEqual(s.pages.first.?, p_out.page); + try testing.expectEqual(s.pages.first.?, p_out.node); try testing.expectEqual(@as(usize, 8), p_out.y); try testing.expectEqual(@as(usize, 0), p_out.x); } @@ -4968,19 +4974,19 @@ test "PageList eraseRowBounded full rows two pages" { defer s.untrackPin(p_out); { - try testing.expectEqual(s.pages.last.?.prev.?, p_first.page); - try testing.expectEqual(@as(usize, p_first.page.data.size.rows - 1), p_first.y); + try testing.expectEqual(s.pages.last.?.prev.?, p_first.node); + try testing.expectEqual(@as(usize, p_first.node.data.size.rows - 1), p_first.y); try testing.expectEqual(@as(usize, 0), p_first.x); - try testing.expectEqual(s.pages.last.?.prev.?, p_first_out.page); - try testing.expectEqual(@as(usize, p_first_out.page.data.size.rows - 2), p_first_out.y); + try testing.expectEqual(s.pages.last.?.prev.?, p_first_out.node); + try testing.expectEqual(@as(usize, p_first_out.node.data.size.rows - 2), p_first_out.y); try testing.expectEqual(@as(usize, 0), p_first_out.x); - try testing.expectEqual(s.pages.last.?, p_in.page); + try testing.expectEqual(s.pages.last.?, p_in.node); try testing.expectEqual(@as(usize, 3), p_in.y); try testing.expectEqual(@as(usize, 0), p_in.x); - try testing.expectEqual(s.pages.last.?, p_out.page); + try testing.expectEqual(s.pages.last.?, p_out.node); try testing.expectEqual(@as(usize, 4), p_out.y); try testing.expectEqual(@as(usize, 0), p_out.x); } @@ -4996,22 +5002,22 @@ test "PageList eraseRowBounded full rows two pages" { } })); // In page in first page is shifted - try testing.expectEqual(s.pages.last.?.prev.?, p_first.page); - try testing.expectEqual(@as(usize, p_first.page.data.size.rows - 2), p_first.y); + try testing.expectEqual(s.pages.last.?.prev.?, p_first.node); + try testing.expectEqual(@as(usize, p_first.node.data.size.rows - 2), p_first.y); try testing.expectEqual(@as(usize, 0), p_first.x); // Out page in first page should not be shifted - try testing.expectEqual(s.pages.last.?.prev.?, p_first_out.page); - try testing.expectEqual(@as(usize, p_first_out.page.data.size.rows - 2), p_first_out.y); + try testing.expectEqual(s.pages.last.?.prev.?, p_first_out.node); + try testing.expectEqual(@as(usize, p_first_out.node.data.size.rows - 2), p_first_out.y); try testing.expectEqual(@as(usize, 0), p_first_out.x); // In page is shifted - try testing.expectEqual(s.pages.last.?, p_in.page); + try testing.expectEqual(s.pages.last.?, p_in.node); try testing.expectEqual(@as(usize, 2), p_in.y); try testing.expectEqual(@as(usize, 0), p_in.x); // Out page is not shifted - try testing.expectEqual(s.pages.last.?, p_out.page); + try testing.expectEqual(s.pages.last.?, p_out.node); try testing.expectEqual(@as(usize, 4), p_out.y); try testing.expectEqual(@as(usize, 0), p_out.x); } @@ -5669,7 +5675,7 @@ test "PageList resize (no reflow) less cols" { var it = s.rowIterator(.right_down, .{ .screen = .{} }, null); while (it.next()) |offset| { const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expectEqual(@as(usize, 5), cells.len); } } @@ -5693,7 +5699,7 @@ test "PageList resize (no reflow) less cols pin in trimmed cols" { var it = s.rowIterator(.right_down, .{ .screen = .{} }, null); while (it.next()) |offset| { const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expectEqual(@as(usize, 5), cells.len); } @@ -5729,7 +5735,7 @@ test "PageList resize (no reflow) less cols clears graphemes" { var it = s.pageIterator(.right_down, .{ .screen = .{} }, null); while (it.next()) |chunk| { - try testing.expectEqual(@as(usize, 0), chunk.page.data.graphemeCount()); + try testing.expectEqual(@as(usize, 0), chunk.node.data.graphemeCount()); } } @@ -5748,7 +5754,7 @@ test "PageList resize (no reflow) more cols" { var it = s.rowIterator(.right_down, .{ .screen = .{} }, null); while (it.next()) |offset| { const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expectEqual(@as(usize, 10), cells.len); } } @@ -5921,7 +5927,7 @@ test "PageList resize (no reflow) less cols then more cols" { var it = s.rowIterator(.right_down, .{ .screen = .{} }, null); while (it.next()) |offset| { const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expectEqual(@as(usize, 5), cells.len); } } @@ -5941,7 +5947,7 @@ test "PageList resize (no reflow) less rows and cols" { var it = s.rowIterator(.right_down, .{ .screen = .{} }, null); while (it.next()) |offset| { const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expectEqual(@as(usize, 5), cells.len); } } @@ -5962,7 +5968,7 @@ test "PageList resize (no reflow) more rows and less cols" { var it = s.rowIterator(.right_down, .{ .screen = .{} }, null); while (it.next()) |offset| { const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expectEqual(@as(usize, 5), cells.len); } } @@ -6002,7 +6008,7 @@ test "PageList resize (no reflow) empty screen" { var it = s.rowIterator(.right_down, .{ .screen = .{} }, null); while (it.next()) |offset| { const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expectEqual(@as(usize, 10), cells.len); } } @@ -6041,7 +6047,7 @@ test "PageList resize (no reflow) more cols forces smaller cap" { var it = s.rowIterator(.right_down, .{ .screen = .{} }, null); while (it.next()) |offset| { const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expectEqual(@as(usize, cap2.cols), cells.len); try testing.expectEqual(@as(u21, 'A'), cells[0].content.codepoint); } @@ -6146,7 +6152,7 @@ test "PageList resize reflow more cols no wrapped rows" { var it = s.rowIterator(.right_down, .{ .screen = .{} }, null); while (it.next()) |offset| { const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expectEqual(@as(usize, 10), cells.len); try testing.expectEqual(@as(u21, 'A'), cells[0].content.codepoint); } @@ -6197,7 +6203,7 @@ test "PageList resize reflow more cols wrapped rows" { // First row should be unwrapped const offset = it.next().?; const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expect(!rac.row.wrap); try testing.expectEqual(@as(usize, 4), cells.len); try testing.expectEqual(@as(u21, 'A'), cells[0].content.codepoint); @@ -6453,7 +6459,7 @@ test "PageList resize reflow more cols wrap across page boundary cursor in secon // Put a tracked pin in wrapped row on the last page const p = try s.trackPin(s.pin(.{ .active = .{ .x = 1, .y = 9 } }).?); defer s.untrackPin(p); - try testing.expect(p.page == s.pages.last.?); + try testing.expect(p.node == s.pages.last.?); // We expect one fewer rows since we unwrapped a row. const end_rows = s.totalRows() - 1; @@ -6537,7 +6543,7 @@ test "PageList resize reflow less cols wrap across page boundary cursor in secon // Put a tracked pin in wrapped row on the last page const p = try s.trackPin(s.pin(.{ .active = .{ .x = 2, .y = 5 } }).?); defer s.untrackPin(p); - try testing.expect(p.page == s.pages.last.?); + try testing.expect(p.node == s.pages.last.?); try testing.expect(p.y == 0); // PageList.diagram -> @@ -7235,7 +7241,7 @@ test "PageList resize reflow less cols no wrapped rows" { var offset_copy = offset; offset_copy.x = @intCast(x); const rac = offset_copy.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expectEqual(@as(usize, 5), cells.len); try testing.expectEqual(@as(u21, @intCast(x)), cells[x].content.codepoint); } @@ -7279,7 +7285,7 @@ test "PageList resize reflow less cols wrapped rows" { // First row should be wrapped const offset = it.next().?; const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expect(rac.row.wrap); try testing.expectEqual(@as(usize, 2), cells.len); try testing.expectEqual(@as(u21, 0), cells[0].content.codepoint); @@ -7287,7 +7293,7 @@ test "PageList resize reflow less cols wrapped rows" { { const offset = it.next().?; const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expect(!rac.row.wrap); try testing.expectEqual(@as(usize, 2), cells.len); try testing.expectEqual(@as(u21, 2), cells[0].content.codepoint); @@ -7296,7 +7302,7 @@ test "PageList resize reflow less cols wrapped rows" { // First row should be wrapped const offset = it.next().?; const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expect(rac.row.wrap); try testing.expectEqual(@as(usize, 2), cells.len); try testing.expectEqual(@as(u21, 0), cells[0].content.codepoint); @@ -7304,7 +7310,7 @@ test "PageList resize reflow less cols wrapped rows" { { const offset = it.next().?; const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expect(!rac.row.wrap); try testing.expectEqual(@as(usize, 2), cells.len); try testing.expectEqual(@as(u21, 2), cells[0].content.codepoint); @@ -7355,7 +7361,7 @@ test "PageList resize reflow less cols wrapped rows with graphemes" { // First row should be wrapped const offset = it.next().?; const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expect(rac.row.wrap); try testing.expectEqual(@as(usize, 2), cells.len); try testing.expectEqual(@as(u21, 0), cells[0].content.codepoint); @@ -7363,7 +7369,7 @@ test "PageList resize reflow less cols wrapped rows with graphemes" { { const offset = it.next().?; const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expect(!rac.row.wrap); try testing.expect(rac.row.grapheme); try testing.expectEqual(@as(usize, 2), cells.len); @@ -7377,7 +7383,7 @@ test "PageList resize reflow less cols wrapped rows with graphemes" { // First row should be wrapped const offset = it.next().?; const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expect(rac.row.wrap); try testing.expectEqual(@as(usize, 2), cells.len); try testing.expectEqual(@as(u21, 0), cells[0].content.codepoint); @@ -7385,7 +7391,7 @@ test "PageList resize reflow less cols wrapped rows with graphemes" { { const offset = it.next().?; const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expect(!rac.row.wrap); try testing.expect(rac.row.grapheme); try testing.expectEqual(@as(usize, 2), cells.len); @@ -7721,7 +7727,7 @@ test "PageList resize reflow less cols blank lines" { // First row should be wrapped const offset = it.next().?; const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expect(rac.row.wrap); try testing.expectEqual(@as(usize, 2), cells.len); try testing.expectEqual(@as(u21, 0), cells[0].content.codepoint); @@ -7729,7 +7735,7 @@ test "PageList resize reflow less cols blank lines" { { const offset = it.next().?; const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expect(!rac.row.wrap); try testing.expectEqual(@as(usize, 2), cells.len); try testing.expectEqual(@as(u21, 2), cells[0].content.codepoint); @@ -7777,7 +7783,7 @@ test "PageList resize reflow less cols blank lines between" { { const offset = it.next().?; const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expect(rac.row.wrap); try testing.expectEqual(@as(usize, 2), cells.len); try testing.expectEqual(@as(u21, 0), cells[0].content.codepoint); @@ -7785,7 +7791,7 @@ test "PageList resize reflow less cols blank lines between" { { const offset = it.next().?; const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expect(!rac.row.wrap); try testing.expectEqual(@as(usize, 2), cells.len); try testing.expectEqual(@as(u21, 2), cells[0].content.codepoint); @@ -7824,7 +7830,7 @@ test "PageList resize reflow less cols blank lines between no scrollback" { { const offset = it.next().?; const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expect(!rac.row.wrap); try testing.expectEqual(@as(usize, 2), cells.len); try testing.expectEqual(@as(u21, 'A'), cells[0].content.codepoint); @@ -7832,13 +7838,13 @@ test "PageList resize reflow less cols blank lines between no scrollback" { { const offset = it.next().?; const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expectEqual(@as(u21, 0), cells[0].content.codepoint); } { const offset = it.next().?; const rac = offset.rowAndCell(); - const cells = offset.page.data.getCells(rac.row); + const cells = offset.node.data.getCells(rac.row); try testing.expect(!rac.row.wrap); try testing.expectEqual(@as(usize, 2), cells.len); try testing.expectEqual(@as(u21, 'C'), cells[0].content.codepoint); @@ -7931,8 +7937,8 @@ test "PageList resize reflow less cols copy style" { const style_id = rac.cell.style_id; try testing.expect(style_id != 0); - const style = offset.page.data.styles.get( - offset.page.data.memory, + const style = offset.node.data.styles.get( + offset.node.data.memory, style_id, ); try testing.expect(style.flags.bold); diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index e9d2fccd8..4f3fe270e 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -203,7 +203,7 @@ pub fn init( errdefer pages.deinit(); // Create our tracked pin for the cursor. - const page_pin = try pages.trackPin(.{ .page = pages.pages.first.? }); + const page_pin = try pages.trackPin(.{ .node = pages.pages.first.? }); errdefer pages.untrackPin(page_pin); const page_rac = page_pin.rowAndCell(); @@ -331,7 +331,7 @@ pub fn clonePool( }; } - const page_pin = try pages.trackPin(.{ .page = pages.pages.first.? }); + const page_pin = try pages.trackPin(.{ .node = pages.pages.first.? }); const page_rac = page_pin.rowAndCell(); break :cursor .{ .x = 0, @@ -376,7 +376,7 @@ pub fn clonePool( if (!sel.contains(self, clone_top)) break :sel null; } - break :start try pages.trackPin(.{ .page = pages.pages.first.? }); + break :start try pages.trackPin(.{ .node = pages.pages.first.? }); }; const end_pin = pin_remap.get(ordered.br) orelse end: { @@ -414,21 +414,21 @@ pub fn clonePool( /// cursor page. pub fn adjustCapacity( self: *Screen, - page: *PageList.List.Node, + node: *PageList.List.Node, adjustment: PageList.AdjustCapacity, ) PageList.AdjustCapacityError!*PageList.List.Node { // If the page being modified isn't our cursor page then // this is a quick operation because we have no additional // accounting. - if (page != self.cursor.page_pin.page) { - return try self.pages.adjustCapacity(page, adjustment); + if (node != self.cursor.page_pin.node) { + return try self.pages.adjustCapacity(node, adjustment); } // We're modifying the cursor page. When we adjust the // capacity below it will be short the ref count on our // current style and hyperlink, so we need to init those. - const node = try self.pages.adjustCapacity(page, adjustment); - const new_page = &node.data; + const new_node = try self.pages.adjustCapacity(node, adjustment); + const new_page: *Page = &new_node.data; // All additions below have unreachable catches because when // we adjust cap we should have enough memory to fit the @@ -460,7 +460,7 @@ pub fn adjustCapacity( // So our page row/cell and so on are all off. self.cursorReload(); - return node; + return new_node; } pub fn cursorCellRight(self: *Screen, n: size.CellCountInt) *pagepkg.Cell { @@ -636,13 +636,14 @@ pub fn cursorDownScroll(self: *Screen) !void { // so our cursor is in the correct place we just have to clear // the cells. if (self.pages.rows == 1) { + const page: *Page = &self.cursor.page_pin.node.data; self.clearCells( - &self.cursor.page_pin.page.data, + page, self.cursor.page_row, - self.cursor.page_pin.page.data.getCells(self.cursor.page_row), + page.getCells(self.cursor.page_row), ); - var dirty = self.cursor.page_pin.page.data.dirtyBitSet(); + var dirty = page.dirtyBitSet(); dirty.set(0); } else { // eraseRow will shift everything below it up. @@ -684,7 +685,7 @@ pub fn cursorDownScroll(self: *Screen) !void { // was on was pruned. In this case, grow() moves the pin to // the top-left of the new page. This effectively moves it by // one already, we just need to fix up the x value. - const page_pin = if (old_pin.page == self.cursor.page_pin.page) + const page_pin = if (old_pin.node == self.cursor.page_pin.node) self.cursor.page_pin.down(1).? else reuse: { var pin = self.cursor.page_pin.*; @@ -714,10 +715,11 @@ pub fn cursorDownScroll(self: *Screen) !void { // Clear the new row so it gets our bg color. We only do this // if we have a bg color at all. if (self.cursor.style.bg_color != .none) { + const page: *Page = &page_pin.node.data; self.clearCells( - &page_pin.page.data, + page, self.cursor.page_row, - page_pin.page.data.getCells(self.cursor.page_row), + page.getCells(self.cursor.page_row), ); } } @@ -753,15 +755,15 @@ pub fn cursorScrollAbove(self: *Screen) !void { } else { // In this case, it means grow() didn't allocate a new page. - if (self.cursor.page_pin.page == self.pages.pages.last) { + if (self.cursor.page_pin.node == self.pages.pages.last) { // If we're on the last page we can do a very fast path because // all the rows we need to move around are within a single page. - assert(old_pin.page == self.cursor.page_pin.page); + assert(old_pin.node == self.cursor.page_pin.node); self.cursor.page_pin.* = self.cursor.page_pin.down(1).?; const pin = self.cursor.page_pin; - const page = &self.cursor.page_pin.page.data; + const page: *Page = &self.cursor.page_pin.node.data; // Rotate the rows so that the newly created empty row is at the // beginning. e.g. [ 0 1 2 3 ] in to [ 3 0 1 2 ]. @@ -822,7 +824,7 @@ fn cursorScrollAboveRotate(self: *Screen) !void { // Go through each of the pages following our pin, shift all rows // down by one, and copy the last row of the previous page. var current = self.pages.pages.last.?; - while (current != self.cursor.page_pin.page) : (current = current.prev.?) { + while (current != self.cursor.page_pin.node) : (current = current.prev.?) { const prev = current.prev.?; const prev_page = &prev.data; const cur_page = ¤t.data; @@ -846,7 +848,7 @@ fn cursorScrollAboveRotate(self: *Screen) !void { // Our current is our cursor page, we need to rotate down from // our cursor and clear our row. - assert(current == self.cursor.page_pin.page); + assert(current == self.cursor.page_pin.node); const cur_page = ¤t.data; const cur_rows = cur_page.rows.ptr(cur_page.memory.ptr); fastmem.rotateOnceR(Row, cur_rows[self.cursor.page_pin.y..cur_page.size.rows]); @@ -922,7 +924,7 @@ pub fn cursorCopy(self: *Screen, other: Cursor, opts: struct { // If the other cursor had a hyperlink, add it to ours. if (opts.hyperlink and other.hyperlink_id != 0) { // Get the hyperlink from the other cursor's page. - const other_page = &other.page_pin.page.data; + const other_page = &other.page_pin.node.data; const other_link = other_page.hyperlink_set.get(other_page.memory, other.hyperlink_id); const uri = other_link.uri.offset.ptr(other_page.memory)[0..other_link.uri.len]; @@ -957,7 +959,7 @@ fn cursorChangePin(self: *Screen, new: Pin) void { // If our pin is on the same page, then we can just update the pin. // We don't need to migrate any state. - if (self.cursor.page_pin.page == new.page) { + if (self.cursor.page_pin.node == new.node) { self.cursor.page_pin.* = new; return; } @@ -974,7 +976,7 @@ fn cursorChangePin(self: *Screen, new: Pin) void { // If we have a hyperlink then we need to release it from the old page. if (self.cursor.hyperlink != null) { - const old_page = &self.cursor.page_pin.page.data; + const old_page: *Page = &self.cursor.page_pin.node.data; old_page.hyperlink_set.release(old_page.memory, self.cursor.hyperlink_id); } @@ -1049,12 +1051,12 @@ pub fn cursorResetWrap(self: *Screen) void { // If the last cell in the row is a spacer head we need to clear it. const cells = self.cursor.page_pin.cells(.all); - const cell = cells[self.cursor.page_pin.page.data.size.cols - 1]; + const cell = cells[self.cursor.page_pin.node.data.size.cols - 1]; if (cell.wide == .spacer_head) { self.clearCells( - &self.cursor.page_pin.page.data, + &self.cursor.page_pin.node.data, page_row, - cells[self.cursor.page_pin.page.data.size.cols - 1 ..][0..1], + cells[self.cursor.page_pin.node.data.size.cols - 1 ..][0..1], ); } } @@ -1144,22 +1146,22 @@ pub fn clearRows( var it = self.pages.pageIterator(.right_down, tl, bl); while (it.next()) |chunk| { // Mark everything in this chunk as dirty - var dirty = chunk.page.data.dirtyBitSet(); + var dirty = chunk.node.data.dirtyBitSet(); dirty.setRangeValue(.{ .start = chunk.start, .end = chunk.end }, true); for (chunk.rows()) |*row| { const cells_offset = row.cells; - const cells_multi: [*]Cell = row.cells.ptr(chunk.page.data.memory); + const cells_multi: [*]Cell = row.cells.ptr(chunk.node.data.memory); const cells = cells_multi[0..self.pages.cols]; // Clear all cells if (protected) { - self.clearUnprotectedCells(&chunk.page.data, row, cells); + self.clearUnprotectedCells(&chunk.node.data, row, cells); // We need to preserve other row attributes since we only // cleared unprotected cells. row.cells = cells_offset; } else { - self.clearCells(&chunk.page.data, row, cells); + self.clearCells(&chunk.node.data, row, cells); row.* = .{ .cells = cells_offset }; } } @@ -1294,8 +1296,8 @@ pub fn clearPrompt(self: *Screen) void { var clear_it = top.rowIterator(.right_down, null); while (clear_it.next()) |p| { const row = p.rowAndCell().row; - p.page.data.clearCells(row, 0, p.page.data.size.cols); - p.page.data.assertIntegrity(); + p.node.data.clearCells(row, 0, p.node.data.size.cols); + p.node.data.assertIntegrity(); } } } @@ -1336,12 +1338,12 @@ pub fn splitCellBoundary( self: *Screen, x: size.CellCountInt, ) void { - const page = &self.cursor.page_pin.page.data; + const page = &self.cursor.page_pin.node.data; page.pauseIntegrityChecks(true); defer page.pauseIntegrityChecks(false); - const cols = self.cursor.page_pin.page.data.size.cols; + const cols = self.cursor.page_pin.node.data.size.cols; // `x` may be up to an INCLUDING `cols`, since that signifies splitting // the boundary to the right of the final cell in the row. @@ -1385,12 +1387,12 @@ pub fn splitCellBoundary( if (self.cursor.page_pin.up(1)) |p_row| { const p_rac = p_row.rowAndCell(); const p_cells = p_row.cells(.all); - const p_cell = p_cells[p_row.page.data.size.cols - 1]; + const p_cell = p_cells[p_row.node.data.size.cols - 1]; if (p_cell.wide == .spacer_head) { self.clearCells( - &p_row.page.data, + &p_row.node.data, p_rac.row, - p_cells[p_row.page.data.size.cols - 1 ..][0..1], + p_cells[p_row.node.data.size.cols - 1 ..][0..1], ); } } @@ -1506,7 +1508,7 @@ fn resizeInternal( if (self.cursor.hyperlink_id != 0) { // Note we do NOT use endHyperlink because we want to keep // our allocated self.cursor.hyperlink valid. - var page = &self.cursor.page_pin.page.data; + var page = &self.cursor.page_pin.node.data; page.hyperlink_set.release(page.memory, self.cursor.hyperlink_id); self.cursor.hyperlink_id = 0; self.cursor.hyperlink = null; @@ -1701,7 +1703,7 @@ pub fn setAttribute(self: *Screen, attr: sgr.Attribute) !void { /// Call this whenever you manually change the cursor style. pub fn manualStyleUpdate(self: *Screen) !void { - var page = &self.cursor.page_pin.page.data; + var page: *Page = &self.cursor.page_pin.node.data; // std.log.warn("active styles={}", .{page.styles.count()}); @@ -1730,7 +1732,7 @@ pub fn manualStyleUpdate(self: *Screen) !void { // and double the style capacity for it if it was // full. const node = try self.adjustCapacity( - self.cursor.page_pin.page, + self.cursor.page_pin.node, switch (err) { error.OutOfMemory => .{ .styles = page.capacity.styles * 2 }, error.NeedsRehash => .{}, @@ -1749,8 +1751,8 @@ pub fn manualStyleUpdate(self: *Screen) !void { /// Append a grapheme to the given cell within the current cursor row. pub fn appendGrapheme(self: *Screen, cell: *Cell, cp: u21) !void { - defer self.cursor.page_pin.page.data.assertIntegrity(); - self.cursor.page_pin.page.data.appendGrapheme( + defer self.cursor.page_pin.node.data.assertIntegrity(); + self.cursor.page_pin.node.data.appendGrapheme( self.cursor.page_row, cell, cp, @@ -1768,7 +1770,7 @@ pub fn appendGrapheme(self: *Screen, cell: *Cell, cp: u21) !void { // Adjust our capacity. This will update our cursor page pin and // force us to reload. - const original_node = self.cursor.page_pin.page; + const original_node = self.cursor.page_pin.node; const new_bytes = original_node.data.capacity.grapheme_bytes * 2; _ = try self.adjustCapacity( original_node, @@ -1783,7 +1785,7 @@ pub fn appendGrapheme(self: *Screen, cell: *Cell, cp: u21) !void { .gt => self.cursorCellRight(@intCast(cell_idx - self.cursor.x)), }; - try self.cursor.page_pin.page.data.appendGrapheme( + try self.cursor.page_pin.node.data.appendGrapheme( self.cursor.page_row, reloaded_cell, cp, @@ -1827,19 +1829,19 @@ pub fn startHyperlink( // strings table is out of memory, adjust it up error.StringsOutOfMemory => _ = try self.adjustCapacity( - self.cursor.page_pin.page, - .{ .string_bytes = self.cursor.page_pin.page.data.capacity.string_bytes * 2 }, + self.cursor.page_pin.node, + .{ .string_bytes = self.cursor.page_pin.node.data.capacity.string_bytes * 2 }, ), // hyperlink set is out of memory, adjust it up error.SetOutOfMemory => _ = try self.adjustCapacity( - self.cursor.page_pin.page, - .{ .hyperlink_bytes = self.cursor.page_pin.page.data.capacity.hyperlink_bytes * 2 }, + self.cursor.page_pin.node, + .{ .hyperlink_bytes = self.cursor.page_pin.node.data.capacity.hyperlink_bytes * 2 }, ), // hyperlink set is too full, rehash it error.SetNeedsRehash => _ = try self.adjustCapacity( - self.cursor.page_pin.page, + self.cursor.page_pin.node, .{}, ), } @@ -1866,7 +1868,7 @@ fn startHyperlinkOnce( errdefer link.deinit(self.alloc); // Insert the hyperlink into page memory - var page = &self.cursor.page_pin.page.data; + var page = &self.cursor.page_pin.node.data; const id: hyperlink.Id = try page.insertHyperlink(link.*); // Save it all @@ -1893,7 +1895,7 @@ pub fn endHyperlink(self: *Screen) void { // how RefCountedSet works). This causes some memory fragmentation but // is fine because if it is ever pruned the context deleted callback // will be called. - var page = &self.cursor.page_pin.page.data; + var page: *Page = &self.cursor.page_pin.node.data; page.hyperlink_set.release(page.memory, self.cursor.hyperlink_id); self.cursor.hyperlink.?.deinit(self.alloc); self.alloc.destroy(self.cursor.hyperlink.?); @@ -1905,7 +1907,7 @@ pub fn endHyperlink(self: *Screen) void { pub fn cursorSetHyperlink(self: *Screen) !void { assert(self.cursor.hyperlink_id != 0); - var page = &self.cursor.page_pin.page.data; + var page = &self.cursor.page_pin.node.data; if (page.setHyperlink( self.cursor.page_row, self.cursor.page_cell, @@ -1918,7 +1920,7 @@ pub fn cursorSetHyperlink(self: *Screen) !void { // hyperlink_map is out of space, realloc the page to be larger error.HyperlinkMapOutOfMemory => { _ = try self.adjustCapacity( - self.cursor.page_pin.page, + self.cursor.page_pin.node, .{ .hyperlink_bytes = page.capacity.hyperlink_bytes * 2 }, ); @@ -2021,7 +2023,7 @@ pub fn selectionString(self: *Screen, alloc: Allocator, opts: SelectionString) ! while (page_it.next()) |chunk| { const rows = chunk.rows(); for (rows, chunk.start..) |row, y| { - const cells_ptr = row.cells.ptr(chunk.page.data.memory); + const cells_ptr = row.cells.ptr(chunk.node.data.memory); const start_x = if (row_count == 0 or sel_ordered.rectangle) sel_start.x @@ -2048,20 +2050,20 @@ pub fn selectionString(self: *Screen, alloc: Allocator, opts: SelectionString) ! try strbuilder.appendSlice(buf[0..encode_len]); if (mapbuilder) |*b| { for (0..encode_len) |_| try b.append(.{ - .page = chunk.page, + .node = chunk.node, .y = @intCast(y), .x = @intCast(x), }); } } if (cell.hasGrapheme()) { - const cps = chunk.page.data.lookupGrapheme(cell).?; + const cps = chunk.node.data.lookupGrapheme(cell).?; for (cps) |cp| { const encode_len = try std.unicode.utf8Encode(cp, &buf); try strbuilder.appendSlice(buf[0..encode_len]); if (mapbuilder) |*b| { for (0..encode_len) |_| try b.append(.{ - .page = chunk.page, + .node = chunk.node, .y = @intCast(y), .x = @intCast(x), }); @@ -2070,16 +2072,16 @@ pub fn selectionString(self: *Screen, alloc: Allocator, opts: SelectionString) ! } } - const is_final_row = chunk.page == sel_end.page and y == sel_end.y; + const is_final_row = chunk.node == sel_end.node and y == sel_end.y; if (!is_final_row and (!row.wrap or sel_ordered.rectangle)) { try strbuilder.append('\n'); if (mapbuilder) |*b| try b.append(.{ - .page = chunk.page, + .node = chunk.node, .y = @intCast(y), - .x = chunk.page.data.size.cols - 1, + .x = chunk.node.data.size.cols - 1, }); } @@ -2209,14 +2211,14 @@ pub fn selectLine(self: *const Screen, opts: SelectLine) ?Selection { const current_prompt = row.semantic_prompt.promptOrInput(); if (current_prompt != v) { var prev = p.up(1).?; - prev.x = p.page.data.size.cols - 1; + prev.x = p.node.data.size.cols - 1; break :end_pin prev; } } if (!row.wrap) { var copy = p; - copy.x = p.page.data.size.cols - 1; + copy.x = p.node.data.size.cols - 1; break :end_pin copy; } } @@ -2417,7 +2419,7 @@ pub fn selectWord(self: *Screen, pin: Pin) ?Selection { // If we are going to the next row and it isn't wrapped, we // return the previous. - if (p.x == p.page.data.size.cols - 1 and !rac.row.wrap) { + if (p.x == p.node.data.size.cols - 1 and !rac.row.wrap) { break :end p; } @@ -2437,7 +2439,7 @@ pub fn selectWord(self: *Screen, pin: Pin) ?Selection { // If we are going to the next row and it isn't wrapped, we // return the previous. - if (p.x == p.page.data.size.cols - 1 and !rac.row.wrap) { + if (p.x == p.node.data.size.cols - 1 and !rac.row.wrap) { break :start prev; } @@ -2491,7 +2493,7 @@ pub fn selectOutput(self: *Screen, pin: Pin) ?Selection { switch (row.semantic_prompt) { .input, .prompt_continuation, .prompt => { var copy = it_prev; - copy.x = it_prev.page.data.size.cols - 1; + copy.x = it_prev.node.data.size.cols - 1; break :boundary copy; }, else => {}, @@ -2504,10 +2506,10 @@ pub fn selectOutput(self: *Screen, pin: Pin) ?Selection { it = it_prev.rowIterator(.left_up, null); while (it.next()) |p| { const row = p.rowAndCell().row; - const cells = p.page.data.getCells(row); + const cells = p.node.data.getCells(row); if (Cell.hasTextAny(cells)) { var copy = p; - copy.x = p.page.data.size.cols - 1; + copy.x = p.node.data.size.cols - 1; break :boundary copy; } } @@ -2598,7 +2600,7 @@ pub fn selectPrompt(self: *Screen, pin: Pin) ?Selection { const end: Pin = end: { var it = pin.rowIterator(.right_down, null); var it_prev = it.next().?; - it_prev.x = it_prev.page.data.size.cols - 1; + it_prev.x = it_prev.node.data.size.cols - 1; while (it.next()) |p| { const row = p.rowAndCell().row; switch (row.semantic_prompt) { @@ -2610,7 +2612,7 @@ pub fn selectPrompt(self: *Screen, pin: Pin) ?Selection { } it_prev = p; - it_prev.x = it_prev.page.data.size.cols - 1; + it_prev.x = it_prev.node.data.size.cols - 1; } break :end it_prev; @@ -2764,7 +2766,7 @@ pub fn dumpString( .codepoint_grapheme => { try writer.print("{u}", .{cell.content.codepoint}); - const cps = row_offset.page.data.lookupGrapheme(cell).?; + const cps = row_offset.node.data.lookupGrapheme(cell).?; for (cps) |cp| { try writer.print("{u}", .{cp}); } @@ -2843,7 +2845,7 @@ pub fn testWriteString(self: *Screen, text: []const u8) !void { break :cell cell; }; - try self.cursor.page_pin.page.data.appendGrapheme( + try self.cursor.page_pin.node.data.appendGrapheme( self.cursor.page_row, cell, c, @@ -2872,7 +2874,7 @@ pub fn testWriteString(self: *Screen, text: []const u8) !void { // If we have a ref-counted style, increase. if (self.cursor.style_id != style.default_id) { - const page = self.cursor.page_pin.page.data; + const page = self.cursor.page_pin.node.data; page.styles.use(page.memory, self.cursor.style_id); self.cursor.page_row.styled = true; } @@ -2914,7 +2916,7 @@ pub fn testWriteString(self: *Screen, text: []const u8) !void { // If we have a ref-counted style, increase twice. if (self.cursor.style_id != style.default_id) { - const page = self.cursor.page_pin.page.data; + const page = self.cursor.page_pin.node.data; page.styles.use(page.memory, self.cursor.style_id); page.styles.use(page.memory, self.cursor.style_id); self.cursor.page_row.styled = true; @@ -3054,7 +3056,7 @@ test "Screen cursorCopy style deref" { var s2 = try Screen.init(alloc, 10, 10, 0); defer s2.deinit(); - const page = &s2.cursor.page_pin.page.data; + const page = &s2.cursor.page_pin.node.data; // Bold should create our style try s2.setAttribute(.{ .bold = {} }); @@ -3110,12 +3112,12 @@ test "Screen cursorCopy style deref new page" { // +-------------+ // This should be PAGE 1 - const page = &s2.cursor.page_pin.page.data; + const page = &s2.cursor.page_pin.node.data; // It should be the last page in the list. try testing.expectEqual(&s2.pages.pages.last.?.data, page); // It should have a previous page. - try testing.expect(s2.cursor.page_pin.page.prev != null); + try testing.expect(s2.cursor.page_pin.node.prev != null); // The cursor should be at 2, 9 try testing.expect(s2.cursor.x == 2); @@ -3132,7 +3134,7 @@ test "Screen cursorCopy style deref new page" { try testing.expect(!s2.cursor.style.flags.bold); try testing.expectEqual(@as(usize, 0), page.styles.count()); // The page after the page the cursor is now in should be page 1. - try testing.expectEqual(page, &s2.cursor.page_pin.page.next.?.data); + try testing.expectEqual(page, &s2.cursor.page_pin.node.next.?.data); // The cursor should be at 0, 0 try testing.expect(s2.cursor.x == 0); try testing.expect(s2.cursor.y == 0); @@ -3148,7 +3150,7 @@ test "Screen cursorCopy style copy" { var s2 = try Screen.init(alloc, 10, 10, 0); defer s2.deinit(); - const page = &s2.cursor.page_pin.page.data; + const page = &s2.cursor.page_pin.node.data; try s2.cursorCopy(s.cursor, .{}); try testing.expect(s2.cursor.style.flags.bold); try testing.expectEqual(@as(usize, 1), page.styles.count()); @@ -3163,7 +3165,7 @@ test "Screen cursorCopy hyperlink deref" { var s2 = try Screen.init(alloc, 10, 10, 0); defer s2.deinit(); - const page = &s2.cursor.page_pin.page.data; + const page = &s2.cursor.page_pin.node.data; // Create a hyperlink for the cursor. try s2.startHyperlink("https://example.com/", null); @@ -3219,12 +3221,12 @@ test "Screen cursorCopy hyperlink deref new page" { // +-------------+ // This should be PAGE 1 - const page = &s2.cursor.page_pin.page.data; + const page = &s2.cursor.page_pin.node.data; // It should be the last page in the list. try testing.expectEqual(&s2.pages.pages.last.?.data, page); // It should have a previous page. - try testing.expect(s2.cursor.page_pin.page.prev != null); + try testing.expect(s2.cursor.page_pin.node.prev != null); // The cursor should be at 2, 9 try testing.expect(s2.cursor.x == 2); @@ -3241,7 +3243,7 @@ test "Screen cursorCopy hyperlink deref new page" { try testing.expectEqual(@as(usize, 0), page.hyperlink_set.count()); try testing.expect(s2.cursor.hyperlink_id == 0); // The page after the page the cursor is now in should be page 1. - try testing.expectEqual(page, &s2.cursor.page_pin.page.next.?.data); + try testing.expectEqual(page, &s2.cursor.page_pin.node.next.?.data); // The cursor should be at 0, 0 try testing.expect(s2.cursor.x == 0); try testing.expect(s2.cursor.y == 0); @@ -3256,12 +3258,12 @@ test "Screen cursorCopy hyperlink copy" { // Create a hyperlink for the cursor. try s.startHyperlink("https://example.com/", null); - try testing.expectEqual(@as(usize, 1), s.cursor.page_pin.page.data.hyperlink_set.count()); + try testing.expectEqual(@as(usize, 1), s.cursor.page_pin.node.data.hyperlink_set.count()); try testing.expect(s.cursor.hyperlink_id != 0); var s2 = try Screen.init(alloc, 10, 10, 0); defer s2.deinit(); - const page = &s2.cursor.page_pin.page.data; + const page = &s2.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 0), page.hyperlink_set.count()); try testing.expect(s2.cursor.hyperlink_id == 0); @@ -3281,12 +3283,12 @@ test "Screen cursorCopy hyperlink copy disabled" { // Create a hyperlink for the cursor. try s.startHyperlink("https://example.com/", null); - try testing.expectEqual(@as(usize, 1), s.cursor.page_pin.page.data.hyperlink_set.count()); + try testing.expectEqual(@as(usize, 1), s.cursor.page_pin.node.data.hyperlink_set.count()); try testing.expect(s.cursor.hyperlink_id != 0); var s2 = try Screen.init(alloc, 10, 10, 0); defer s2.deinit(); - const page = &s2.cursor.page_pin.page.data; + const page = &s2.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 0), page.hyperlink_set.count()); try testing.expect(s2.cursor.hyperlink_id == 0); @@ -3303,7 +3305,7 @@ test "Screen style basics" { var s = try Screen.init(alloc, 80, 24, 1000); defer s.deinit(); - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 0), page.styles.count()); // Set a new style @@ -3325,7 +3327,7 @@ test "Screen style reset to default" { var s = try Screen.init(alloc, 80, 24, 1000); defer s.deinit(); - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 0), page.styles.count()); // Set a new style @@ -3345,7 +3347,7 @@ test "Screen style reset with unset" { var s = try Screen.init(alloc, 80, 24, 1000); defer s.deinit(); - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 0), page.styles.count()); // Set a new style @@ -3402,7 +3404,7 @@ test "Screen clearRows active styled line" { try s.setAttribute(.{ .unset = {} }); // We should have one style - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.styles.count()); s.clearRows(.{ .active = .{} }, null, false); @@ -3628,21 +3630,21 @@ test "Screen: cursorDown across pages preserves style" { // assertion fails then the bug is in the test: we should be scrolling // above enough for a new page to show up. { - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; try testing.expect(start_page != page); } // Scroll back to the previous page s.cursorUp(1); { - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; try testing.expect(start_page == page); } // Go back up, set a style try s.setAttribute(.{ .bold = {} }); { - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; const styleval = page.styles.get( page.memory, s.cursor.style_id, @@ -3653,7 +3655,7 @@ test "Screen: cursorDown across pages preserves style" { // Go back down into the next page and we should have that style s.cursorDown(1); { - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; const styleval = page.styles.get( page.memory, s.cursor.style_id, @@ -3678,14 +3680,14 @@ test "Screen: cursorUp across pages preserves style" { // assertion fails then the bug is in the test: we should be scrolling // above enough for a new page to show up. { - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; try testing.expect(start_page != page); } // Go back up, set a style try s.setAttribute(.{ .bold = {} }); { - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; const styleval = page.styles.get( page.memory, s.cursor.style_id, @@ -3696,7 +3698,7 @@ test "Screen: cursorUp across pages preserves style" { // Go back down into the prev page and we should have that style s.cursorUp(1); { - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; try testing.expect(start_page == page); const styleval = page.styles.get( @@ -3723,14 +3725,14 @@ test "Screen: cursorAbsolute across pages preserves style" { // assertion fails then the bug is in the test: we should be scrolling // above enough for a new page to show up. { - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; try testing.expect(start_page != page); } // Go back up, set a style try s.setAttribute(.{ .bold = {} }); { - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; const styleval = page.styles.get( page.memory, s.cursor.style_id, @@ -3741,7 +3743,7 @@ test "Screen: cursorAbsolute across pages preserves style" { // Go back down into the prev page and we should have that style s.cursorAbsolute(1, 1); { - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; try testing.expect(start_page == page); const styleval = page.styles.get( @@ -4345,7 +4347,7 @@ test "Screen: scroll above same page but cursor on previous page" { s.pages.clearDirty(); // Ensure we're still on the first page and have a second - try testing.expect(s.cursor.page_pin.page == s.pages.pages.first.?); + try testing.expect(s.cursor.page_pin.node == s.pages.pages.first.?); try testing.expect(s.pages.pages.first.?.next != null); // At this point: @@ -4403,7 +4405,7 @@ test "Screen: scroll above same page but cursor on previous page last row" { s.pages.clearDirty(); // Ensure we're still on the first page and have a second - try testing.expect(s.cursor.page_pin.page == s.pages.pages.first.?); + try testing.expect(s.cursor.page_pin.node == s.pages.pages.first.?); try testing.expect(s.pages.pages.first.?.next != null); // At this point: @@ -4478,7 +4480,7 @@ test "Screen: scroll above creates new page" { s.pages.clearDirty(); // Ensure we're still on the first page - try testing.expect(s.cursor.page_pin.page == s.pages.pages.first.?); + try testing.expect(s.cursor.page_pin.node == s.pages.pages.first.?); try s.cursorScrollAbove(); { @@ -8150,7 +8152,7 @@ test "Screen: selectionString with zero width joiner" { const cell = pin.rowAndCell().cell; try testing.expectEqual(@as(u21, 0x1F468), cell.content.codepoint); try testing.expectEqual(Cell.Wide.wide, cell.wide); - const cps = pin.page.data.lookupGrapheme(cell).?; + const cps = pin.node.data.lookupGrapheme(cell).?; try testing.expectEqual(@as(usize, 1), cps.len); } @@ -8381,21 +8383,21 @@ test "Screen: hyperlink start/end" { defer s.deinit(); try testing.expect(s.cursor.hyperlink_id == 0); { - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; try testing.expectEqual(0, page.hyperlink_set.count()); } try s.startHyperlink("http://example.com", null); try testing.expect(s.cursor.hyperlink_id != 0); { - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; try testing.expectEqual(1, page.hyperlink_set.count()); } s.endHyperlink(); try testing.expect(s.cursor.hyperlink_id == 0); { - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; try testing.expectEqual(0, page.hyperlink_set.count()); } } @@ -8409,7 +8411,7 @@ test "Screen: hyperlink reuse" { try testing.expect(s.cursor.hyperlink_id == 0); { - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; try testing.expectEqual(0, page.hyperlink_set.count()); } @@ -8422,14 +8424,14 @@ test "Screen: hyperlink reuse" { try s.startHyperlink("http://example.com", null); try testing.expectEqual(id, s.cursor.hyperlink_id); { - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; try testing.expectEqual(1, page.hyperlink_set.count()); } s.endHyperlink(); try testing.expect(s.cursor.hyperlink_id == 0); { - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; try testing.expectEqual(0, page.hyperlink_set.count()); } } @@ -8449,7 +8451,7 @@ test "Screen: hyperlink cursor state on resize" { try s.startHyperlink("http://example.com", null); try testing.expect(s.cursor.hyperlink_id != 0); { - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; try testing.expectEqual(1, page.hyperlink_set.count()); } @@ -8457,14 +8459,14 @@ test "Screen: hyperlink cursor state on resize" { try s.resize(10, 10); try testing.expect(s.cursor.hyperlink_id != 0); { - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; try testing.expectEqual(1, page.hyperlink_set.count()); } s.endHyperlink(); try testing.expect(s.cursor.hyperlink_id == 0); { - const page = &s.cursor.page_pin.page.data; + const page = &s.cursor.page_pin.node.data; try testing.expectEqual(0, page.hyperlink_set.count()); } } @@ -8489,8 +8491,8 @@ test "Screen: adjustCapacity cursor style ref count" { // This forces the page to change. _ = try s.adjustCapacity( - s.cursor.page_pin.page, - .{ .grapheme_bytes = s.cursor.page_pin.page.data.capacity.grapheme_bytes * 2 }, + s.cursor.page_pin.node, + .{ .grapheme_bytes = s.cursor.page_pin.node.data.capacity.grapheme_bytes * 2 }, ); // Our ref counts should still be the same diff --git a/src/terminal/Selection.zig b/src/terminal/Selection.zig index 452643f92..6fdb921e7 100644 --- a/src/terminal/Selection.zig +++ b/src/terminal/Selection.zig @@ -372,7 +372,7 @@ pub fn adjust( var current = end_pin.*; while (current.down(1)) |next| : (current = next) { const rac = next.rowAndCell(); - const cells = next.page.data.getCells(rac.row); + const cells = next.node.data.getCells(rac.row); if (page.Cell.hasTextAny(cells)) { end_pin.* = next; break; @@ -434,7 +434,7 @@ pub fn adjust( ); while (it.next()) |next| { const rac = next.rowAndCell(); - const cells = next.page.data.getCells(rac.row); + const cells = next.node.data.getCells(rac.row); if (page.Cell.hasTextAny(cells)) { end_pin.* = next; end_pin.x = @intCast(cells.len - 1); @@ -445,7 +445,7 @@ pub fn adjust( .beginning_of_line => end_pin.x = 0, - .end_of_line => end_pin.x = end_pin.page.data.size.cols - 1, + .end_of_line => end_pin.x = end_pin.node.data.size.cols - 1, } } diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index a2bf6d50e..f5784b6ab 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -313,7 +313,7 @@ pub fn print(self: *Terminal, c: u21) !void { var state: unicode.GraphemeBreakState = .{}; var cp1: u21 = prev.cell.content.codepoint; if (prev.cell.hasGrapheme()) { - const cps = self.screen.cursor.page_pin.page.data.lookupGrapheme(prev.cell).?; + const cps = self.screen.cursor.page_pin.node.data.lookupGrapheme(prev.cell).?; for (cps) |cp2| { // log.debug("cp1={x} cp2={x}", .{ cp1, cp2 }); assert(!unicode.graphemeBreak(cp1, cp2, &state)); @@ -567,7 +567,7 @@ fn printCell( const spacer_cell = self.screen.cursorCellRight(1); self.screen.clearCells( - &self.screen.cursor.page_pin.page.data, + &self.screen.cursor.page_pin.node.data, self.screen.cursor.page_row, spacer_cell[0..1], ); @@ -588,7 +588,7 @@ fn printCell( const wide_cell = self.screen.cursorCellLeft(1); self.screen.clearCells( - &self.screen.cursor.page_pin.page.data, + &self.screen.cursor.page_pin.node.data, self.screen.cursor.page_row, wide_cell[0..1], ); @@ -607,7 +607,7 @@ fn printCell( // If the prior value had graphemes, clear those if (cell.hasGrapheme()) { - self.screen.cursor.page_pin.page.data.clearGrapheme( + self.screen.cursor.page_pin.node.data.clearGrapheme( self.screen.cursor.page_row, cell, ); @@ -617,7 +617,7 @@ fn printCell( // cell's new style will be different after writing. const style_changed = cell.style_id != self.screen.cursor.style_id; if (style_changed) { - var page = &self.screen.cursor.page_pin.page.data; + var page = &self.screen.cursor.page_pin.node.data; // Release the old style. if (cell.style_id != style.default_id) { @@ -639,7 +639,7 @@ fn printCell( }; if (style_changed) { - var page = &self.screen.cursor.page_pin.page.data; + var page = &self.screen.cursor.page_pin.node.data; // Use the new style. if (cell.style_id != style.default_id) { @@ -664,7 +664,7 @@ fn printCell( }; } else if (had_hyperlink) { // If the previous cell had a hyperlink then we need to clear it. - var page = &self.screen.cursor.page_pin.page.data; + var page = &self.screen.cursor.page_pin.node.data; page.clearHyperlink(self.screen.cursor.page_row, cell); } } @@ -1500,8 +1500,8 @@ pub fn insertLines(self: *Terminal, count: usize) void { const off_rac = off_p.rowAndCell(); const off_row: *Row = off_rac.row; - self.rowWillBeShifted(&cur_p.page.data, cur_row); - self.rowWillBeShifted(&off_p.page.data, off_row); + self.rowWillBeShifted(&cur_p.node.data, cur_row); + self.rowWillBeShifted(&off_p.node.data, off_row); // If our scrolling region is full width, then we unset wrap. if (!left_right) { @@ -1518,19 +1518,19 @@ pub fn insertLines(self: *Terminal, count: usize) void { // If our page doesn't match, then we need to do a copy from // one page to another. This is the slow path. - if (src_p.page != dst_p.page) { - dst_p.page.data.clonePartialRowFrom( - &src_p.page.data, + if (src_p.node != dst_p.node) { + dst_p.node.data.clonePartialRowFrom( + &src_p.node.data, dst_row, src_row, self.scrolling_region.left, self.scrolling_region.right + 1, ) catch |err| { - const cap = dst_p.page.data.capacity; + const cap = dst_p.node.data.capacity; // Adjust our page capacity to make // room for we didn't have space for _ = self.screen.adjustCapacity( - dst_p.page, + dst_p.node, switch (err) { // Rehash the sets error.StyleSetNeedsRehash, @@ -1589,11 +1589,11 @@ pub fn insertLines(self: *Terminal, count: usize) void { src_row.* = dst; // Ensure what we did didn't corrupt the page - cur_p.page.data.assertIntegrity(); + cur_p.node.data.assertIntegrity(); } else { // Left/right scroll margins we have to // copy cells, which is much slower... - const page = &cur_p.page.data; + const page = &cur_p.node.data; page.moveCells( src_row, self.scrolling_region.left, @@ -1605,7 +1605,7 @@ pub fn insertLines(self: *Terminal, count: usize) void { } } else { // Clear the cells for this row, it has been shifted. - const page = &cur_p.page.data; + const page = &cur_p.node.data; const cells = page.getCells(cur_row); self.screen.clearCells( page, @@ -1698,8 +1698,8 @@ pub fn deleteLines(self: *Terminal, count: usize) void { const off_rac = off_p.rowAndCell(); const off_row: *Row = off_rac.row; - self.rowWillBeShifted(&cur_p.page.data, cur_row); - self.rowWillBeShifted(&off_p.page.data, off_row); + self.rowWillBeShifted(&cur_p.node.data, cur_row); + self.rowWillBeShifted(&off_p.node.data, off_row); // If our scrolling region is full width, then we unset wrap. if (!left_right) { @@ -1716,19 +1716,19 @@ pub fn deleteLines(self: *Terminal, count: usize) void { // If our page doesn't match, then we need to do a copy from // one page to another. This is the slow path. - if (src_p.page != dst_p.page) { - dst_p.page.data.clonePartialRowFrom( - &src_p.page.data, + if (src_p.node != dst_p.node) { + dst_p.node.data.clonePartialRowFrom( + &src_p.node.data, dst_row, src_row, self.scrolling_region.left, self.scrolling_region.right + 1, ) catch |err| { - const cap = dst_p.page.data.capacity; + const cap = dst_p.node.data.capacity; // Adjust our page capacity to make // room for we didn't have space for _ = self.screen.adjustCapacity( - dst_p.page, + dst_p.node, switch (err) { // Rehash the sets error.StyleSetNeedsRehash, @@ -1782,11 +1782,11 @@ pub fn deleteLines(self: *Terminal, count: usize) void { src_row.* = dst; // Ensure what we did didn't corrupt the page - cur_p.page.data.assertIntegrity(); + cur_p.node.data.assertIntegrity(); } else { // Left/right scroll margins we have to // copy cells, which is much slower... - const page = &cur_p.page.data; + const page = &cur_p.node.data; page.moveCells( src_row, self.scrolling_region.left, @@ -1798,7 +1798,7 @@ pub fn deleteLines(self: *Terminal, count: usize) void { } } else { // Clear the cells for this row, it's from out of bounds. - const page = &cur_p.page.data; + const page = &cur_p.node.data; const cells = page.getCells(cur_row); self.screen.clearCells( page, @@ -1843,7 +1843,7 @@ pub fn insertBlanks(self: *Terminal, count: usize) void { // left is just the cursor position but as a multi-pointer const left: [*]Cell = @ptrCast(self.screen.cursor.page_cell); - var page = &self.screen.cursor.page_pin.page.data; + var page = &self.screen.cursor.page_pin.node.data; // If our X is a wide spacer tail then we need to erase the // previous cell too so we don't split a multi-cell character. @@ -1914,7 +1914,7 @@ pub fn deleteChars(self: *Terminal, count_req: usize) void { // left is just the cursor position but as a multi-pointer const left: [*]Cell = @ptrCast(self.screen.cursor.page_cell); - var page = &self.screen.cursor.page_pin.page.data; + var page = &self.screen.cursor.page_pin.node.data; // Remaining cols from our cursor to the right margin. const rem = self.scrolling_region.right - self.screen.cursor.x + 1; @@ -1995,7 +1995,7 @@ pub fn eraseChars(self: *Terminal, count_req: usize) void { // mode was not ISO we also always ignore protection attributes. if (self.screen.protected_mode != .iso) { self.screen.clearCells( - &self.screen.cursor.page_pin.page.data, + &self.screen.cursor.page_pin.node.data, self.screen.cursor.page_row, cells[0..end], ); @@ -2003,7 +2003,7 @@ pub fn eraseChars(self: *Terminal, count_req: usize) void { } self.screen.clearUnprotectedCells( - &self.screen.cursor.page_pin.page.data, + &self.screen.cursor.page_pin.node.data, self.screen.cursor.page_row, cells[0..end], ); @@ -2075,7 +2075,7 @@ pub fn eraseLine( // to fill the entire line. if (!protected) { self.screen.clearCells( - &self.screen.cursor.page_pin.page.data, + &self.screen.cursor.page_pin.node.data, self.screen.cursor.page_row, cells[start..end], ); @@ -2083,7 +2083,7 @@ pub fn eraseLine( } self.screen.clearUnprotectedCells( - &self.screen.cursor.page_pin.page.data, + &self.screen.cursor.page_pin.node.data, self.screen.cursor.page_row, cells[start..end], ); @@ -2257,7 +2257,7 @@ pub fn decaln(self: *Terminal) !void { // Fill with Es by moving the cursor but reset it after. while (true) { - const page = &self.screen.cursor.page_pin.page.data; + const page = &self.screen.cursor.page_pin.node.data; const row = self.screen.cursor.page_row; const cells_multi: [*]Cell = row.cells.ptr(page.memory); const cells = cells_multi[0..page.size.cols]; @@ -2986,7 +2986,7 @@ test "Terminal: print over wide char with bold" { try t.print(0x1F600); // Smiley face // verify we have styles in our style map { - const page = &t.screen.cursor.page_pin.page.data; + const page = &t.screen.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.styles.count()); } @@ -2997,7 +2997,7 @@ test "Terminal: print over wide char with bold" { // verify our style is gone { - const page = &t.screen.cursor.page_pin.page.data; + const page = &t.screen.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 0), page.styles.count()); } @@ -3016,7 +3016,7 @@ test "Terminal: print over wide char with bg color" { try t.print(0x1F600); // Smiley face // verify we have styles in our style map { - const page = &t.screen.cursor.page_pin.page.data; + const page = &t.screen.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.styles.count()); } @@ -3027,7 +3027,7 @@ test "Terminal: print over wide char with bg color" { // verify our style is gone { - const page = &t.screen.cursor.page_pin.page.data; + const page = &t.screen.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 0), page.styles.count()); } @@ -3058,7 +3058,7 @@ test "Terminal: print multicodepoint grapheme, disabled mode 2027" { try testing.expectEqual(@as(u21, 0x1F468), cell.content.codepoint); try testing.expect(cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.wide, cell.wide); - const cps = list_cell.page.data.lookupGrapheme(cell).?; + const cps = list_cell.node.data.lookupGrapheme(cell).?; try testing.expectEqual(@as(usize, 1), cps.len); } { @@ -3067,7 +3067,7 @@ test "Terminal: print multicodepoint grapheme, disabled mode 2027" { try testing.expectEqual(@as(u21, 0), cell.content.codepoint); try testing.expect(!cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.spacer_tail, cell.wide); - try testing.expect(list_cell.page.data.lookupGrapheme(cell) == null); + try testing.expect(list_cell.node.data.lookupGrapheme(cell) == null); } { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 2, .y = 0 } }).?; @@ -3075,7 +3075,7 @@ test "Terminal: print multicodepoint grapheme, disabled mode 2027" { try testing.expectEqual(@as(u21, 0x1F469), cell.content.codepoint); try testing.expect(cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.wide, cell.wide); - const cps = list_cell.page.data.lookupGrapheme(cell).?; + const cps = list_cell.node.data.lookupGrapheme(cell).?; try testing.expectEqual(@as(usize, 1), cps.len); } { @@ -3084,7 +3084,7 @@ test "Terminal: print multicodepoint grapheme, disabled mode 2027" { try testing.expectEqual(@as(u21, 0), cell.content.codepoint); try testing.expect(!cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.spacer_tail, cell.wide); - try testing.expect(list_cell.page.data.lookupGrapheme(cell) == null); + try testing.expect(list_cell.node.data.lookupGrapheme(cell) == null); } { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 4, .y = 0 } }).?; @@ -3092,7 +3092,7 @@ test "Terminal: print multicodepoint grapheme, disabled mode 2027" { try testing.expectEqual(@as(u21, 0x1F467), cell.content.codepoint); try testing.expect(!cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.wide, cell.wide); - try testing.expect(list_cell.page.data.lookupGrapheme(cell) == null); + try testing.expect(list_cell.node.data.lookupGrapheme(cell) == null); } { const list_cell = t.screen.pages.getCell(.{ .screen = .{ .x = 5, .y = 0 } }).?; @@ -3100,7 +3100,7 @@ test "Terminal: print multicodepoint grapheme, disabled mode 2027" { try testing.expectEqual(@as(u21, 0), cell.content.codepoint); try testing.expect(!cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.spacer_tail, cell.wide); - try testing.expect(list_cell.page.data.lookupGrapheme(cell) == null); + try testing.expect(list_cell.node.data.lookupGrapheme(cell) == null); } try testing.expect(t.isDirty(.{ .screen = .{ .x = 0, .y = 0 } })); @@ -3128,7 +3128,7 @@ test "Terminal: VS16 doesn't make character with 2027 disabled" { try testing.expectEqual(@as(u21, 0x2764), cell.content.codepoint); try testing.expect(cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.narrow, cell.wide); - const cps = list_cell.page.data.lookupGrapheme(cell).?; + const cps = list_cell.node.data.lookupGrapheme(cell).?; try testing.expectEqual(@as(usize, 1), cps.len); } } @@ -3221,7 +3221,7 @@ test "Terminal: print multicodepoint grapheme, mode 2027" { try testing.expectEqual(@as(u21, 0x1F468), cell.content.codepoint); try testing.expect(cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.wide, cell.wide); - const cps = list_cell.page.data.lookupGrapheme(cell).?; + const cps = list_cell.node.data.lookupGrapheme(cell).?; try testing.expectEqual(@as(usize, 4), cps.len); } { @@ -3288,7 +3288,7 @@ test "Terminal: VS15 to make narrow character" { try testing.expectEqual(@as(u21, 0x26C8), cell.content.codepoint); try testing.expect(cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.narrow, cell.wide); - const cps = list_cell.page.data.lookupGrapheme(cell).?; + const cps = list_cell.node.data.lookupGrapheme(cell).?; try testing.expectEqual(@as(usize, 1), cps.len); } } @@ -3319,7 +3319,7 @@ test "Terminal: VS16 to make wide character with mode 2027" { try testing.expectEqual(@as(u21, 0x2764), cell.content.codepoint); try testing.expect(cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.wide, cell.wide); - const cps = list_cell.page.data.lookupGrapheme(cell).?; + const cps = list_cell.node.data.lookupGrapheme(cell).?; try testing.expectEqual(@as(usize, 1), cps.len); } } @@ -3350,7 +3350,7 @@ test "Terminal: VS16 repeated with mode 2027" { try testing.expectEqual(@as(u21, 0x2764), cell.content.codepoint); try testing.expect(cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.wide, cell.wide); - const cps = list_cell.page.data.lookupGrapheme(cell).?; + const cps = list_cell.node.data.lookupGrapheme(cell).?; try testing.expectEqual(@as(usize, 1), cps.len); } { @@ -3359,7 +3359,7 @@ test "Terminal: VS16 repeated with mode 2027" { try testing.expectEqual(@as(u21, 0x2764), cell.content.codepoint); try testing.expect(cell.hasGrapheme()); try testing.expectEqual(Cell.Wide.wide, cell.wide); - const cps = list_cell.page.data.lookupGrapheme(cell).?; + const cps = list_cell.node.data.lookupGrapheme(cell).?; try testing.expectEqual(@as(usize, 1), cps.len); } } @@ -3482,7 +3482,7 @@ test "Terminal: overwrite multicodepoint grapheme clears grapheme data" { try testing.expectEqual(@as(usize, 2), t.screen.cursor.x); // We should have one cell with graphemes - const page = &t.screen.cursor.page_pin.page.data; + const page = &t.screen.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.graphemeCount()); // Move back and overwrite wide @@ -3522,7 +3522,7 @@ test "Terminal: overwrite multicodepoint grapheme tail clears grapheme data" { try testing.expectEqual(@as(usize, 2), t.screen.cursor.x); // We should have one cell with graphemes - const page = &t.screen.cursor.page_pin.page.data; + const page = &t.screen.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.graphemeCount()); // Move back and overwrite wide @@ -3971,7 +3971,7 @@ test "Terminal: print with hyperlink" { try testing.expect(row.hyperlink); const cell = list_cell.cell; try testing.expect(cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell).?; + const id = list_cell.node.data.lookupHyperlink(cell).?; try testing.expectEqual(@as(hyperlink.Id, 1), id); } @@ -3998,7 +3998,7 @@ test "Terminal: print over cell with same hyperlink" { try testing.expect(row.hyperlink); const cell = list_cell.cell; try testing.expect(cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell).?; + const id = list_cell.node.data.lookupHyperlink(cell).?; try testing.expectEqual(@as(hyperlink.Id, 1), id); } @@ -4025,7 +4025,7 @@ test "Terminal: print and end hyperlink" { try testing.expect(row.hyperlink); const cell = list_cell.cell; try testing.expect(cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell).?; + const id = list_cell.node.data.lookupHyperlink(cell).?; try testing.expectEqual(@as(hyperlink.Id, 1), id); } for (3..6) |x| { @@ -4060,7 +4060,7 @@ test "Terminal: print and change hyperlink" { } }).?; const cell = list_cell.cell; try testing.expect(cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell).?; + const id = list_cell.node.data.lookupHyperlink(cell).?; try testing.expectEqual(@as(hyperlink.Id, 1), id); } for (3..6) |x| { @@ -4070,7 +4070,7 @@ test "Terminal: print and change hyperlink" { } }).?; const cell = list_cell.cell; try testing.expect(cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell).?; + const id = list_cell.node.data.lookupHyperlink(cell).?; try testing.expectEqual(@as(hyperlink.Id, 2), id); } @@ -4094,7 +4094,7 @@ test "Terminal: overwrite hyperlink" { .x = @intCast(x), .y = 0, } }).?; - const page = &list_cell.page.data; + const page = &list_cell.node.data; const row = list_cell.row; try testing.expect(!row.hyperlink); const cell = list_cell.cell; @@ -4865,7 +4865,7 @@ test "Terminal: insertLines handles style refs" { try t.setAttribute(.{ .unset = {} }); // verify we have styles in our style map - const page = &t.screen.cursor.page_pin.page.data; + const page = &t.screen.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.styles.count()); t.setCursorPos(2, 2); @@ -5233,9 +5233,9 @@ test "Terminal: scrollUp moves hyperlink" { try testing.expect(row.hyperlink); const cell = list_cell.cell; try testing.expect(cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell).?; + const id = list_cell.node.data.lookupHyperlink(cell).?; try testing.expectEqual(@as(hyperlink.Id, 1), id); - const page = &list_cell.page.data; + const page = &list_cell.node.data; try testing.expectEqual(1, page.hyperlink_set.count()); } for (0..3) |x| { @@ -5247,7 +5247,7 @@ test "Terminal: scrollUp moves hyperlink" { try testing.expect(!row.hyperlink); const cell = list_cell.cell; try testing.expect(!cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell); + const id = list_cell.node.data.lookupHyperlink(cell); try testing.expect(id == null); } } @@ -5284,7 +5284,7 @@ test "Terminal: scrollUp clears hyperlink" { try testing.expect(!row.hyperlink); const cell = list_cell.cell; try testing.expect(!cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell); + const id = list_cell.node.data.lookupHyperlink(cell); try testing.expect(id == null); } } @@ -5386,7 +5386,7 @@ test "Terminal: scrollUp left/right scroll region hyperlink" { } }).?; const cell = list_cell.cell; try testing.expect(!cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell); + const id = list_cell.node.data.lookupHyperlink(cell); try testing.expect(id == null); } for (1..4) |x| { @@ -5398,9 +5398,9 @@ test "Terminal: scrollUp left/right scroll region hyperlink" { try testing.expect(row.hyperlink); const cell = list_cell.cell; try testing.expect(cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell).?; + const id = list_cell.node.data.lookupHyperlink(cell).?; try testing.expectEqual(@as(hyperlink.Id, 1), id); - const page = &list_cell.page.data; + const page = &list_cell.node.data; try testing.expectEqual(1, page.hyperlink_set.count()); } for (4..6) |x| { @@ -5410,7 +5410,7 @@ test "Terminal: scrollUp left/right scroll region hyperlink" { } }).?; const cell = list_cell.cell; try testing.expect(!cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell); + const id = list_cell.node.data.lookupHyperlink(cell); try testing.expect(id == null); } } @@ -5426,9 +5426,9 @@ test "Terminal: scrollUp left/right scroll region hyperlink" { try testing.expect(row.hyperlink); const cell = list_cell.cell; try testing.expect(cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell).?; + const id = list_cell.node.data.lookupHyperlink(cell).?; try testing.expectEqual(@as(hyperlink.Id, 1), id); - const page = &list_cell.page.data; + const page = &list_cell.node.data; try testing.expectEqual(1, page.hyperlink_set.count()); } for (1..4) |x| { @@ -5438,7 +5438,7 @@ test "Terminal: scrollUp left/right scroll region hyperlink" { } }).?; const cell = list_cell.cell; try testing.expect(!cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell); + const id = list_cell.node.data.lookupHyperlink(cell); try testing.expect(id == null); } for (4..6) |x| { @@ -5450,9 +5450,9 @@ test "Terminal: scrollUp left/right scroll region hyperlink" { try testing.expect(row.hyperlink); const cell = list_cell.cell; try testing.expect(cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell).?; + const id = list_cell.node.data.lookupHyperlink(cell).?; try testing.expectEqual(@as(hyperlink.Id, 1), id); - const page = &list_cell.page.data; + const page = &list_cell.node.data; try testing.expectEqual(1, page.hyperlink_set.count()); } } @@ -5596,9 +5596,9 @@ test "Terminal: scrollDown hyperlink moves" { try testing.expect(row.hyperlink); const cell = list_cell.cell; try testing.expect(cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell).?; + const id = list_cell.node.data.lookupHyperlink(cell).?; try testing.expectEqual(@as(hyperlink.Id, 1), id); - const page = &list_cell.page.data; + const page = &list_cell.node.data; try testing.expectEqual(1, page.hyperlink_set.count()); } for (0..3) |x| { @@ -5610,7 +5610,7 @@ test "Terminal: scrollDown hyperlink moves" { try testing.expect(!row.hyperlink); const cell = list_cell.cell; try testing.expect(!cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell); + const id = list_cell.node.data.lookupHyperlink(cell); try testing.expect(id == null); } } @@ -5720,9 +5720,9 @@ test "Terminal: scrollDown left/right scroll region hyperlink" { try testing.expect(row.hyperlink); const cell = list_cell.cell; try testing.expect(cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell).?; + const id = list_cell.node.data.lookupHyperlink(cell).?; try testing.expectEqual(@as(hyperlink.Id, 1), id); - const page = &list_cell.page.data; + const page = &list_cell.node.data; try testing.expectEqual(1, page.hyperlink_set.count()); } for (1..4) |x| { @@ -5732,7 +5732,7 @@ test "Terminal: scrollDown left/right scroll region hyperlink" { } }).?; const cell = list_cell.cell; try testing.expect(!cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell); + const id = list_cell.node.data.lookupHyperlink(cell); try testing.expect(id == null); } for (4..6) |x| { @@ -5744,9 +5744,9 @@ test "Terminal: scrollDown left/right scroll region hyperlink" { try testing.expect(row.hyperlink); const cell = list_cell.cell; try testing.expect(cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell).?; + const id = list_cell.node.data.lookupHyperlink(cell).?; try testing.expectEqual(@as(hyperlink.Id, 1), id); - const page = &list_cell.page.data; + const page = &list_cell.node.data; try testing.expectEqual(1, page.hyperlink_set.count()); } } @@ -5760,7 +5760,7 @@ test "Terminal: scrollDown left/right scroll region hyperlink" { } }).?; const cell = list_cell.cell; try testing.expect(!cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell); + const id = list_cell.node.data.lookupHyperlink(cell); try testing.expect(id == null); } for (1..4) |x| { @@ -5772,9 +5772,9 @@ test "Terminal: scrollDown left/right scroll region hyperlink" { try testing.expect(row.hyperlink); const cell = list_cell.cell; try testing.expect(cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell).?; + const id = list_cell.node.data.lookupHyperlink(cell).?; try testing.expectEqual(@as(hyperlink.Id, 1), id); - const page = &list_cell.page.data; + const page = &list_cell.node.data; try testing.expectEqual(1, page.hyperlink_set.count()); } for (4..6) |x| { @@ -5784,7 +5784,7 @@ test "Terminal: scrollDown left/right scroll region hyperlink" { } }).?; const cell = list_cell.cell; try testing.expect(!cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell); + const id = list_cell.node.data.lookupHyperlink(cell); try testing.expect(id == null); } } @@ -6018,7 +6018,7 @@ test "Terminal: eraseChars handles refcounted styles" { try t.print('C'); // verify we have styles in our style map - const page = &t.screen.cursor.page_pin.page.data; + const page = &t.screen.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.styles.count()); t.setCursorPos(1, 1); @@ -6095,7 +6095,7 @@ test "Terminal: eraseChars wide char boundary conditions" { t.setCursorPos(1, 2); t.eraseChars(3); - t.screen.cursor.page_pin.page.data.assertIntegrity(); + t.screen.cursor.page_pin.node.data.assertIntegrity(); { const str = try t.plainString(alloc); @@ -6122,7 +6122,7 @@ test "Terminal: eraseChars wide char wrap boundary conditions" { t.setCursorPos(2, 2); t.eraseChars(3); - t.screen.cursor.page_pin.page.data.assertIntegrity(); + t.screen.cursor.page_pin.node.data.assertIntegrity(); { const str = try t.plainString(alloc); @@ -6425,7 +6425,7 @@ test "Terminal: index scrolling with hyperlink" { try testing.expect(row.hyperlink); const cell = list_cell.cell; try testing.expect(cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell).?; + const id = list_cell.node.data.lookupHyperlink(cell).?; try testing.expectEqual(@as(hyperlink.Id, 1), id); } { @@ -6437,7 +6437,7 @@ test "Terminal: index scrolling with hyperlink" { try testing.expect(!row.hyperlink); const cell = list_cell.cell; try testing.expect(!cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell); + const id = list_cell.node.data.lookupHyperlink(cell); try testing.expect(id == null); } } @@ -6599,7 +6599,7 @@ test "Terminal: index bottom of scroll region with hyperlinks" { try testing.expect(row.hyperlink); const cell = list_cell.cell; try testing.expect(cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell).?; + const id = list_cell.node.data.lookupHyperlink(cell).?; try testing.expectEqual(@as(hyperlink.Id, 1), id); } { @@ -6611,7 +6611,7 @@ test "Terminal: index bottom of scroll region with hyperlinks" { try testing.expect(!row.hyperlink); const cell = list_cell.cell; try testing.expect(!cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell); + const id = list_cell.node.data.lookupHyperlink(cell); try testing.expect(id == null); } } @@ -6648,9 +6648,9 @@ test "Terminal: index bottom of scroll region clear hyperlinks" { try testing.expect(!row.hyperlink); const cell = list_cell.cell; try testing.expect(!cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell); + const id = list_cell.node.data.lookupHyperlink(cell); try testing.expect(id == null); - const page = &list_cell.page.data; + const page = &list_cell.node.data; try testing.expectEqual(0, page.hyperlink_set.count()); } } @@ -7972,7 +7972,7 @@ test "Terminal: bold style" { const cell = list_cell.cell; try testing.expectEqual(@as(u21, 'A'), cell.content.codepoint); try testing.expect(cell.style_id != 0); - const page = &t.screen.cursor.page_pin.page.data; + const page = &t.screen.cursor.page_pin.node.data; try testing.expect(page.styles.refCount(page.memory, t.screen.cursor.style_id) > 1); } } @@ -7996,7 +7996,7 @@ test "Terminal: garbage collect overwritten" { } // verify we have no styles in our style map - const page = &t.screen.cursor.page_pin.page.data; + const page = &t.screen.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 0), page.styles.count()); } @@ -8018,7 +8018,7 @@ test "Terminal: do not garbage collect old styles in use" { } // verify we have no styles in our style map - const page = &t.screen.cursor.page_pin.page.data; + const page = &t.screen.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.styles.count()); } @@ -8390,7 +8390,7 @@ test "Terminal: insertBlanks deleting graphemes" { try t.print(0x1F467); // We should have one cell with graphemes - const page = &t.screen.cursor.page_pin.page.data; + const page = &t.screen.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.graphemeCount()); t.setCursorPos(1, 1); @@ -8426,7 +8426,7 @@ test "Terminal: insertBlanks shift graphemes" { try t.print(0x1F467); // We should have one cell with graphemes - const page = &t.screen.cursor.page_pin.page.data; + const page = &t.screen.cursor.page_pin.node.data; try testing.expectEqual(@as(usize, 1), page.graphemeCount()); t.setCursorPos(1, 1); @@ -8494,7 +8494,7 @@ test "Terminal: insertBlanks shifts hyperlinks" { try testing.expect(row.hyperlink); const cell = list_cell.cell; try testing.expect(cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell).?; + const id = list_cell.node.data.lookupHyperlink(cell).?; try testing.expectEqual(@as(hyperlink.Id, 1), id); } for (0..2) |x| { @@ -8504,7 +8504,7 @@ test "Terminal: insertBlanks shifts hyperlinks" { } }).?; const cell = list_cell.cell; try testing.expect(!cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell); + const id = list_cell.node.data.lookupHyperlink(cell); try testing.expect(id == null); } } @@ -8534,7 +8534,7 @@ test "Terminal: insertBlanks pushes hyperlink off end completely" { try testing.expect(!row.hyperlink); const cell = list_cell.cell; try testing.expect(!cell.hyperlink); - const id = list_cell.page.data.lookupHyperlink(cell); + const id = list_cell.node.data.lookupHyperlink(cell); try testing.expect(id == null); } } @@ -9036,7 +9036,7 @@ test "Terminal: deleteChars wide char boundary conditions" { t.setCursorPos(1, 2); t.deleteChars(3); - t.screen.cursor.page_pin.page.data.assertIntegrity(); + t.screen.cursor.page_pin.node.data.assertIntegrity(); { const str = try t.plainString(alloc); @@ -9088,7 +9088,7 @@ test "Terminal: deleteChars wide char wrap boundary conditions" { t.setCursorPos(2, 2); t.deleteChars(3); - t.screen.cursor.page_pin.page.data.assertIntegrity(); + t.screen.cursor.page_pin.node.data.assertIntegrity(); { const str = try t.plainString(alloc); @@ -9127,7 +9127,7 @@ test "Terminal: deleteChars wide char across right margin" { t.setCursorPos(1, 2); t.deleteChars(1); - t.screen.cursor.page_pin.page.data.assertIntegrity(); + t.screen.cursor.page_pin.node.data.assertIntegrity(); // NOTE: This behavior is slightly inconsistent with xterm. xterm // _visually_ splits the wide character (half the wide character shows From a436bd0af62a4bdc5af14774b955f7b46ccd9deb Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 7 Nov 2024 14:38:54 -0800 Subject: [PATCH 06/27] move datastructures to dedicated "datastruct" package --- src/App.zig | 2 +- src/{ => datastruct}/blocking_queue.zig | 0 src/{ => datastruct}/cache_table.zig | 2 +- src/{ => datastruct}/circ_buf.zig | 2 +- src/{ => datastruct}/lru.zig | 0 src/datastruct/main.zig | 17 +++++++++++++++++ src/{ => datastruct}/segmented_pool.zig | 0 src/font/shaper/Cache.zig | 2 +- src/inspector/key.zig | 2 +- src/inspector/termio.zig | 2 +- src/main_ghostty.zig | 9 +-------- src/os/cf_release_thread.zig | 2 +- src/renderer/Thread.zig | 2 +- src/termio/Exec.zig | 2 +- src/termio/Termio.zig | 1 - src/termio/Thread.zig | 2 +- src/termio/backend.zig | 1 - src/termio/mailbox.zig | 2 +- 18 files changed, 29 insertions(+), 21 deletions(-) rename src/{ => datastruct}/blocking_queue.zig (100%) rename src/{ => datastruct}/cache_table.zig (99%) rename src/{ => datastruct}/circ_buf.zig (99%) rename src/{ => datastruct}/lru.zig (100%) create mode 100644 src/datastruct/main.zig rename src/{ => datastruct}/segmented_pool.zig (100%) diff --git a/src/App.zig b/src/App.zig index cc8277c52..271ba2043 100644 --- a/src/App.zig +++ b/src/App.zig @@ -13,7 +13,7 @@ const Surface = @import("Surface.zig"); const tracy = @import("tracy"); const input = @import("input.zig"); const Config = @import("config.zig").Config; -const BlockingQueue = @import("./blocking_queue.zig").BlockingQueue; +const BlockingQueue = @import("datastruct/main.zig").BlockingQueue; const renderer = @import("renderer.zig"); const font = @import("font/main.zig"); const internal_os = @import("os/main.zig"); diff --git a/src/blocking_queue.zig b/src/datastruct/blocking_queue.zig similarity index 100% rename from src/blocking_queue.zig rename to src/datastruct/blocking_queue.zig diff --git a/src/cache_table.zig b/src/datastruct/cache_table.zig similarity index 99% rename from src/cache_table.zig rename to src/datastruct/cache_table.zig index 7a1a10b2b..40d36cc24 100644 --- a/src/cache_table.zig +++ b/src/datastruct/cache_table.zig @@ -1,4 +1,4 @@ -const fastmem = @import("./fastmem.zig"); +const fastmem = @import("../fastmem.zig"); const std = @import("std"); const assert = std.debug.assert; diff --git a/src/circ_buf.zig b/src/datastruct/circ_buf.zig similarity index 99% rename from src/circ_buf.zig rename to src/datastruct/circ_buf.zig index 4157fd0a4..ccee41801 100644 --- a/src/circ_buf.zig +++ b/src/datastruct/circ_buf.zig @@ -1,7 +1,7 @@ const std = @import("std"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; -const fastmem = @import("fastmem.zig"); +const fastmem = @import("../fastmem.zig"); /// Returns a circular buffer containing type T. pub fn CircBuf(comptime T: type, comptime default: T) type { diff --git a/src/lru.zig b/src/datastruct/lru.zig similarity index 100% rename from src/lru.zig rename to src/datastruct/lru.zig diff --git a/src/datastruct/main.zig b/src/datastruct/main.zig new file mode 100644 index 000000000..70a7ce97f --- /dev/null +++ b/src/datastruct/main.zig @@ -0,0 +1,17 @@ +//! The datastruct package contains data structures or anything closely +//! related to data structures. + +const blocking_queue = @import("blocking_queue.zig"); +const cache_table = @import("cache_table.zig"); +const circ_buf = @import("circ_buf.zig"); +const segmented_pool = @import("segmented_pool.zig"); + +pub const lru = @import("lru.zig"); +pub const BlockingQueue = blocking_queue.BlockingQueue; +pub const CacheTable = cache_table.CacheTable; +pub const CircBuf = circ_buf.CircBuf; +pub const SegmentedPool = segmented_pool.SegmentedPool; + +test { + @import("std").testing.refAllDecls(@This()); +} diff --git a/src/segmented_pool.zig b/src/datastruct/segmented_pool.zig similarity index 100% rename from src/segmented_pool.zig rename to src/datastruct/segmented_pool.zig diff --git a/src/font/shaper/Cache.zig b/src/font/shaper/Cache.zig index 2a1424118..672845bfd 100644 --- a/src/font/shaper/Cache.zig +++ b/src/font/shaper/Cache.zig @@ -14,7 +14,7 @@ const std = @import("std"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; const font = @import("../main.zig"); -const CacheTable = @import("../../cache_table.zig").CacheTable; +const CacheTable = @import("../../datastruct/main.zig").CacheTable; const log = std.log.scoped(.font_shaper_cache); diff --git a/src/inspector/key.zig b/src/inspector/key.zig index 9e1d6eacb..e28bd5d4a 100644 --- a/src/inspector/key.zig +++ b/src/inspector/key.zig @@ -1,7 +1,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const input = @import("../input.zig"); -const CircBuf = @import("../circ_buf.zig").CircBuf; +const CircBuf = @import("../datastruct/main.zig").CircBuf; const cimgui = @import("cimgui"); /// Circular buffer of key events. diff --git a/src/inspector/termio.zig b/src/inspector/termio.zig index bae07bcfe..78b35e19b 100644 --- a/src/inspector/termio.zig +++ b/src/inspector/termio.zig @@ -2,7 +2,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const cimgui = @import("cimgui"); const terminal = @import("../terminal/main.zig"); -const CircBuf = @import("../circ_buf.zig").CircBuf; +const CircBuf = @import("../datastruct/main.zig").CircBuf; const Surface = @import("../Surface.zig"); /// The stream handler for our inspector. diff --git a/src/main_ghostty.zig b/src/main_ghostty.zig index a435d8772..071d4d530 100644 --- a/src/main_ghostty.zig +++ b/src/main_ghostty.zig @@ -168,7 +168,6 @@ pub const std_options: std.Options = .{ }; test { - _ = @import("circ_buf.zig"); _ = @import("pty.zig"); _ = @import("Command.zig"); _ = @import("font/main.zig"); @@ -180,17 +179,11 @@ test { _ = @import("surface_mouse.zig"); // Libraries - _ = @import("segmented_pool.zig"); _ = @import("crash/main.zig"); + _ = @import("datastruct/main.zig"); _ = @import("inspector/main.zig"); _ = @import("terminal/main.zig"); _ = @import("terminfo/main.zig"); _ = @import("simd/main.zig"); _ = @import("unicode/main.zig"); - - // TODO - _ = @import("blocking_queue.zig"); - _ = @import("cache_table.zig"); - _ = @import("config.zig"); - _ = @import("lru.zig"); } diff --git a/src/os/cf_release_thread.zig b/src/os/cf_release_thread.zig index 32069163b..5001441e0 100644 --- a/src/os/cf_release_thread.zig +++ b/src/os/cf_release_thread.zig @@ -9,7 +9,7 @@ const builtin = @import("builtin"); const xev = @import("xev"); const macos = @import("macos"); -const BlockingQueue = @import("../blocking_queue.zig").BlockingQueue; +const BlockingQueue = @import("../datastruct/main.zig").BlockingQueue; const Allocator = std.mem.Allocator; const log = std.log.scoped(.cf_release_thread); diff --git a/src/renderer/Thread.zig b/src/renderer/Thread.zig index 35679e994..94a1280d9 100644 --- a/src/renderer/Thread.zig +++ b/src/renderer/Thread.zig @@ -9,7 +9,7 @@ const crash = @import("../crash/main.zig"); const renderer = @import("../renderer.zig"); const apprt = @import("../apprt.zig"); const configpkg = @import("../config.zig"); -const BlockingQueue = @import("../blocking_queue.zig").BlockingQueue; +const BlockingQueue = @import("../datastruct/main.zig").BlockingQueue; const App = @import("../App.zig"); const Allocator = std.mem.Allocator; diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 5018ced33..3bf288cb7 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -20,7 +20,7 @@ const shell_integration = @import("shell_integration.zig"); const terminal = @import("../terminal/main.zig"); const termio = @import("../termio.zig"); const Command = @import("../Command.zig"); -const SegmentedPool = @import("../segmented_pool.zig").SegmentedPool; +const SegmentedPool = @import("../datastruct/main.zig").SegmentedPool; const ptypkg = @import("../pty.zig"); const Pty = ptypkg.Pty; const EnvMap = std.process.EnvMap; diff --git a/src/termio/Termio.zig b/src/termio/Termio.zig index f28eb118e..f2cdfc770 100644 --- a/src/termio/Termio.zig +++ b/src/termio/Termio.zig @@ -15,7 +15,6 @@ const posix = std.posix; const termio = @import("../termio.zig"); const Command = @import("../Command.zig"); const Pty = @import("../pty.zig").Pty; -const SegmentedPool = @import("../segmented_pool.zig").SegmentedPool; const StreamHandler = @import("stream_handler.zig").StreamHandler; const terminal = @import("../terminal/main.zig"); const terminfo = @import("../terminfo/main.zig"); diff --git a/src/termio/Thread.zig b/src/termio/Thread.zig index 0f9cd782e..3d316e399 100644 --- a/src/termio/Thread.zig +++ b/src/termio/Thread.zig @@ -17,7 +17,7 @@ const builtin = @import("builtin"); const xev = @import("xev"); const crash = @import("../crash/main.zig"); const termio = @import("../termio.zig"); -const BlockingQueue = @import("../blocking_queue.zig").BlockingQueue; +const BlockingQueue = @import("../datastruct/main.zig").BlockingQueue; const Allocator = std.mem.Allocator; const log = std.log.scoped(.io_thread); diff --git a/src/termio/backend.zig b/src/termio/backend.zig index 0080e7628..68b283a00 100644 --- a/src/termio/backend.zig +++ b/src/termio/backend.zig @@ -12,7 +12,6 @@ const shell_integration = @import("shell_integration.zig"); const terminal = @import("../terminal/main.zig"); const termio = @import("../termio.zig"); const Command = @import("../Command.zig"); -const SegmentedPool = @import("../segmented_pool.zig").SegmentedPool; const Pty = @import("../pty.zig").Pty; // The preallocation size for the write request pool. This should be big diff --git a/src/termio/mailbox.zig b/src/termio/mailbox.zig index 85471009d..cac453a1c 100644 --- a/src/termio/mailbox.zig +++ b/src/termio/mailbox.zig @@ -5,7 +5,7 @@ const Allocator = std.mem.Allocator; const xev = @import("xev"); const renderer = @import("../renderer.zig"); const termio = @import("../termio.zig"); -const BlockingQueue = @import("../blocking_queue.zig").BlockingQueue; +const BlockingQueue = @import("../datastruct/main.zig").BlockingQueue; const log = std.log.scoped(.io_writer); From 3aff43b2e88e0f6cab702de7873e787f06685933 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 7 Nov 2024 14:57:46 -0800 Subject: [PATCH 07/27] datastruct: add intrusive doubly linked list --- src/datastruct/intrusive_linked_list.zig | 177 +++++++++++++++++++++++ src/datastruct/main.zig | 2 + 2 files changed, 179 insertions(+) create mode 100644 src/datastruct/intrusive_linked_list.zig diff --git a/src/datastruct/intrusive_linked_list.zig b/src/datastruct/intrusive_linked_list.zig new file mode 100644 index 000000000..02279457f --- /dev/null +++ b/src/datastruct/intrusive_linked_list.zig @@ -0,0 +1,177 @@ +const std = @import("std"); +const testing = std.testing; + +/// An intrusive doubly-linked list. The type T must have a "next" and "prev" +/// field pointing to itself. +/// +/// This is an adaptation of the DoublyLinkedList from the Zig standard +/// library, which is MIT licensed. I've removed functionality that I don't +/// need. +pub fn DoublyLinkedList(comptime T: type) type { + return struct { + const Self = @This(); + + first: ?*T = null, + last: ?*T = null, + + /// Insert a new node after an existing one. + /// + /// Arguments: + /// node: Pointer to a node in the list. + /// new_node: Pointer to the new node to insert. + pub fn insertAfter(list: *Self, node: *T, new_node: *T) void { + new_node.prev = node; + if (node.next) |next_node| { + // Intermediate node. + new_node.next = next_node; + next_node.prev = new_node; + } else { + // Last element of the list. + new_node.next = null; + list.last = new_node; + } + node.next = new_node; + } + + /// Insert a new node before an existing one. + /// + /// Arguments: + /// node: Pointer to a node in the list. + /// new_node: Pointer to the new node to insert. + pub fn insertBefore(list: *Self, node: *T, new_node: *T) void { + new_node.next = node; + if (node.prev) |prev_node| { + // Intermediate node. + new_node.prev = prev_node; + prev_node.next = new_node; + } else { + // First element of the list. + new_node.prev = null; + list.first = new_node; + } + node.prev = new_node; + } + + /// Insert a new node at the end of the list. + /// + /// Arguments: + /// new_node: Pointer to the new node to insert. + pub fn append(list: *Self, new_node: *T) void { + if (list.last) |last| { + // Insert after last. + list.insertAfter(last, new_node); + } else { + // Empty list. + list.prepend(new_node); + } + } + + /// Insert a new node at the beginning of the list. + /// + /// Arguments: + /// new_node: Pointer to the new node to insert. + pub fn prepend(list: *Self, new_node: *T) void { + if (list.first) |first| { + // Insert before first. + list.insertBefore(first, new_node); + } else { + // Empty list. + list.first = new_node; + list.last = new_node; + new_node.prev = null; + new_node.next = null; + } + } + + /// Remove a node from the list. + /// + /// Arguments: + /// node: Pointer to the node to be removed. + pub fn remove(list: *Self, node: *T) void { + if (node.prev) |prev_node| { + // Intermediate node. + prev_node.next = node.next; + } else { + // First element of the list. + list.first = node.next; + } + + if (node.next) |next_node| { + // Intermediate node. + next_node.prev = node.prev; + } else { + // Last element of the list. + list.last = node.prev; + } + } + + /// Remove and return the last node in the list. + /// + /// Returns: + /// A pointer to the last node in the list. + pub fn pop(list: *Self) ?*T { + const last = list.last orelse return null; + list.remove(last); + return last; + } + + /// Remove and return the first node in the list. + /// + /// Returns: + /// A pointer to the first node in the list. + pub fn popFirst(list: *Self) ?*T { + const first = list.first orelse return null; + list.remove(first); + return first; + } + }; +} + +test "basic DoublyLinkedList test" { + const Node = struct { + data: u32, + prev: ?*@This() = null, + next: ?*@This() = null, + }; + const L = DoublyLinkedList(Node); + var list: L = .{}; + + var one: Node = .{ .data = 1 }; + var two: Node = .{ .data = 2 }; + var three: Node = .{ .data = 3 }; + var four: Node = .{ .data = 4 }; + var five: Node = .{ .data = 5 }; + + list.append(&two); // {2} + list.append(&five); // {2, 5} + list.prepend(&one); // {1, 2, 5} + list.insertBefore(&five, &four); // {1, 2, 4, 5} + list.insertAfter(&two, &three); // {1, 2, 3, 4, 5} + + // Traverse forwards. + { + var it = list.first; + var index: u32 = 1; + while (it) |node| : (it = node.next) { + try testing.expect(node.data == index); + index += 1; + } + } + + // Traverse backwards. + { + var it = list.last; + var index: u32 = 1; + while (it) |node| : (it = node.prev) { + try testing.expect(node.data == (6 - index)); + index += 1; + } + } + + _ = list.popFirst(); // {2, 3, 4, 5} + _ = list.pop(); // {2, 3, 4} + list.remove(&three); // {2, 4} + + try testing.expect(list.first.?.data == 2); + try testing.expect(list.last.?.data == 4); +} diff --git a/src/datastruct/main.zig b/src/datastruct/main.zig index 70a7ce97f..4f45f9483 100644 --- a/src/datastruct/main.zig +++ b/src/datastruct/main.zig @@ -4,12 +4,14 @@ const blocking_queue = @import("blocking_queue.zig"); const cache_table = @import("cache_table.zig"); const circ_buf = @import("circ_buf.zig"); +const intrusive_linked_list = @import("intrusive_linked_list.zig"); const segmented_pool = @import("segmented_pool.zig"); pub const lru = @import("lru.zig"); pub const BlockingQueue = blocking_queue.BlockingQueue; pub const CacheTable = cache_table.CacheTable; pub const CircBuf = circ_buf.CircBuf; +pub const IntrusiveDoublyLinkedList = intrusive_linked_list.DoublyLinkedList; pub const SegmentedPool = segmented_pool.SegmentedPool; test { From 1335af3e4adb76b841d4d592a79377e53bf2ffc2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 7 Nov 2024 15:05:13 -0800 Subject: [PATCH 08/27] terminal: change pagelist linked list to an intrusive linked list --- src/datastruct/intrusive_linked_list.zig | 22 +++++++++++++--------- src/terminal/PageList.zig | 17 ++++++++++++++--- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/datastruct/intrusive_linked_list.zig b/src/datastruct/intrusive_linked_list.zig index 02279457f..61bf8157c 100644 --- a/src/datastruct/intrusive_linked_list.zig +++ b/src/datastruct/intrusive_linked_list.zig @@ -11,15 +11,19 @@ pub fn DoublyLinkedList(comptime T: type) type { return struct { const Self = @This(); - first: ?*T = null, - last: ?*T = null, + /// The type of the node in the list. This makes it easy to get the + /// node type from the list type. + pub const Node = T; + + first: ?*Node = null, + last: ?*Node = null, /// Insert a new node after an existing one. /// /// Arguments: /// node: Pointer to a node in the list. /// new_node: Pointer to the new node to insert. - pub fn insertAfter(list: *Self, node: *T, new_node: *T) void { + pub fn insertAfter(list: *Self, node: *Node, new_node: *Node) void { new_node.prev = node; if (node.next) |next_node| { // Intermediate node. @@ -38,7 +42,7 @@ pub fn DoublyLinkedList(comptime T: type) type { /// Arguments: /// node: Pointer to a node in the list. /// new_node: Pointer to the new node to insert. - pub fn insertBefore(list: *Self, node: *T, new_node: *T) void { + pub fn insertBefore(list: *Self, node: *Node, new_node: *Node) void { new_node.next = node; if (node.prev) |prev_node| { // Intermediate node. @@ -56,7 +60,7 @@ pub fn DoublyLinkedList(comptime T: type) type { /// /// Arguments: /// new_node: Pointer to the new node to insert. - pub fn append(list: *Self, new_node: *T) void { + pub fn append(list: *Self, new_node: *Node) void { if (list.last) |last| { // Insert after last. list.insertAfter(last, new_node); @@ -70,7 +74,7 @@ pub fn DoublyLinkedList(comptime T: type) type { /// /// Arguments: /// new_node: Pointer to the new node to insert. - pub fn prepend(list: *Self, new_node: *T) void { + pub fn prepend(list: *Self, new_node: *Node) void { if (list.first) |first| { // Insert before first. list.insertBefore(first, new_node); @@ -87,7 +91,7 @@ pub fn DoublyLinkedList(comptime T: type) type { /// /// Arguments: /// node: Pointer to the node to be removed. - pub fn remove(list: *Self, node: *T) void { + pub fn remove(list: *Self, node: *Node) void { if (node.prev) |prev_node| { // Intermediate node. prev_node.next = node.next; @@ -109,7 +113,7 @@ pub fn DoublyLinkedList(comptime T: type) type { /// /// Returns: /// A pointer to the last node in the list. - pub fn pop(list: *Self) ?*T { + pub fn pop(list: *Self) ?*Node { const last = list.last orelse return null; list.remove(last); return last; @@ -119,7 +123,7 @@ pub fn DoublyLinkedList(comptime T: type) type { /// /// Returns: /// A pointer to the first node in the list. - pub fn popFirst(list: *Self) ?*T { + pub fn popFirst(list: *Self) ?*Node { const first = list.first orelse return null; list.remove(first); return first; diff --git a/src/terminal/PageList.zig b/src/terminal/PageList.zig index f1dbc2561..70f972ebe 100644 --- a/src/terminal/PageList.zig +++ b/src/terminal/PageList.zig @@ -7,8 +7,9 @@ const std = @import("std"); const build_config = @import("../build_config.zig"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; -const color = @import("color.zig"); const fastmem = @import("../fastmem.zig"); +const DoublyLinkedList = @import("../datastruct/main.zig").IntrusiveDoublyLinkedList; +const color = @import("color.zig"); const kitty = @import("kitty.zig"); const point = @import("point.zig"); const pagepkg = @import("page.zig"); @@ -33,7 +34,16 @@ const page_preheat = 4; /// The list of pages in the screen. These are expected to be in order /// where the first page is the topmost page (scrollback) and the last is /// the bottommost page (the current active page). -pub const List = std.DoublyLinkedList(Page); +pub const List = DoublyLinkedList(Node); + +/// A single node within the PageList linked list. +/// +/// This isn't pub because you can access the type via List.Node. +const Node = struct { + prev: ?*Node = null, + next: ?*Node = null, + data: Page, +}; /// The memory pool we get page nodes from. const NodePool = std.heap.MemoryPool(List.Node); @@ -1699,7 +1709,8 @@ pub fn grow(self: *PageList) !?*List.Node { // reuses the popped page. It is possible to have a single page and // exceed the max size if that page was adjusted to be larger after // initial allocation. - if (self.pages.len > 1 and + if (self.pages.first != null and + self.pages.first != self.pages.last and self.page_size + PagePool.item_size > self.maxSize()) prune: { // If we need to add more memory to ensure our active area is From 84707932d27df65198ede1ba4b0b963f17c0f120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristo=CC=81fer=20R?= Date: Thu, 7 Nov 2024 18:17:51 -0500 Subject: [PATCH 09/27] os/hostname: fix mac address handling when last section starts with '0' I hit an edge case when using Private Wi-Fi addressing on macOS where the last section of the randomized mac address starts with a '0'. The hostname parsing for the shell integration didn't handle this case, so issue #2512 reappeared. This fixes that by explicitly handling port numbers < 10. --- src/os/hostname.zig | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/os/hostname.zig b/src/os/hostname.zig index 6956ed71f..22f29ceff 100644 --- a/src/os/hostname.zig +++ b/src/os/hostname.zig @@ -36,15 +36,16 @@ pub fn bufPrintHostnameFromFileUri( // it's not a partial MAC-address. const port = uri.port orelse return host; - // If the port is not a 2-digit number we're not looking at a partial + // If the port is not a 1 or 2-digit number we're not looking at a partial // MAC-address, and instead just a regular port so we return the plain // URI hostname. - if (port < 10 or port > 99) return host; + if (port > 99) return host; var fbs = std.io.fixedBufferStream(buf); try std.fmt.format( fbs.writer(), - "{s}:{d}", + // Make sure "port" is always 2 digits, prefixed with a 0 when "port" is a 1-digit number. + "{s}:{d:0>2}", .{ host, port }, ); @@ -85,6 +86,14 @@ test "bufPrintHostnameFromFileUri succeeds with hostname as mac address" { try std.testing.expectEqualStrings("12:34:56:78:90:12", actual); } +test "bufPrintHostnameFromFileUri succeeds with hostname as a mac address and the last section is < 10" { + const uri = try std.Uri.parse("file://12:34:56:78:90:05"); + + var buf: [posix.HOST_NAME_MAX]u8 = undefined; + const actual = try bufPrintHostnameFromFileUri(&buf, uri); + try std.testing.expectEqualStrings("12:34:56:78:90:05", actual); +} + test "bufPrintHostnameFromFileUri returns only hostname when there is a port component in the URI" { // First: try with a non-2-digit port, to test general port handling. const four_port_uri = try std.Uri.parse("file://has-a-port:1234"); From 74bda5a6ebd14d5a9d6891f3fc21953879a36d16 Mon Sep 17 00:00:00 2001 From: Nadir Fejzic Date: Fri, 8 Nov 2024 20:26:21 +0100 Subject: [PATCH 10/27] feat: implement configurable freetype load flags --- src/build/fish_completions.zig | 2 +- src/build/mdgen/mdgen.zig | 2 +- src/config/Config.zig | 29 +++++++++++++++++++++++++++++ src/font/face.zig | 3 +++ src/font/face/freetype.zig | 8 +++++++- src/renderer/Metal.zig | 12 +++++++++++- src/renderer/OpenGL.zig | 13 ++++++++++++- 7 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/build/fish_completions.zig b/src/build/fish_completions.zig index 0ff0a2163..64fbea44e 100644 --- a/src/build/fish_completions.zig +++ b/src/build/fish_completions.zig @@ -12,7 +12,7 @@ pub const fish_completions = comptimeGenerateFishCompletions(); fn comptimeGenerateFishCompletions() []const u8 { comptime { - @setEvalBranchQuota(17000); + @setEvalBranchQuota(18000); var counter = std.io.countingWriter(std.io.null_writer); try writeFishCompletions(&counter.writer()); diff --git a/src/build/mdgen/mdgen.zig b/src/build/mdgen/mdgen.zig index 2e2884f1a..7e05596d7 100644 --- a/src/build/mdgen/mdgen.zig +++ b/src/build/mdgen/mdgen.zig @@ -26,7 +26,7 @@ pub fn genConfig(writer: anytype, cli: bool) !void { \\ ); - @setEvalBranchQuota(2000); + @setEvalBranchQuota(3000); inline for (@typeInfo(Config).Struct.fields) |field| { if (field.name[0] == '_') continue; diff --git a/src/config/Config.zig b/src/config/Config.zig index e6b9d35ab..908935a5d 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -287,6 +287,26 @@ const c = @cImport({ /// terminals. Only new terminals will use the new configuration. @"grapheme-width-method": GraphemeWidthMethod = .unicode, +/// Freetype load flags to enable. The format of this is a list of flags to +/// enable separated by commas. If you prefix a flag with `no-` then it is +/// disabled. If you omit a flag, it's default value is used, so you must +/// explicitely disable flags you don't want. You can also use `true` or `false` +/// to turn all flags on or off. +/// +/// Available flags: +/// +/// * `hinting` - Enable or disable hinting, enabled by default. +/// * `bitmap` - Enable or disable loading of any pre-rendered bitmap strikes, +/// enabled by default +/// * `force-autohint` - Use the freetype auto-hinter rather than the font's +/// native hinter. Enabled by default. +/// * `monochrome` - Instructs renderer to use 1-bit monochrome rendering. +/// This option doesn't impact the hinter. Enabled by default. +/// * `autohint` - Use the freetype auto-hinter. Enabled by default. +/// +/// Example: `hinting`, `no-hinting`, `force-autohint`, `no-force-autohint` +@"freetype-load-flag": FreetypeLoadFlags = .{}, + /// A theme to use. If the theme is an absolute pathname, Ghostty will attempt /// to load that file as a theme. If that file does not exist or is inaccessible, /// an error will be logged and no other directories will be searched. @@ -4565,6 +4585,15 @@ pub const GraphemeWidthMethod = enum { unicode, }; +/// See freetype-load-flag +pub const FreetypeLoadFlags = packed struct { + hinting: bool = true, + bitmap: bool = true, + @"force-autohint": bool = false, + monochrome: bool = false, + autohint: bool = true, +}; + /// See linux-cgroup pub const LinuxCgroup = enum { never, diff --git a/src/font/face.zig b/src/font/face.zig index 8bcfb8209..77fb9e45b 100644 --- a/src/font/face.zig +++ b/src/font/face.zig @@ -2,6 +2,7 @@ const std = @import("std"); const builtin = @import("builtin"); const options = @import("main.zig").options; pub const Metrics = @import("face/Metrics.zig"); +const FreetypeLoadFlags = @import("../config/Config.zig").FreetypeLoadFlags; const freetype = @import("face/freetype.zig"); const coretext = @import("face/coretext.zig"); pub const web_canvas = @import("face/web_canvas.zig"); @@ -90,6 +91,8 @@ pub const RenderOptions = struct { /// /// This only works with CoreText currently. thicken: bool = false, + + load_flags: FreetypeLoadFlags, }; test { diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index 7bb9ecbab..e3ad5322b 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -318,7 +318,13 @@ pub const Face = struct { // // This must be enabled for color faces though because those are // often colored bitmaps, which we support. - .no_bitmap = !self.face.hasColor(), + .no_bitmap = !self.face.hasColor() or !opts.load_flags.bitmap, + + // use options from config + .no_hinting = !opts.load_flags.hinting, + .force_autohint = !opts.load_flags.@"force-autohint", + .monochrome = !opts.load_flags.monochrome, + .no_autohint = !opts.load_flags.autohint, }); const glyph = self.face.handle.*.glyph; diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 742dfbcd4..561168b90 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -354,6 +354,7 @@ pub const DerivedConfig = struct { font_thicken: bool, font_features: std.ArrayListUnmanaged([:0]const u8), font_styles: font.CodepointResolver.StyleStatus, + load_flags: configpkg.Config.FreetypeLoadFlags, cursor_color: ?terminal.color.RGB, cursor_invert: bool, cursor_opacity: f64, @@ -399,11 +400,14 @@ pub const DerivedConfig = struct { const cursor_invert = config.@"cursor-invert-fg-bg"; + const load_flags = config.@"freetype-load-flag"; + return .{ .background_opacity = @max(0, @min(1, config.@"background-opacity")), .font_thicken = config.@"font-thicken", .font_features = font_features, .font_styles = font_styles, + .load_flags = load_flags, .cursor_color = if (!cursor_invert and config.@"cursor-color" != null) config.@"cursor-color".?.toTerminalRGB() @@ -2719,6 +2723,7 @@ fn addUnderline( .{ .cell_width = 1, .grid_metrics = self.grid_metrics, + .load_flags = self.config.load_flags, }, ); @@ -2751,6 +2756,7 @@ fn addOverline( .{ .cell_width = 1, .grid_metrics = self.grid_metrics, + .load_flags = self.config.load_flags, }, ); @@ -2783,6 +2789,7 @@ fn addStrikethrough( .{ .cell_width = 1, .grid_metrics = self.grid_metrics, + .load_flags = self.config.load_flags, }, ); @@ -2822,6 +2829,7 @@ fn addGlyph( .{ .grid_metrics = self.grid_metrics, .thicken = self.config.font_thicken, + .load_flags = self.config.load_flags, }, ); @@ -2901,6 +2909,7 @@ fn addCursor( .{ .cell_width = if (wide) 2 else 1, .grid_metrics = self.grid_metrics, + .load_flags = self.config.load_flags, }, ) catch |err| { log.warn("error rendering cursor glyph err={}", .{err}); @@ -2916,6 +2925,7 @@ fn addCursor( .{ .cell_width = if (wide) 2 else 1, .grid_metrics = self.grid_metrics, + .load_flags = self.config.load_flags, }, ) catch |err| { log.warn("error rendering cursor glyph err={}", .{err}); @@ -2956,7 +2966,7 @@ fn addPreeditCell( @intCast(cp.codepoint), .regular, .text, - .{ .grid_metrics = self.grid_metrics }, + .{ .grid_metrics = self.grid_metrics, .load_flags = self.config.load_flags }, ) catch |err| { log.warn("error rendering preedit glyph err={}", .{err}); return; diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 5313315b1..7cb391bcc 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -11,6 +11,7 @@ const ArenaAllocator = std.heap.ArenaAllocator; const link = @import("link.zig"); const isCovering = @import("cell.zig").isCovering; const fgMode = @import("cell.zig").fgMode; +const freetype = @import("freetype").Face; const shadertoy = @import("shadertoy.zig"); const apprt = @import("../apprt.zig"); const configpkg = @import("../config.zig"); @@ -288,6 +289,7 @@ pub const DerivedConfig = struct { font_thicken: bool, font_features: std.ArrayListUnmanaged([:0]const u8), font_styles: font.CodepointResolver.StyleStatus, + load_flags: configpkg.Config.FreetypeLoadFlags, cursor_color: ?terminal.color.RGB, cursor_invert: bool, cursor_text: ?terminal.color.RGB, @@ -332,11 +334,14 @@ pub const DerivedConfig = struct { const cursor_invert = config.@"cursor-invert-fg-bg"; + const load_flags = config.@"freetype-load-flag"; + return .{ .background_opacity = @max(0, @min(1, config.@"background-opacity")), .font_thicken = config.@"font-thicken", .font_features = font_features, .font_styles = font_styles, + .load_flags = load_flags, .cursor_color = if (!cursor_invert and config.@"cursor-color" != null) config.@"cursor-color".?.toTerminalRGB() @@ -1765,7 +1770,7 @@ fn addPreeditCell( @intCast(cp.codepoint), .regular, .text, - .{ .grid_metrics = self.grid_metrics }, + .{ .grid_metrics = self.grid_metrics, .load_flags = self.config.load_flags }, ) catch |err| { log.warn("error rendering preedit glyph err={}", .{err}); return; @@ -1866,6 +1871,7 @@ fn addCursor( .{ .cell_width = if (wide) 2 else 1, .grid_metrics = self.grid_metrics, + .load_flags = self.config.load_flags, }, ) catch |err| { log.warn("error rendering cursor glyph err={}", .{err}); @@ -1881,6 +1887,7 @@ fn addCursor( .{ .cell_width = if (wide) 2 else 1, .grid_metrics = self.grid_metrics, + .load_flags = self.config.load_flags, }, ) catch |err| { log.warn("error rendering cursor glyph err={}", .{err}); @@ -1943,6 +1950,7 @@ fn addUnderline( .{ .cell_width = 1, .grid_metrics = self.grid_metrics, + .load_flags = self.config.load_flags, }, ); @@ -1984,6 +1992,7 @@ fn addOverline( .{ .cell_width = 1, .grid_metrics = self.grid_metrics, + .load_flags = self.config.load_flags, }, ); @@ -2025,6 +2034,7 @@ fn addStrikethrough( .{ .cell_width = 1, .grid_metrics = self.grid_metrics, + .load_flags = self.config.load_flags, }, ); @@ -2073,6 +2083,7 @@ fn addGlyph( .{ .grid_metrics = self.grid_metrics, .thicken = self.config.font_thicken, + .load_flags = self.config.load_flags, }, ); From 945a715b08d9955b759e06f0c7ce6726d4e2604f Mon Sep 17 00:00:00 2001 From: Nadir Fejzic Date: Sat, 9 Nov 2024 00:41:55 +0100 Subject: [PATCH 11/27] refactor: handle freetype load flags in face instead of renderer --- src/config/Config.zig | 4 ++-- src/font/Collection.zig | 3 +++ src/font/SharedGridSet.zig | 7 +++++++ src/font/face.zig | 3 +-- src/font/face/freetype.zig | 15 ++++++++++----- src/renderer/Metal.zig | 12 +----------- src/renderer/OpenGL.zig | 12 +----------- 7 files changed, 25 insertions(+), 31 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 908935a5d..aef589571 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -305,7 +305,7 @@ const c = @cImport({ /// * `autohint` - Use the freetype auto-hinter. Enabled by default. /// /// Example: `hinting`, `no-hinting`, `force-autohint`, `no-force-autohint` -@"freetype-load-flag": FreetypeLoadFlags = .{}, +@"freetype-load-flags": FreetypeLoadFlags = .{}, /// A theme to use. If the theme is an absolute pathname, Ghostty will attempt /// to load that file as a theme. If that file does not exist or is inaccessible, @@ -4586,7 +4586,7 @@ pub const GraphemeWidthMethod = enum { }; /// See freetype-load-flag -pub const FreetypeLoadFlags = packed struct { +pub const FreetypeLoadFlags = packed struct { hinting: bool = true, bitmap: bool = true, @"force-autohint": bool = false, diff --git a/src/font/Collection.zig b/src/font/Collection.zig index 476787749..25615bde5 100644 --- a/src/font/Collection.zig +++ b/src/font/Collection.zig @@ -452,6 +452,8 @@ pub const LoadOptions = struct { /// for this is owned by the user and is not freed by the collection. metric_modifiers: Metrics.ModifierSet = .{}, + freetype_load_flags: config.Config.FreetypeLoadFlags = .{}, + pub fn deinit(self: *LoadOptions, alloc: Allocator) void { _ = self; _ = alloc; @@ -462,6 +464,7 @@ pub const LoadOptions = struct { return .{ .size = self.size, .metric_modifiers = &self.metric_modifiers, + .freetype_load_flags = self.freetype_load_flags, }; } }; diff --git a/src/font/SharedGridSet.zig b/src/font/SharedGridSet.zig index c3067fa6d..d4ad49a74 100644 --- a/src/font/SharedGridSet.zig +++ b/src/font/SharedGridSet.zig @@ -168,6 +168,7 @@ fn collection( .library = self.font_lib, .size = size, .metric_modifiers = key.metric_modifiers, + .freetype_load_flags = config.@"freetype-load-flags", }; var c = Collection.init(); @@ -427,6 +428,7 @@ pub const DerivedConfig = struct { @"adjust-strikethrough-position": ?Metrics.Modifier, @"adjust-strikethrough-thickness": ?Metrics.Modifier, @"adjust-cursor-thickness": ?Metrics.Modifier, + @"freetype-load-flags": configpkg.Config.FreetypeLoadFlags, /// Initialize a DerivedConfig. The config should be either a /// config.Config or another DerivedConfig to clone from. @@ -461,6 +463,7 @@ pub const DerivedConfig = struct { .@"adjust-strikethrough-position" = config.@"adjust-strikethrough-position", .@"adjust-strikethrough-thickness" = config.@"adjust-strikethrough-thickness", .@"adjust-cursor-thickness" = config.@"adjust-cursor-thickness", + .@"freetype-load-flags" = config.@"freetype-load-flags", // This must be last so the arena contains all our allocations // from above since Zig does assignment in order. @@ -500,6 +503,8 @@ pub const Key = struct { /// font grid. font_size: DesiredSize = .{ .points = 12 }, + load_flags: configpkg.Config.FreetypeLoadFlags = .{}, + const style_offsets_len = std.enums.directEnumArrayLen(Style, 0); const StyleOffsets = [style_offsets_len]usize; @@ -618,6 +623,7 @@ pub const Key = struct { .codepoint_map = codepoint_map, .metric_modifiers = metric_modifiers, .font_size = font_size, + .load_flags = config.@"freetype-load-flags", }; } @@ -647,6 +653,7 @@ pub const Key = struct { for (self.descriptors) |d| d.hash(hasher); self.codepoint_map.hash(hasher); autoHash(hasher, self.metric_modifiers.count()); + autoHash(hasher, self.load_flags); if (self.metric_modifiers.count() > 0) { inline for (@typeInfo(Metrics.Key).Enum.fields) |field| { const key = @field(Metrics.Key, field.name); diff --git a/src/font/face.zig b/src/font/face.zig index 77fb9e45b..d3fd89aa5 100644 --- a/src/font/face.zig +++ b/src/font/face.zig @@ -31,6 +31,7 @@ pub const default_dpi = if (builtin.os.tag == .macos) 72 else 96; pub const Options = struct { size: DesiredSize, metric_modifiers: ?*const Metrics.ModifierSet = null, + freetype_load_flags: FreetypeLoadFlags, }; /// The desired size for loading a font. @@ -91,8 +92,6 @@ pub const RenderOptions = struct { /// /// This only works with CoreText currently. thicken: bool = false, - - load_flags: FreetypeLoadFlags, }; test { diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index e3ad5322b..ac173d6dd 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -18,6 +18,7 @@ const Library = font.Library; const convert = @import("freetype_convert.zig"); const fastmem = @import("../../fastmem.zig"); const quirks = @import("../../quirks.zig"); +const FreetypeLoadFlags = @import("../../config/Config.zig").FreetypeLoadFlags; const log = std.log.scoped(.font_face); @@ -34,6 +35,9 @@ pub const Face = struct { /// Metrics for this font face. These are useful for renderers. metrics: font.face.Metrics, + /// Metrics for this font face. These are useful for renderers. + load_flags: FreetypeLoadFlags, + /// Set quirks.disableDefaultFontFeatures quirks_disable_default_font_features: bool = false, @@ -77,6 +81,7 @@ pub const Face = struct { .face = face, .hb_font = hb_font, .metrics = calcMetrics(face, opts.metric_modifiers), + .load_flags = opts.freetype_load_flags, }; result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result); @@ -318,13 +323,13 @@ pub const Face = struct { // // This must be enabled for color faces though because those are // often colored bitmaps, which we support. - .no_bitmap = !self.face.hasColor() or !opts.load_flags.bitmap, + .no_bitmap = !self.face.hasColor() or !self.load_flags.bitmap, // use options from config - .no_hinting = !opts.load_flags.hinting, - .force_autohint = !opts.load_flags.@"force-autohint", - .monochrome = !opts.load_flags.monochrome, - .no_autohint = !opts.load_flags.autohint, + .no_hinting = !self.load_flags.hinting, + .force_autohint = !self.load_flags.@"force-autohint", + .monochrome = !self.load_flags.monochrome, + .no_autohint = !self.load_flags.autohint, }); const glyph = self.face.handle.*.glyph; diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 561168b90..742dfbcd4 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -354,7 +354,6 @@ pub const DerivedConfig = struct { font_thicken: bool, font_features: std.ArrayListUnmanaged([:0]const u8), font_styles: font.CodepointResolver.StyleStatus, - load_flags: configpkg.Config.FreetypeLoadFlags, cursor_color: ?terminal.color.RGB, cursor_invert: bool, cursor_opacity: f64, @@ -400,14 +399,11 @@ pub const DerivedConfig = struct { const cursor_invert = config.@"cursor-invert-fg-bg"; - const load_flags = config.@"freetype-load-flag"; - return .{ .background_opacity = @max(0, @min(1, config.@"background-opacity")), .font_thicken = config.@"font-thicken", .font_features = font_features, .font_styles = font_styles, - .load_flags = load_flags, .cursor_color = if (!cursor_invert and config.@"cursor-color" != null) config.@"cursor-color".?.toTerminalRGB() @@ -2723,7 +2719,6 @@ fn addUnderline( .{ .cell_width = 1, .grid_metrics = self.grid_metrics, - .load_flags = self.config.load_flags, }, ); @@ -2756,7 +2751,6 @@ fn addOverline( .{ .cell_width = 1, .grid_metrics = self.grid_metrics, - .load_flags = self.config.load_flags, }, ); @@ -2789,7 +2783,6 @@ fn addStrikethrough( .{ .cell_width = 1, .grid_metrics = self.grid_metrics, - .load_flags = self.config.load_flags, }, ); @@ -2829,7 +2822,6 @@ fn addGlyph( .{ .grid_metrics = self.grid_metrics, .thicken = self.config.font_thicken, - .load_flags = self.config.load_flags, }, ); @@ -2909,7 +2901,6 @@ fn addCursor( .{ .cell_width = if (wide) 2 else 1, .grid_metrics = self.grid_metrics, - .load_flags = self.config.load_flags, }, ) catch |err| { log.warn("error rendering cursor glyph err={}", .{err}); @@ -2925,7 +2916,6 @@ fn addCursor( .{ .cell_width = if (wide) 2 else 1, .grid_metrics = self.grid_metrics, - .load_flags = self.config.load_flags, }, ) catch |err| { log.warn("error rendering cursor glyph err={}", .{err}); @@ -2966,7 +2956,7 @@ fn addPreeditCell( @intCast(cp.codepoint), .regular, .text, - .{ .grid_metrics = self.grid_metrics, .load_flags = self.config.load_flags }, + .{ .grid_metrics = self.grid_metrics }, ) catch |err| { log.warn("error rendering preedit glyph err={}", .{err}); return; diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 7cb391bcc..9d1c1a27d 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -289,7 +289,6 @@ pub const DerivedConfig = struct { font_thicken: bool, font_features: std.ArrayListUnmanaged([:0]const u8), font_styles: font.CodepointResolver.StyleStatus, - load_flags: configpkg.Config.FreetypeLoadFlags, cursor_color: ?terminal.color.RGB, cursor_invert: bool, cursor_text: ?terminal.color.RGB, @@ -334,14 +333,11 @@ pub const DerivedConfig = struct { const cursor_invert = config.@"cursor-invert-fg-bg"; - const load_flags = config.@"freetype-load-flag"; - return .{ .background_opacity = @max(0, @min(1, config.@"background-opacity")), .font_thicken = config.@"font-thicken", .font_features = font_features, .font_styles = font_styles, - .load_flags = load_flags, .cursor_color = if (!cursor_invert and config.@"cursor-color" != null) config.@"cursor-color".?.toTerminalRGB() @@ -1770,7 +1766,7 @@ fn addPreeditCell( @intCast(cp.codepoint), .regular, .text, - .{ .grid_metrics = self.grid_metrics, .load_flags = self.config.load_flags }, + .{ .grid_metrics = self.grid_metrics }, ) catch |err| { log.warn("error rendering preedit glyph err={}", .{err}); return; @@ -1871,7 +1867,6 @@ fn addCursor( .{ .cell_width = if (wide) 2 else 1, .grid_metrics = self.grid_metrics, - .load_flags = self.config.load_flags, }, ) catch |err| { log.warn("error rendering cursor glyph err={}", .{err}); @@ -1887,7 +1882,6 @@ fn addCursor( .{ .cell_width = if (wide) 2 else 1, .grid_metrics = self.grid_metrics, - .load_flags = self.config.load_flags, }, ) catch |err| { log.warn("error rendering cursor glyph err={}", .{err}); @@ -1950,7 +1944,6 @@ fn addUnderline( .{ .cell_width = 1, .grid_metrics = self.grid_metrics, - .load_flags = self.config.load_flags, }, ); @@ -1992,7 +1985,6 @@ fn addOverline( .{ .cell_width = 1, .grid_metrics = self.grid_metrics, - .load_flags = self.config.load_flags, }, ); @@ -2034,7 +2026,6 @@ fn addStrikethrough( .{ .cell_width = 1, .grid_metrics = self.grid_metrics, - .load_flags = self.config.load_flags, }, ); @@ -2083,7 +2074,6 @@ fn addGlyph( .{ .grid_metrics = self.grid_metrics, .thicken = self.config.font_thicken, - .load_flags = self.config.load_flags, }, ); From 290857a87171d82e5c1e206fce490a8069f5dacc Mon Sep 17 00:00:00 2001 From: Nadir Fejzic Date: Sat, 9 Nov 2024 00:44:19 +0100 Subject: [PATCH 12/27] chore: remove unused import --- src/renderer/OpenGL.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 9d1c1a27d..5313315b1 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -11,7 +11,6 @@ const ArenaAllocator = std.heap.ArenaAllocator; const link = @import("link.zig"); const isCovering = @import("cell.zig").isCovering; const fgMode = @import("cell.zig").fgMode; -const freetype = @import("freetype").Face; const shadertoy = @import("shadertoy.zig"); const apprt = @import("../apprt.zig"); const configpkg = @import("../config.zig"); From c0b24ee60d65bee135dea7c99ad9446e9649a574 Mon Sep 17 00:00:00 2001 From: Nadir Fejzic Date: Sat, 9 Nov 2024 01:35:39 +0100 Subject: [PATCH 13/27] refactor: make freetype flags void for non-freetype backend This is an attempt to use `void` as type for Freetype Load Flags when backend does not use these flags. --- src/config.zig | 14 ++++++++++++++ src/font/Collection.zig | 13 ++++++++++++- src/font/SharedGridSet.zig | 28 +++++++++++++++++++++++++--- src/font/face.zig | 4 ++-- src/font/face/freetype.zig | 4 ++-- 5 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/config.zig b/src/config.zig index b9f214fc9..f2d4876ae 100644 --- a/src/config.zig +++ b/src/config.zig @@ -1,6 +1,8 @@ const builtin = @import("builtin"); const formatter = @import("config/formatter.zig"); +const font = @import("font/main.zig"); +const options = font.options; pub const Config = @import("config/Config.zig"); pub const string = @import("config/string.zig"); pub const edit = @import("config/edit.zig"); @@ -9,6 +11,18 @@ pub const url = @import("config/url.zig"); pub const FileFormatter = formatter.FileFormatter; pub const entryFormatter = formatter.entryFormatter; pub const formatEntry = formatter.formatEntry; +pub const FreetypeLoadFlags = switch (options.backend) { + .freetype, + .fontconfig_freetype, + .coretext_freetype, + => Config.FreetypeLoadFlags, + + .coretext, + .coretext_harfbuzz, + .coretext_noshape, + .web_canvas, + => void, +}; // Field types pub const ClipboardAccess = Config.ClipboardAccess; diff --git a/src/font/Collection.zig b/src/font/Collection.zig index 25615bde5..b65b4bd2e 100644 --- a/src/font/Collection.zig +++ b/src/font/Collection.zig @@ -452,7 +452,18 @@ pub const LoadOptions = struct { /// for this is owned by the user and is not freed by the collection. metric_modifiers: Metrics.ModifierSet = .{}, - freetype_load_flags: config.Config.FreetypeLoadFlags = .{}, + freetype_load_flags: config.FreetypeLoadFlags = switch (font.options.backend) { + .freetype, + .fontconfig_freetype, + .coretext_freetype, + => .{}, + + .coretext, + .coretext_harfbuzz, + .coretext_noshape, + .web_canvas, + => {}, + }, pub fn deinit(self: *LoadOptions, alloc: Allocator) void { _ = self; diff --git a/src/font/SharedGridSet.zig b/src/font/SharedGridSet.zig index d4ad49a74..49886c8f2 100644 --- a/src/font/SharedGridSet.zig +++ b/src/font/SharedGridSet.zig @@ -428,7 +428,7 @@ pub const DerivedConfig = struct { @"adjust-strikethrough-position": ?Metrics.Modifier, @"adjust-strikethrough-thickness": ?Metrics.Modifier, @"adjust-cursor-thickness": ?Metrics.Modifier, - @"freetype-load-flags": configpkg.Config.FreetypeLoadFlags, + @"freetype-load-flags": configpkg.FreetypeLoadFlags, /// Initialize a DerivedConfig. The config should be either a /// config.Config or another DerivedConfig to clone from. @@ -463,7 +463,18 @@ pub const DerivedConfig = struct { .@"adjust-strikethrough-position" = config.@"adjust-strikethrough-position", .@"adjust-strikethrough-thickness" = config.@"adjust-strikethrough-thickness", .@"adjust-cursor-thickness" = config.@"adjust-cursor-thickness", - .@"freetype-load-flags" = config.@"freetype-load-flags", + .@"freetype-load-flags" = switch (font.options.backend) { + .freetype, + .fontconfig_freetype, + .coretext_freetype, + => config.@"freetype-load-flags", + + .coretext, + .coretext_harfbuzz, + .coretext_noshape, + .web_canvas, + => {}, + }, // This must be last so the arena contains all our allocations // from above since Zig does assignment in order. @@ -503,7 +514,18 @@ pub const Key = struct { /// font grid. font_size: DesiredSize = .{ .points = 12 }, - load_flags: configpkg.Config.FreetypeLoadFlags = .{}, + load_flags: configpkg.FreetypeLoadFlags = switch (font.options.backend) { + .freetype, + .fontconfig_freetype, + .coretext_freetype, + => .{}, + + .coretext, + .coretext_harfbuzz, + .coretext_noshape, + .web_canvas, + => {}, + }, const style_offsets_len = std.enums.directEnumArrayLen(Style, 0); const StyleOffsets = [style_offsets_len]usize; diff --git a/src/font/face.zig b/src/font/face.zig index d3fd89aa5..663a86672 100644 --- a/src/font/face.zig +++ b/src/font/face.zig @@ -2,7 +2,7 @@ const std = @import("std"); const builtin = @import("builtin"); const options = @import("main.zig").options; pub const Metrics = @import("face/Metrics.zig"); -const FreetypeLoadFlags = @import("../config/Config.zig").FreetypeLoadFlags; +const config = @import("../config.zig"); const freetype = @import("face/freetype.zig"); const coretext = @import("face/coretext.zig"); pub const web_canvas = @import("face/web_canvas.zig"); @@ -31,7 +31,7 @@ pub const default_dpi = if (builtin.os.tag == .macos) 72 else 96; pub const Options = struct { size: DesiredSize, metric_modifiers: ?*const Metrics.ModifierSet = null, - freetype_load_flags: FreetypeLoadFlags, + freetype_load_flags: config.FreetypeLoadFlags, }; /// The desired size for loading a font. diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index ac173d6dd..2715d664a 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -18,7 +18,7 @@ const Library = font.Library; const convert = @import("freetype_convert.zig"); const fastmem = @import("../../fastmem.zig"); const quirks = @import("../../quirks.zig"); -const FreetypeLoadFlags = @import("../../config/Config.zig").FreetypeLoadFlags; +const config = @import("../../config.zig"); const log = std.log.scoped(.font_face); @@ -36,7 +36,7 @@ pub const Face = struct { metrics: font.face.Metrics, /// Metrics for this font face. These are useful for renderers. - load_flags: FreetypeLoadFlags, + load_flags: config.FreetypeLoadFlags, /// Set quirks.disableDefaultFontFeatures quirks_disable_default_font_features: bool = false, From e7f286d83fc5d43be519abd0e21a03fadb43377f Mon Sep 17 00:00:00 2001 From: Nadir Fejzic Date: Sat, 9 Nov 2024 01:40:39 +0100 Subject: [PATCH 14/27] docs: describe `load_flags` field in `Face` struct --- src/font/face/freetype.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index 2715d664a..5bbbb246b 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -35,7 +35,7 @@ pub const Face = struct { /// Metrics for this font face. These are useful for renderers. metrics: font.face.Metrics, - /// Metrics for this font face. These are useful for renderers. + /// Freetype load flags for this font face. load_flags: config.FreetypeLoadFlags, /// Set quirks.disableDefaultFontFeatures From b353ddf46dafe2bec4fa397ab8fb69f59fb5f911 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Fri, 8 Nov 2024 22:16:44 -0600 Subject: [PATCH 15/27] core/gtk: unify libadwaita/adwaita options in the code Fixes #2574 --- build.zig | 8 ++++---- src/apprt/gtk/adwaita.zig | 4 ++-- src/apprt/gtk/c.zig | 2 +- src/build_config.zig | 6 +++--- src/cli/version.zig | 2 +- src/config/Config.zig | 10 +++++----- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/build.zig b/build.zig index 2cce41a31..c50932b63 100644 --- a/build.zig +++ b/build.zig @@ -92,10 +92,10 @@ pub fn build(b: *std.Build) !void { "The app runtime to use. Not all values supported on all platforms.", ) orelse renderer.Impl.default(target.result, wasm_target); - config.libadwaita = b.option( + config.adwaita = b.option( bool, - "gtk-libadwaita", - "Enables the use of libadwaita when using the gtk rendering backend.", + "gtk-adwaita", + "Enables the use of Adwaita when using the GTK rendering backend.", ) orelse true; const conformance = b.option( @@ -1321,7 +1321,7 @@ fn addDeps( .gtk => { step.linkSystemLibrary2("gtk4", dynamic_link_opts); - if (config.libadwaita) step.linkSystemLibrary2("adwaita-1", dynamic_link_opts); + if (config.adwaita) step.linkSystemLibrary2("adwaita-1", dynamic_link_opts); { const gresource = @import("src/apprt/gtk/gresource.zig"); diff --git a/src/apprt/gtk/adwaita.zig b/src/apprt/gtk/adwaita.zig index ac9c59674..2c28bc39b 100644 --- a/src/apprt/gtk/adwaita.zig +++ b/src/apprt/gtk/adwaita.zig @@ -12,7 +12,7 @@ const Config = @import("../../config.zig").Config; /// This must be `inline` so that the comptime check noops conditional /// paths that are not enabled. pub inline fn enabled(config: *const Config) bool { - return build_options.libadwaita and + return build_options.adwaita and config.@"gtk-adwaita"; } @@ -30,7 +30,7 @@ pub fn versionAtLeast( comptime minor: u16, comptime micro: u16, ) bool { - if (comptime !build_options.libadwaita) return false; + if (comptime !build_options.adwaita) return false; // If our header has lower versions than the given version, // we can return false immediately. This prevents us from diff --git a/src/apprt/gtk/c.zig b/src/apprt/gtk/c.zig index e8788afee..63801250e 100644 --- a/src/apprt/gtk/c.zig +++ b/src/apprt/gtk/c.zig @@ -1,7 +1,7 @@ /// Imported C API directly from header files pub const c = @cImport({ @cInclude("gtk/gtk.h"); - if (@import("build_options").libadwaita) { + if (@import("build_options").adwaita) { @cInclude("libadwaita-1/adwaita.h"); } diff --git a/src/build_config.zig b/src/build_config.zig index 48391ef4f..715552e03 100644 --- a/src/build_config.zig +++ b/src/build_config.zig @@ -21,7 +21,7 @@ const WasmTarget = @import("os/wasm/target.zig").Target; pub const BuildConfig = struct { version: std.SemanticVersion = .{ .major = 0, .minor = 0, .patch = 0 }, flatpak: bool = false, - libadwaita: bool = false, + adwaita: bool = false, app_runtime: apprt.Runtime = .none, renderer: rendererpkg.Impl = .opengl, font_backend: font.Backend = .freetype, @@ -40,7 +40,7 @@ pub const BuildConfig = struct { // We need to break these down individual because addOption doesn't // support all types. step.addOption(bool, "flatpak", self.flatpak); - step.addOption(bool, "libadwaita", self.libadwaita); + step.addOption(bool, "adwaita", self.adwaita); step.addOption(apprt.Runtime, "app_runtime", self.app_runtime); step.addOption(font.Backend, "font_backend", self.font_backend); step.addOption(rendererpkg.Impl, "renderer", self.renderer); @@ -67,7 +67,7 @@ pub const BuildConfig = struct { return .{ .version = options.app_version, .flatpak = options.flatpak, - .libadwaita = options.libadwaita, + .adwaita = options.adwaita, .app_runtime = std.meta.stringToEnum(apprt.Runtime, @tagName(options.app_runtime)).?, .font_backend = std.meta.stringToEnum(font.Backend, @tagName(options.font_backend)).?, .renderer = std.meta.stringToEnum(rendererpkg.Impl, @tagName(options.renderer)).?, diff --git a/src/cli/version.zig b/src/cli/version.zig index 6212b1743..26d5dcc74 100644 --- a/src/cli/version.zig +++ b/src/cli/version.zig @@ -42,7 +42,7 @@ pub fn run(alloc: Allocator) !u8 { gtk.gtk_get_minor_version(), gtk.gtk_get_micro_version(), }); - if (comptime build_options.libadwaita) { + if (comptime build_options.adwaita) { try stdout.print(" - libadwaita : enabled\n", .{}); try stdout.print(" build : {s}\n", .{ gtk.ADW_VERSION_S, diff --git a/src/config/Config.zig b/src/config/Config.zig index e6b9d35ab..f2622d726 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -940,7 +940,7 @@ keybind: Keybinds = .{}, /// * `dark` - Use the dark theme regardless of system theme. /// * `ghostty` - Use the background and foreground colors specified in the /// Ghostty configuration. This is only supported on Linux builds with -/// libadwaita and `gtk-adwaita` enabled. +/// Adwaita and `gtk-adwaita` enabled. /// /// On macOS, if `macos-titlebar-style` is "tabs", the window theme will be /// automatically set based on the luminosity of the terminal background color. @@ -1618,12 +1618,12 @@ keybind: Keybinds = .{}, /// Determines the side of the screen that the GTK tab bar will stick to. /// Top, bottom, left, and right are supported. The default is top. /// -/// If this option has value `left` or `right` when using `libadwaita`, it falls +/// If this option has value `left` or `right` when using Adwaita, it falls /// back to `top`. @"gtk-tabs-location": GtkTabsLocation = .top, /// Determines the appearance of the top and bottom bars when using the -/// adwaita tab bar. This requires `gtk-adwaita` to be enabled (it is +/// Adwaita tab bar. This requires `gtk-adwaita` to be enabled (it is /// by default). /// /// Valid values are: @@ -1642,7 +1642,7 @@ keybind: Keybinds = .{}, /// which is the old style. @"gtk-wide-tabs": bool = true, -/// If `true` (default), Ghostty will enable libadwaita theme support. This +/// If `true` (default), Ghostty will enable Adwaita theme support. This /// will make `window-theme` work properly and will also allow Ghostty to /// properly respond to system theme changes, light/dark mode changing, etc. /// This requires a GTK4 desktop with a GTK4 theme. @@ -1653,7 +1653,7 @@ keybind: Keybinds = .{}, /// expected. /// /// This configuration only has an effect if Ghostty was built with -/// libadwaita support. +/// Adwaita support. @"gtk-adwaita": bool = true, /// If `true` (default), applications running in the terminal can show desktop From ca844ca3c064cfdd51a9c29a20dcbfe314c0d712 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sat, 2 Nov 2024 23:30:21 -0500 Subject: [PATCH 16/27] core: list valid options if an invalid value is detected parsing an enum --- src/cli/args.zig | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/cli/args.zig b/src/cli/args.zig index bfd40c633..9a8d1ae42 100644 --- a/src/cli/args.zig +++ b/src/cli/args.zig @@ -133,7 +133,29 @@ pub fn parse( error.OutOfMemory => return err, error.InvalidField => "unknown field", error.ValueRequired => "value required", - error.InvalidValue => "invalid value", + error.InvalidValue => msg: { + var buf = std.ArrayList(u8).init(arena_alloc); + errdefer buf.deinit(); + const writer = buf.writer(); + try writer.print("invalid value \"{?s}\"", .{value}); + const typeinfo = @typeInfo(T); + inline for (typeinfo.Struct.fields) |f| { + if (std.mem.eql(u8, key, f.name)) { + switch (@typeInfo(f.type)) { + .Enum => |e| { + try writer.print(", valid values are: ", .{}); + inline for (e.fields, 0..) |field, i| { + if (i != 0) try writer.print(", ", .{}); + try writer.print("{s}", .{field.name}); + } + }, + else => {}, + } + break; + } + } + break :msg try buf.toOwnedSliceSentinel(0); + }, else => try std.fmt.allocPrintZ( arena_alloc, "unknown error {}", From 3c493f2d0d1565a71e2fc7d935617a7d85ce08e2 Mon Sep 17 00:00:00 2001 From: Pepper Lebeck-Jobe Date: Sat, 9 Nov 2024 10:10:29 +0100 Subject: [PATCH 17/27] Fix copying the theme name Prior to this change both C and c would copy the path to the theme even though the help screen claimed that c would copy the theme name. There is a bug in libvaxis that results in both of these matches matching c: `key.matches('c', .{})` `key.matches('c', .{ .shift = true })` Tested: Before the change: 'c' copies path and 'C' copies path After the change: 'c' copies the name and 'C' copies the path --- src/cli/list_themes.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/list_themes.zig b/src/cli/list_themes.zig index 92cb57be2..0903fab94 100644 --- a/src/cli/list_themes.zig +++ b/src/cli/list_themes.zig @@ -382,8 +382,8 @@ const Preview = struct { self.tty.anyWriter(), self.themes[self.filtered.items[self.current]].theme, alloc, - ); - if (key.matches('c', .{ .shift = true })) + ) + else if (key.matches('c', .{ .shift = true })) try self.vx.copyToSystemClipboard( self.tty.anyWriter(), self.themes[self.filtered.items[self.current]].path, From 83c4d0077b7a06d487057fca3bddcc8a3685ed03 Mon Sep 17 00:00:00 2001 From: Nadir Fejzic Date: Sat, 9 Nov 2024 11:55:29 +0100 Subject: [PATCH 18/27] refactor: define `FreetypeLoadFlags` struct and default in `font.face` --- src/config.zig | 12 ------------ src/font/Collection.zig | 13 +------------ src/font/SharedGridSet.zig | 15 ++------------- src/font/face.zig | 16 +++++++++++++++- src/font/face/freetype.zig | 2 +- 5 files changed, 19 insertions(+), 39 deletions(-) diff --git a/src/config.zig b/src/config.zig index f2d4876ae..08d93a6a3 100644 --- a/src/config.zig +++ b/src/config.zig @@ -11,18 +11,6 @@ pub const url = @import("config/url.zig"); pub const FileFormatter = formatter.FileFormatter; pub const entryFormatter = formatter.entryFormatter; pub const formatEntry = formatter.formatEntry; -pub const FreetypeLoadFlags = switch (options.backend) { - .freetype, - .fontconfig_freetype, - .coretext_freetype, - => Config.FreetypeLoadFlags, - - .coretext, - .coretext_harfbuzz, - .coretext_noshape, - .web_canvas, - => void, -}; // Field types pub const ClipboardAccess = Config.ClipboardAccess; diff --git a/src/font/Collection.zig b/src/font/Collection.zig index b65b4bd2e..478c39ded 100644 --- a/src/font/Collection.zig +++ b/src/font/Collection.zig @@ -452,18 +452,7 @@ pub const LoadOptions = struct { /// for this is owned by the user and is not freed by the collection. metric_modifiers: Metrics.ModifierSet = .{}, - freetype_load_flags: config.FreetypeLoadFlags = switch (font.options.backend) { - .freetype, - .fontconfig_freetype, - .coretext_freetype, - => .{}, - - .coretext, - .coretext_harfbuzz, - .coretext_noshape, - .web_canvas, - => {}, - }, + freetype_load_flags: font.face.FreetypeLoadFlags = font.face.freetype_load_flags_default, pub fn deinit(self: *LoadOptions, alloc: Allocator) void { _ = self; diff --git a/src/font/SharedGridSet.zig b/src/font/SharedGridSet.zig index 49886c8f2..6459435a1 100644 --- a/src/font/SharedGridSet.zig +++ b/src/font/SharedGridSet.zig @@ -428,7 +428,7 @@ pub const DerivedConfig = struct { @"adjust-strikethrough-position": ?Metrics.Modifier, @"adjust-strikethrough-thickness": ?Metrics.Modifier, @"adjust-cursor-thickness": ?Metrics.Modifier, - @"freetype-load-flags": configpkg.FreetypeLoadFlags, + @"freetype-load-flags": font.face.FreetypeLoadFlags, /// Initialize a DerivedConfig. The config should be either a /// config.Config or another DerivedConfig to clone from. @@ -514,18 +514,7 @@ pub const Key = struct { /// font grid. font_size: DesiredSize = .{ .points = 12 }, - load_flags: configpkg.FreetypeLoadFlags = switch (font.options.backend) { - .freetype, - .fontconfig_freetype, - .coretext_freetype, - => .{}, - - .coretext, - .coretext_harfbuzz, - .coretext_noshape, - .web_canvas, - => {}, - }, + load_flags: configpkg.FreetypeLoadFlags = font.face.freetype_load_flags_default, const style_offsets_len = std.enums.directEnumArrayLen(Style, 0); const StyleOffsets = [style_offsets_len]usize; diff --git a/src/font/face.zig b/src/font/face.zig index 663a86672..24c9b0422 100644 --- a/src/font/face.zig +++ b/src/font/face.zig @@ -27,11 +27,25 @@ pub const Face = switch (options.backend) { /// using whatever platform method you can. pub const default_dpi = if (builtin.os.tag == .macos) 72 else 96; +pub const FreetypeLoadFlags = switch (options.backend) { + .freetype, + .fontconfig_freetype, + .coretext_freetype, + => config.Config.FreetypeLoadFlags, + + .coretext, + .coretext_harfbuzz, + .coretext_noshape, + .web_canvas, + => void, +}; +pub const freetype_load_flags_default = if (options.backend.hasFreetype()) .{} else {}; + /// Options for initializing a font face. pub const Options = struct { size: DesiredSize, metric_modifiers: ?*const Metrics.ModifierSet = null, - freetype_load_flags: config.FreetypeLoadFlags, + freetype_load_flags: FreetypeLoadFlags, }; /// The desired size for loading a font. diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index 5bbbb246b..8a1465d7e 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -36,7 +36,7 @@ pub const Face = struct { metrics: font.face.Metrics, /// Freetype load flags for this font face. - load_flags: config.FreetypeLoadFlags, + load_flags: font.face.FreetypeLoadFlags, /// Set quirks.disableDefaultFontFeatures quirks_disable_default_font_features: bool = false, From 0e0751ad5b82c9926dd6fabd127edb086a915a9e Mon Sep 17 00:00:00 2001 From: Nadir Fejzic Date: Sat, 9 Nov 2024 12:34:45 +0100 Subject: [PATCH 19/27] docs: write documentation for `freetype_load_flags` field --- src/font/Collection.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/font/Collection.zig b/src/font/Collection.zig index 478c39ded..f79c80936 100644 --- a/src/font/Collection.zig +++ b/src/font/Collection.zig @@ -452,6 +452,10 @@ pub const LoadOptions = struct { /// for this is owned by the user and is not freed by the collection. metric_modifiers: Metrics.ModifierSet = .{}, + /// Freetype Load Flags to use when loading glyphs. This is a list of + /// bitfield constants that controls operations to perform during glyph + /// loading. Only a subset is exposed for configuration, for the whole set + /// of flags see `pkg.freetype.face.LoadFlags`. freetype_load_flags: font.face.FreetypeLoadFlags = font.face.freetype_load_flags_default, pub fn deinit(self: *LoadOptions, alloc: Allocator) void { From 08720a6d23d71c9b4fd8ae5324a205122e6809b9 Mon Sep 17 00:00:00 2001 From: Nadir Fejzic Date: Sat, 9 Nov 2024 12:49:53 +0100 Subject: [PATCH 20/27] chore: fix typo --- src/config/Config.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index aef589571..4cc943859 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -290,7 +290,7 @@ const c = @cImport({ /// Freetype load flags to enable. The format of this is a list of flags to /// enable separated by commas. If you prefix a flag with `no-` then it is /// disabled. If you omit a flag, it's default value is used, so you must -/// explicitely disable flags you don't want. You can also use `true` or `false` +/// explicitly disable flags you don't want. You can also use `true` or `false` /// to turn all flags on or off. /// /// Available flags: From 4c086882758f0e855e2cfecddea44a105d575b84 Mon Sep 17 00:00:00 2001 From: Nadir Fejzic Date: Sat, 9 Nov 2024 12:50:51 +0100 Subject: [PATCH 21/27] refactor: remove unused imports --- src/config.zig | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/config.zig b/src/config.zig index 08d93a6a3..b9f214fc9 100644 --- a/src/config.zig +++ b/src/config.zig @@ -1,8 +1,6 @@ const builtin = @import("builtin"); const formatter = @import("config/formatter.zig"); -const font = @import("font/main.zig"); -const options = font.options; pub const Config = @import("config/Config.zig"); pub const string = @import("config/string.zig"); pub const edit = @import("config/edit.zig"); From 69aa579ee3bc4a42ba7c3f4f86d83d4c46b52711 Mon Sep 17 00:00:00 2001 From: Nadir Fejzic Date: Sat, 9 Nov 2024 12:51:28 +0100 Subject: [PATCH 22/27] fix: use ternary if expression and correct types --- src/font/SharedGridSet.zig | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/font/SharedGridSet.zig b/src/font/SharedGridSet.zig index 6459435a1..72bca7277 100644 --- a/src/font/SharedGridSet.zig +++ b/src/font/SharedGridSet.zig @@ -463,18 +463,7 @@ pub const DerivedConfig = struct { .@"adjust-strikethrough-position" = config.@"adjust-strikethrough-position", .@"adjust-strikethrough-thickness" = config.@"adjust-strikethrough-thickness", .@"adjust-cursor-thickness" = config.@"adjust-cursor-thickness", - .@"freetype-load-flags" = switch (font.options.backend) { - .freetype, - .fontconfig_freetype, - .coretext_freetype, - => config.@"freetype-load-flags", - - .coretext, - .coretext_harfbuzz, - .coretext_noshape, - .web_canvas, - => {}, - }, + .@"freetype-load-flags" = if (comptime font.options.backend.hasFreetype()) config.@"freetype-load-flags" else {}, // This must be last so the arena contains all our allocations // from above since Zig does assignment in order. @@ -514,7 +503,7 @@ pub const Key = struct { /// font grid. font_size: DesiredSize = .{ .points = 12 }, - load_flags: configpkg.FreetypeLoadFlags = font.face.freetype_load_flags_default, + load_flags: font.face.FreetypeLoadFlags = font.face.freetype_load_flags_default, const style_offsets_len = std.enums.directEnumArrayLen(Style, 0); const StyleOffsets = [style_offsets_len]usize; From 67966cb09189a05afa169e486f23e6cc1a57f7e6 Mon Sep 17 00:00:00 2001 From: Nadir Fejzic Date: Sat, 9 Nov 2024 13:06:36 +0100 Subject: [PATCH 23/27] refactor: add default value for `freetype_load_flags' --- src/font/face.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/font/face.zig b/src/font/face.zig index 24c9b0422..991bfb7e0 100644 --- a/src/font/face.zig +++ b/src/font/face.zig @@ -45,7 +45,7 @@ pub const freetype_load_flags_default = if (options.backend.hasFreetype()) .{} e pub const Options = struct { size: DesiredSize, metric_modifiers: ?*const Metrics.ModifierSet = null, - freetype_load_flags: FreetypeLoadFlags, + freetype_load_flags: FreetypeLoadFlags = freetype_load_flags_default, }; /// The desired size for loading a font. From 4def80ce1670f522ee87932e53eb9238a59f0caf Mon Sep 17 00:00:00 2001 From: Nadir Fejzic Date: Sat, 9 Nov 2024 13:09:15 +0100 Subject: [PATCH 24/27] refactor: use if expression instead of switch --- src/font/face.zig | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/font/face.zig b/src/font/face.zig index 991bfb7e0..7b51d660c 100644 --- a/src/font/face.zig +++ b/src/font/face.zig @@ -27,18 +27,7 @@ pub const Face = switch (options.backend) { /// using whatever platform method you can. pub const default_dpi = if (builtin.os.tag == .macos) 72 else 96; -pub const FreetypeLoadFlags = switch (options.backend) { - .freetype, - .fontconfig_freetype, - .coretext_freetype, - => config.Config.FreetypeLoadFlags, - - .coretext, - .coretext_harfbuzz, - .coretext_noshape, - .web_canvas, - => void, -}; +pub const FreetypeLoadFlags = if (options.backend.hasFreetype()) config.Config.FreetypeLoadFlags else void; pub const freetype_load_flags_default = if (options.backend.hasFreetype()) .{} else {}; /// Options for initializing a font face. From 783852ff48141c95d8c4dc19ec080ec1713328fe Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sat, 9 Nov 2024 12:23:53 -0600 Subject: [PATCH 25/27] ci: fix adwaita build --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 409d6b075..68ab0b48a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -324,10 +324,10 @@ jobs: run: nix develop -c zig build -Dapp-runtime=none test - name: Test GTK Build - run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-libadwaita=true -Demit-docs + run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-adwaita=true -Demit-docs - name: Test GTK Build (No Libadwaita) - run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-libadwaita=false -Demit-docs + run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-adwaita=false -Demit-docs - name: Test GLFW Build run: nix develop -c zig build -Dapp-runtime=glfw From 3eef6d205e508557d05a3e619400689a81f9aacf Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sat, 9 Nov 2024 12:49:40 -0600 Subject: [PATCH 26/27] core: address review comments - break formatting values out into a function so that we can catch errors and never fail - eliminate the use of toOwnedSentinelSlice since we are using an arena to clean up memory --- src/cli/args.zig | 74 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 24 deletions(-) diff --git a/src/cli/args.zig b/src/cli/args.zig index 9a8d1ae42..5fdaf6d8b 100644 --- a/src/cli/args.zig +++ b/src/cli/args.zig @@ -132,30 +132,8 @@ pub fn parse( // track more error messages. error.OutOfMemory => return err, error.InvalidField => "unknown field", - error.ValueRequired => "value required", - error.InvalidValue => msg: { - var buf = std.ArrayList(u8).init(arena_alloc); - errdefer buf.deinit(); - const writer = buf.writer(); - try writer.print("invalid value \"{?s}\"", .{value}); - const typeinfo = @typeInfo(T); - inline for (typeinfo.Struct.fields) |f| { - if (std.mem.eql(u8, key, f.name)) { - switch (@typeInfo(f.type)) { - .Enum => |e| { - try writer.print(", valid values are: ", .{}); - inline for (e.fields, 0..) |field, i| { - if (i != 0) try writer.print(", ", .{}); - try writer.print("{s}", .{field.name}); - } - }, - else => {}, - } - break; - } - } - break :msg try buf.toOwnedSliceSentinel(0); - }, + error.ValueRequired => formatValueRequired(T, arena_alloc, key) catch "value required", + error.InvalidValue => formatInvalidValue(T, arena_alloc, key, value) catch "invalid value", else => try std.fmt.allocPrintZ( arena_alloc, "unknown error {}", @@ -173,6 +151,54 @@ pub fn parse( } } +fn formatValueRequired( + comptime T: type, + arena_alloc: std.mem.Allocator, + key: []const u8, +) std.mem.Allocator.Error![:0]const u8 { + var buf = std.ArrayList(u8).init(arena_alloc); + errdefer buf.deinit(); + const writer = buf.writer(); + try writer.print("value required", .{}); + try formatValues(T, key, writer); + try writer.writeByte(0); + return buf.items[0 .. buf.items.len - 1 :0]; +} + +fn formatInvalidValue( + comptime T: type, + arena_alloc: std.mem.Allocator, + key: []const u8, + value: ?[]const u8, +) std.mem.Allocator.Error![:0]const u8 { + var buf = std.ArrayList(u8).init(arena_alloc); + errdefer buf.deinit(); + const writer = buf.writer(); + try writer.print("invalid value \"{?s}\"", .{value}); + try formatValues(T, key, writer); + try writer.writeByte(0); + return buf.items[0 .. buf.items.len - 1 :0]; +} + +fn formatValues(comptime T: type, key: []const u8, writer: anytype) std.mem.Allocator.Error!void { + const typeinfo = @typeInfo(T); + inline for (typeinfo.Struct.fields) |f| { + if (std.mem.eql(u8, key, f.name)) { + switch (@typeInfo(f.type)) { + .Enum => |e| { + try writer.print(", valid values are: ", .{}); + inline for (e.fields, 0..) |field, i| { + if (i != 0) try writer.print(", ", .{}); + try writer.print("{s}", .{field.name}); + } + }, + else => {}, + } + break; + } + } +} + /// Returns true if this type can track diagnostics. fn canTrackDiags(comptime T: type) bool { return @hasField(T, "_diagnostics"); From 3ee6577154b8b78e4113dbaec4c153ee1535e073 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 9 Nov 2024 09:37:03 -0800 Subject: [PATCH 27/27] some tweaks --- src/config.zig | 1 + src/config/Config.zig | 25 +++++++++++++++---------- src/font/SharedGridSet.zig | 15 ++++++++++----- src/font/face.zig | 9 +++++++-- src/font/face/freetype.zig | 7 ++++++- 5 files changed, 39 insertions(+), 18 deletions(-) diff --git a/src/config.zig b/src/config.zig index b9f214fc9..5af7832dd 100644 --- a/src/config.zig +++ b/src/config.zig @@ -16,6 +16,7 @@ pub const CopyOnSelect = Config.CopyOnSelect; pub const CustomShaderAnimation = Config.CustomShaderAnimation; pub const FontSyntheticStyle = Config.FontSyntheticStyle; pub const FontStyle = Config.FontStyle; +pub const FreetypeLoadFlags = Config.FreetypeLoadFlags; pub const Keybinds = Config.Keybinds; pub const MouseShiftCapture = Config.MouseShiftCapture; pub const NonNativeFullscreen = Config.NonNativeFullscreen; diff --git a/src/config/Config.zig b/src/config/Config.zig index 4cc943859..d8f007435 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -287,21 +287,24 @@ const c = @cImport({ /// terminals. Only new terminals will use the new configuration. @"grapheme-width-method": GraphemeWidthMethod = .unicode, -/// Freetype load flags to enable. The format of this is a list of flags to +/// FreeType load flags to enable. The format of this is a list of flags to /// enable separated by commas. If you prefix a flag with `no-` then it is /// disabled. If you omit a flag, it's default value is used, so you must /// explicitly disable flags you don't want. You can also use `true` or `false` /// to turn all flags on or off. /// +/// This configuration only applies to Ghostty builds that use FreeType. +/// This is usually the case only for Linux builds. macOS uses CoreText +/// and does not have an equivalent configuration. +/// /// Available flags: /// /// * `hinting` - Enable or disable hinting, enabled by default. -/// * `bitmap` - Enable or disable loading of any pre-rendered bitmap strikes, -/// enabled by default -/// * `force-autohint` - Use the freetype auto-hinter rather than the font's -/// native hinter. Enabled by default. -/// * `monochrome` - Instructs renderer to use 1-bit monochrome rendering. -/// This option doesn't impact the hinter. Enabled by default. +/// * `force-autohint` - Use the freetype auto-hinter rather than the +/// font's native hinter. Enabled by default. +/// * `monochrome` - Instructs renderer to use 1-bit monochrome +/// rendering. This option doesn't impact the hinter. +/// Enabled by default. /// * `autohint` - Use the freetype auto-hinter. Enabled by default. /// /// Example: `hinting`, `no-hinting`, `force-autohint`, `no-force-autohint` @@ -4587,10 +4590,12 @@ pub const GraphemeWidthMethod = enum { /// See freetype-load-flag pub const FreetypeLoadFlags = packed struct { + // The defaults here at the time of writing this match the defaults + // for Freetype itself. Ghostty hasn't made any opinionated changes + // to these defaults. hinting: bool = true, - bitmap: bool = true, - @"force-autohint": bool = false, - monochrome: bool = false, + @"force-autohint": bool = true, + monochrome: bool = true, autohint: bool = true, }; diff --git a/src/font/SharedGridSet.zig b/src/font/SharedGridSet.zig index 72bca7277..ac2fcbf8a 100644 --- a/src/font/SharedGridSet.zig +++ b/src/font/SharedGridSet.zig @@ -168,7 +168,7 @@ fn collection( .library = self.font_lib, .size = size, .metric_modifiers = key.metric_modifiers, - .freetype_load_flags = config.@"freetype-load-flags", + .freetype_load_flags = key.freetype_load_flags, }; var c = Collection.init(); @@ -463,7 +463,7 @@ pub const DerivedConfig = struct { .@"adjust-strikethrough-position" = config.@"adjust-strikethrough-position", .@"adjust-strikethrough-thickness" = config.@"adjust-strikethrough-thickness", .@"adjust-cursor-thickness" = config.@"adjust-cursor-thickness", - .@"freetype-load-flags" = if (comptime font.options.backend.hasFreetype()) config.@"freetype-load-flags" else {}, + .@"freetype-load-flags" = if (font.face.FreetypeLoadFlags != void) config.@"freetype-load-flags" else {}, // This must be last so the arena contains all our allocations // from above since Zig does assignment in order. @@ -503,7 +503,9 @@ pub const Key = struct { /// font grid. font_size: DesiredSize = .{ .points = 12 }, - load_flags: font.face.FreetypeLoadFlags = font.face.freetype_load_flags_default, + /// The freetype load flags configuration, only non-void if the + /// freetype backend is enabled. + freetype_load_flags: font.face.FreetypeLoadFlags = font.face.freetype_load_flags_default, const style_offsets_len = std.enums.directEnumArrayLen(Style, 0); const StyleOffsets = [style_offsets_len]usize; @@ -623,7 +625,10 @@ pub const Key = struct { .codepoint_map = codepoint_map, .metric_modifiers = metric_modifiers, .font_size = font_size, - .load_flags = config.@"freetype-load-flags", + .freetype_load_flags = if (font.face.FreetypeLoadFlags != void) + config.@"freetype-load-flags" + else + font.face.freetype_load_flags_default, }; } @@ -653,7 +658,7 @@ pub const Key = struct { for (self.descriptors) |d| d.hash(hasher); self.codepoint_map.hash(hasher); autoHash(hasher, self.metric_modifiers.count()); - autoHash(hasher, self.load_flags); + autoHash(hasher, self.freetype_load_flags); if (self.metric_modifiers.count() > 0) { inline for (@typeInfo(Metrics.Key).Enum.fields) |field| { const key = @field(Metrics.Key, field.name); diff --git a/src/font/face.zig b/src/font/face.zig index 7b51d660c..9f80c5637 100644 --- a/src/font/face.zig +++ b/src/font/face.zig @@ -27,8 +27,13 @@ pub const Face = switch (options.backend) { /// using whatever platform method you can. pub const default_dpi = if (builtin.os.tag == .macos) 72 else 96; -pub const FreetypeLoadFlags = if (options.backend.hasFreetype()) config.Config.FreetypeLoadFlags else void; -pub const freetype_load_flags_default = if (options.backend.hasFreetype()) .{} else {}; +/// These are the flags to customize how freetype loads fonts. This is +/// only non-void if the freetype backend is enabled. +pub const FreetypeLoadFlags = if (options.backend.hasFreetype()) + config.FreetypeLoadFlags +else + void; +pub const freetype_load_flags_default = if (FreetypeLoadFlags != void) .{} else {}; /// Options for initializing a font face. pub const Options = struct { diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index 8a1465d7e..683f80cc8 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -23,6 +23,11 @@ const config = @import("../../config.zig"); const log = std.log.scoped(.font_face); pub const Face = struct { + comptime { + // If we have the freetype backend, we should have load flags. + assert(font.face.FreetypeLoadFlags != void); + } + /// Our freetype library lib: freetype.Library, @@ -323,7 +328,7 @@ pub const Face = struct { // // This must be enabled for color faces though because those are // often colored bitmaps, which we support. - .no_bitmap = !self.face.hasColor() or !self.load_flags.bitmap, + .no_bitmap = !self.face.hasColor(), // use options from config .no_hinting = !self.load_flags.hinting,