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 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/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 */, 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/Surface.zig b/src/Surface.zig index 800655a3f..f7faa3aa9 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/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 618a4b5b3..d6fc3324e 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 db1888308..91a4ff45e 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 (new_split.direction) { @@ -276,7 +277,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). @@ -390,7 +391,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/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/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/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/args.zig b/src/cli/args.zig index bfd40c633..5fdaf6d8b 100644 --- a/src/cli/args.zig +++ b/src/cli/args.zig @@ -132,8 +132,8 @@ pub fn parse( // track more error messages. error.OutOfMemory => return err, error.InvalidField => "unknown field", - error.ValueRequired => "value required", - error.InvalidValue => "invalid value", + 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 {}", @@ -151,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"); 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, 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.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 e4dadd9c8..90e7de7bd 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -287,6 +287,29 @@ 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 +/// 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. +/// * `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-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, /// an error will be logged and no other directories will be searched. @@ -940,7 +963,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 +1641,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 +1665,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 +1676,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 @@ -4565,6 +4588,17 @@ pub const GraphemeWidthMethod = enum { unicode, }; +/// 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, + @"force-autohint": bool = true, + monochrome: bool = true, + autohint: bool = true, +}; + /// See linux-cgroup pub const LinuxCgroup = enum { never, 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/datastruct/intrusive_linked_list.zig b/src/datastruct/intrusive_linked_list.zig new file mode 100644 index 000000000..61bf8157c --- /dev/null +++ b/src/datastruct/intrusive_linked_list.zig @@ -0,0 +1,181 @@ +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(); + + /// 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: *Node, new_node: *Node) 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: *Node, new_node: *Node) 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: *Node) 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: *Node) 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: *Node) 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) ?*Node { + 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) ?*Node { + 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/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..4f45f9483 --- /dev/null +++ b/src/datastruct/main.zig @@ -0,0 +1,19 @@ +//! 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 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 { + @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/Collection.zig b/src/font/Collection.zig index 476787749..f79c80936 100644 --- a/src/font/Collection.zig +++ b/src/font/Collection.zig @@ -452,6 +452,12 @@ 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 { _ = self; _ = alloc; @@ -462,6 +468,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..ac2fcbf8a 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 = key.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": font.face.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" = 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. @@ -500,6 +503,10 @@ pub const Key = struct { /// font grid. font_size: DesiredSize = .{ .points = 12 }, + /// 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; @@ -618,6 +625,10 @@ pub const Key = struct { .codepoint_map = codepoint_map, .metric_modifiers = metric_modifiers, .font_size = font_size, + .freetype_load_flags = if (font.face.FreetypeLoadFlags != void) + config.@"freetype-load-flags" + else + font.face.freetype_load_flags_default, }; } @@ -647,6 +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.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 8bcfb8209..9f80c5637 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 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"); @@ -26,10 +27,19 @@ pub const Face = switch (options.backend) { /// using whatever platform method you can. pub const default_dpi = if (builtin.os.tag == .macos) 72 else 96; +/// 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 { size: DesiredSize, metric_modifiers: ?*const Metrics.ModifierSet = null, + freetype_load_flags: FreetypeLoadFlags = freetype_load_flags_default, }; /// The desired size for loading a font. diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index 7bb9ecbab..683f80cc8 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -18,10 +18,16 @@ const Library = font.Library; const convert = @import("freetype_convert.zig"); const fastmem = @import("../../fastmem.zig"); const quirks = @import("../../quirks.zig"); +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, @@ -34,6 +40,9 @@ pub const Face = struct { /// Metrics for this font face. These are useful for renderers. metrics: font.face.Metrics, + /// Freetype load flags for this font face. + load_flags: font.face.FreetypeLoadFlags, + /// Set quirks.disableDefaultFontFeatures quirks_disable_default_font_features: bool = false, @@ -77,6 +86,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); @@ -319,6 +329,12 @@ 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(), + + // use options from config + .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/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/font/sprite/Box.zig b/src/font/sprite/Box.zig index 477708c99..382aa4206 100644 --- a/src/font/sprite/Box.zig +++ b/src/font/sprite/Box.zig @@ -79,6 +79,17 @@ const Quads = packed struct(u4) { br: bool = false, }; +/// 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, + filled: bool = false, +}; + /// Alignment of a figure within a cell const Alignment = struct { horizontal: enum { @@ -474,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), // '╲' @@ -1302,6 +1313,330 @@ 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, .light), + // '' + 0x0f5d1 => self.vline_middle(canvas, .light), + // '' + 0x0f5d2 => self.draw_fading_line(canvas, .right, .light), + // '' + 0x0f5d3 => self.draw_fading_line(canvas, .left, .light), + // '' + 0x0f5d4 => self.draw_fading_line(canvas, .bottom, .light), + // '' + 0x0f5d5 => self.draw_fading_line(canvas, .top, .light), + // '' + 0x0f5d6 => try self.draw_arc(canvas, .br, .light), + // '' + 0x0f5d7 => try self.draw_arc(canvas, .bl, .light), + // '' + 0x0f5d8 => try self.draw_arc(canvas, .tr, .light), + // '' + 0x0f5d9 => try self.draw_arc(canvas, .tl, .light), + // '' + 0x0f5da => { + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .tr, .light); + }, + // '' + 0x0f5db => { + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .br, .light); + }, + // '' + 0x0f5dc => { + try self.draw_arc(canvas, .tr, .light); + try self.draw_arc(canvas, .br, .light); + }, + // '' + 0x0f5dd => { + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .tl, .light); + }, + // '' + 0x0f5de => { + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .bl, .light); + }, + // '' + 0x0f5df => { + try self.draw_arc(canvas, .tl, .light); + try self.draw_arc(canvas, .bl, .light); + }, + + // '' + 0x0f5e0 => { + try self.draw_arc(canvas, .bl, .light); + self.hline_middle(canvas, .light); + }, + // '' + 0x0f5e1 => { + try self.draw_arc(canvas, .br, .light); + self.hline_middle(canvas, .light); + }, + // '' + 0x0f5e2 => { + try self.draw_arc(canvas, .br, .light); + try self.draw_arc(canvas, .bl, .light); + }, + // '' + 0x0f5e3 => { + try self.draw_arc(canvas, .tl, .light); + self.hline_middle(canvas, .light); + }, + // '' + 0x0f5e4 => { + try self.draw_arc(canvas, .tr, .light); + self.hline_middle(canvas, .light); + }, + // '' + 0x0f5e5 => { + try self.draw_arc(canvas, .tr, .light); + try self.draw_arc(canvas, .tl, .light); + }, + // '' + 0x0f5e6 => { + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .tl, .light); + try self.draw_arc(canvas, .tr, .light); + }, + // '' + 0x0f5e7 => { + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .bl, .light); + try self.draw_arc(canvas, .br, .light); + }, + // '' + 0x0f5e8 => { + self.hline_middle(canvas, .light); + try self.draw_arc(canvas, .bl, .light); + try self.draw_arc(canvas, .tl, .light); + }, + // '' + 0x0f5e9 => { + self.hline_middle(canvas, .light); + try self.draw_arc(canvas, .tr, .light); + try self.draw_arc(canvas, .br, .light); + }, + // '' + 0x0f5ea => { + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .tl, .light); + try self.draw_arc(canvas, .br, .light); + }, + // '' + 0x0f5eb => { + self.vline_middle(canvas, .light); + try self.draw_arc(canvas, .tr, .light); + try self.draw_arc(canvas, .bl, .light); + }, + // '' + 0x0f5ec => { + self.hline_middle(canvas, .light); + try self.draw_arc(canvas, .tl, .light); + try self.draw_arc(canvas, .br, .light); + }, + // '' + 0x0f5ed => { + self.hline_middle(canvas, .light); + try self.draw_arc(canvas, .tr, .light); + try self.draw_arc(canvas, .bl, .light); + }, + // '' + 0x0f5ee => self.draw_branch_node(canvas, .{ .filled = true }, .light), + // '' + 0x0f5ef => self.draw_branch_node(canvas, .{}, .light), + + // '' + 0x0f5f0 => self.draw_branch_node(canvas, .{ + .right = true, + .filled = true, + }, .light), + // '' + 0x0f5f1 => self.draw_branch_node(canvas, .{ + .right = true, + }, .light), + // '' + 0x0f5f2 => self.draw_branch_node(canvas, .{ + .left = true, + .filled = true, + }, .light), + // '' + 0x0f5f3 => self.draw_branch_node(canvas, .{ + .left = true, + }, .light), + // '' + 0x0f5f4 => self.draw_branch_node(canvas, .{ + .left = true, + .right = true, + .filled = true, + }, .light), + // '' + 0x0f5f5 => self.draw_branch_node(canvas, .{ + .left = true, + .right = true, + }, .light), + // '' + 0x0f5f6 => self.draw_branch_node(canvas, .{ + .down = true, + .filled = true, + }, .light), + // '' + 0x0f5f7 => self.draw_branch_node(canvas, .{ + .down = true, + }, .light), + // '' + 0x0f5f8 => self.draw_branch_node(canvas, .{ + .up = true, + .filled = true, + }, .light), + // '' + 0x0f5f9 => self.draw_branch_node(canvas, .{ + .up = true, + }, .light), + // '' + 0x0f5fa => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + .filled = true, + }, .light), + // '' + 0x0f5fb => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + }, .light), + // '' + 0x0f5fc => self.draw_branch_node(canvas, .{ + .right = true, + .down = true, + .filled = true, + }, .light), + // '' + 0x0f5fd => self.draw_branch_node(canvas, .{ + .right = true, + .down = true, + }, .light), + // '' + 0x0f5fe => self.draw_branch_node(canvas, .{ + .left = true, + .down = true, + .filled = true, + }, .light), + // '' + 0x0f5ff => self.draw_branch_node(canvas, .{ + .left = true, + .down = true, + }, .light), + + // '' + 0x0f600 => self.draw_branch_node(canvas, .{ + .up = true, + .right = true, + .filled = true, + }, .light), + // '' + 0x0f601 => self.draw_branch_node(canvas, .{ + .up = true, + .right = true, + }, .light), + // '' + 0x0f602 => self.draw_branch_node(canvas, .{ + .up = true, + .left = true, + .filled = true, + }, .light), + // '' + 0x0f603 => self.draw_branch_node(canvas, .{ + .up = true, + .left = true, + }, .light), + // '' + 0x0f604 => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + .right = true, + .filled = true, + }, .light), + // '' + 0x0f605 => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + .right = true, + }, .light), + // '' + 0x0f606 => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + .left = true, + .filled = true, + }, .light), + // '' + 0x0f607 => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + .left = true, + }, .light), + // '' + 0x0f608 => self.draw_branch_node(canvas, .{ + .down = true, + .left = true, + .right = true, + .filled = true, + }, .light), + // '' + 0x0f609 => self.draw_branch_node(canvas, .{ + .down = true, + .left = true, + .right = true, + }, .light), + // '' + 0x0f60a => self.draw_branch_node(canvas, .{ + .up = true, + .left = true, + .right = true, + .filled = true, + }, .light), + // '' + 0x0f60b => self.draw_branch_node(canvas, .{ + .up = true, + .left = true, + .right = true, + }, .light), + // '' + 0x0f60c => self.draw_branch_node(canvas, .{ + .up = true, + .down = true, + .left = true, + .right = true, + .filled = true, + }, .light), + // '' + 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. @intFromEnum(Sprite.cursor_rect) => self.draw_cursor_rect(canvas), @@ -1793,6 +2128,135 @@ fn draw_cell_diagonal( ) catch {}; } +fn draw_fading_line( + self: Box, + canvas: *font.sprite.Canvas, + comptime to: Edge, + 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); + + // 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; + + // 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, + .pattern = .{ + .opaque_pattern = .{ + .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, + }, + }, + .line_width = float_thick, + }; + + var path = z2d.Path.init(canvas.alloc); + defer path.deinit(); + + // 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 (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(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; + } +} + fn draw_circle( self: Box, canvas: *font.sprite.Canvas, @@ -2118,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); @@ -2534,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 af82bb731..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, diff --git a/src/font/sprite/testdata/Box.ppm b/src/font/sprite/testdata/Box.ppm index 29ae539e7..1301a4299 100644 Binary files a/src/font/sprite/testdata/Box.ppm and b/src/font/sprite/testdata/Box.ppm differ diff --git a/src/input/Binding.zig b/src/input/Binding.zig index b2b7ea0c1..7094a5a61 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 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/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"); 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/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/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..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); @@ -69,15 +79,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 +275,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 +286,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 +422,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 +446,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 +636,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 +726,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 +754,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 +804,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 +1046,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 +1197,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 +1317,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 +1336,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 +1401,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 +1412,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 +1425,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 +1441,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 +1495,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 +1503,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; @@ -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 @@ -1723,8 +1734,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 +1748,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 +1798,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 +1833,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 +1911,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 +1953,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 +1996,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 +2027,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 +2048,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 +2080,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 +2107,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 +2125,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 +2148,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 +2172,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 +2200,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 +2232,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 +2245,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 +2255,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 +2288,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 +2297,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 +2358,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 +2373,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 +2405,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 +2440,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 +2480,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 +2511,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 +2561,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 +2589,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 +2668,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 +2689,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 +2728,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 +2815,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 +2837,7 @@ pub const PageIterator = struct { } break :count .{ - .page = row.page, + .node = row.node, .start = row.y, .end = row.y + len, }; @@ -2829,16 +2846,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 +2864,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 +2881,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 +2906,7 @@ pub const PageIterator = struct { } break :count .{ - .page = row.page, + .node = row.node, .start = row.y - len, .end = row.y - 1, }; @@ -2898,17 +2915,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 +2936,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 +2945,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 +3003,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 +3018,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 +3038,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 +3065,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 +3077,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 +3143,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 +3151,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 +3162,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 +3173,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 +3306,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 +3314,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 +3333,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 +3344,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 +3362,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 +3376,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 +3406,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 +3441,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 +3482,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 +3519,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 +3527,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 +3541,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 +3566,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 +3609,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 +3621,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 +3643,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 +3652,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 +3677,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 +3686,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 +3715,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 +3724,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 +4176,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 +4212,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 +4225,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 +4373,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 +4401,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 +4436,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 +4458,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 +4487,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 +4495,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 +4525,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 +4705,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 +4726,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 +4747,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 +4768,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 +4791,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 +4885,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 +4919,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 +4949,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 +4985,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 +5013,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 +5686,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 +5710,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 +5746,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 +5765,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 +5938,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 +5958,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 +5979,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 +6019,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 +6058,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 +6163,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 +6214,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 +6470,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 +6554,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 +7252,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 +7296,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 +7304,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 +7313,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 +7321,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 +7372,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 +7380,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 +7394,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 +7402,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 +7738,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 +7746,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 +7794,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 +7802,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 +7841,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 +7849,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 +7948,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 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);