Merge branch 'ghostty-org:main' into feature-custom-split-size

This commit is contained in:
I Duran
2024-11-10 16:21:16 +03:00
committed by GitHub
53 changed files with 1592 additions and 656 deletions

View File

@ -324,10 +324,10 @@ jobs:
run: nix develop -c zig build -Dapp-runtime=none test run: nix develop -c zig build -Dapp-runtime=none test
- name: Test GTK Build - 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) - 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 - name: Test GLFW Build
run: nix develop -c zig build -Dapp-runtime=glfw run: nix develop -c zig build -Dapp-runtime=glfw

View File

@ -92,10 +92,10 @@ pub fn build(b: *std.Build) !void {
"The app runtime to use. Not all values supported on all platforms.", "The app runtime to use. Not all values supported on all platforms.",
) orelse renderer.Impl.default(target.result, wasm_target); ) orelse renderer.Impl.default(target.result, wasm_target);
config.libadwaita = b.option( config.adwaita = b.option(
bool, bool,
"gtk-libadwaita", "gtk-adwaita",
"Enables the use of libadwaita when using the gtk rendering backend.", "Enables the use of Adwaita when using the GTK rendering backend.",
) orelse true; ) orelse true;
const conformance = b.option( const conformance = b.option(
@ -1321,7 +1321,7 @@ fn addDeps(
.gtk => { .gtk => {
step.linkSystemLibrary2("gtk4", dynamic_link_opts); 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"); const gresource = @import("src/apprt/gtk/gresource.zig");

View File

@ -7,6 +7,7 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
29C15B1D2CDC3B2900520DD4 /* bat in Resources */ = {isa = PBXBuildFile; fileRef = 29C15B1C2CDC3B2000520DD4 /* bat */; };
55154BE02B33911F001622DC /* ghostty in Resources */ = {isa = PBXBuildFile; fileRef = 55154BDF2B33911F001622DC /* ghostty */; }; 55154BE02B33911F001622DC /* ghostty in Resources */ = {isa = PBXBuildFile; fileRef = 55154BDF2B33911F001622DC /* ghostty */; };
552964E62B34A9B400030505 /* vim in Resources */ = {isa = PBXBuildFile; fileRef = 552964E52B34A9B400030505 /* vim */; }; 552964E62B34A9B400030505 /* vim in Resources */ = {isa = PBXBuildFile; fileRef = 552964E52B34A9B400030505 /* vim */; };
857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 857F63802A5E64F200CA4815 /* MainMenu.xib */; }; 857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 857F63802A5E64F200CA4815 /* MainMenu.xib */; };
@ -95,6 +96,7 @@
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
29C15B1C2CDC3B2000520DD4 /* bat */ = {isa = PBXFileReference; lastKnownFileType = folder; name = bat; path = "../zig-out/share/bat"; sourceTree = "<group>"; };
3B39CAA42B33949B00DABEB8 /* GhosttyReleaseLocal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = GhosttyReleaseLocal.entitlements; sourceTree = "<group>"; }; 3B39CAA42B33949B00DABEB8 /* GhosttyReleaseLocal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = GhosttyReleaseLocal.entitlements; sourceTree = "<group>"; };
55154BDF2B33911F001622DC /* ghostty */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ghostty; path = "../zig-out/share/ghostty"; sourceTree = "<group>"; }; 55154BDF2B33911F001622DC /* ghostty */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ghostty; path = "../zig-out/share/ghostty"; sourceTree = "<group>"; };
552964E52B34A9B400030505 /* vim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = vim; path = "../zig-out/share/vim"; sourceTree = "<group>"; }; 552964E52B34A9B400030505 /* vim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = vim; path = "../zig-out/share/vim"; sourceTree = "<group>"; };
@ -366,6 +368,7 @@
A5A1F8862A489D7400D1E8BC /* Resources */ = { A5A1F8862A489D7400D1E8BC /* Resources */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
29C15B1C2CDC3B2000520DD4 /* bat */,
55154BDF2B33911F001622DC /* ghostty */, 55154BDF2B33911F001622DC /* ghostty */,
552964E52B34A9B400030505 /* vim */, 552964E52B34A9B400030505 /* vim */,
A586167B2B7703CC009BDB1D /* fish */, A586167B2B7703CC009BDB1D /* fish */,
@ -538,6 +541,7 @@
A5E112932AF73E6E00C6E0C2 /* ClipboardConfirmation.xib in Resources */, A5E112932AF73E6E00C6E0C2 /* ClipboardConfirmation.xib in Resources */,
A5CDF1912AAF9A5800513312 /* ConfigurationErrors.xib in Resources */, A5CDF1912AAF9A5800513312 /* ConfigurationErrors.xib in Resources */,
857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */, 857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */,
29C15B1D2CDC3B2900520DD4 /* bat in Resources */,
A596309A2AEE1C6400D64628 /* Terminal.xib in Resources */, A596309A2AEE1C6400D64628 /* Terminal.xib in Resources */,
A586167C2B7703CC009BDB1D /* fish in Resources */, A586167C2B7703CC009BDB1D /* fish in Resources */,
55154BE02B33911F001622DC /* ghostty in Resources */, 55154BE02B33911F001622DC /* ghostty in Resources */,

View File

@ -13,7 +13,7 @@ const Surface = @import("Surface.zig");
const tracy = @import("tracy"); const tracy = @import("tracy");
const input = @import("input.zig"); const input = @import("input.zig");
const Config = @import("config.zig").Config; 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 renderer = @import("renderer.zig");
const font = @import("font/main.zig"); const font = @import("font/main.zig");
const internal_os = @import("os/main.zig"); const internal_os = @import("os/main.zig");

View File

@ -3115,7 +3115,7 @@ fn processLinks(self: *Surface, pos: apprt.CursorPos) !bool {
/// if there is no hyperlink. /// if there is no hyperlink.
fn osc8URI(self: *Surface, pin: terminal.Pin) ?[]const u8 { fn osc8URI(self: *Surface, pin: terminal.Pin) ?[]const u8 {
_ = self; _ = self;
const page = &pin.page.data; const page = &pin.node.data;
const cell = pin.rowAndCell().cell; const cell = pin.rowAndCell().cell;
const link_id = page.lookupHyperlink(cell) orelse return null; const link_id = page.lookupHyperlink(cell) orelse return null;
const entry = page.hyperlink_set.get(page.memory, link_id); const entry = page.hyperlink_set.get(page.memory, link_id);

View File

@ -471,12 +471,12 @@ pub fn performAction(
.mouse_shape => try self.setMouseShape(target, value), .mouse_shape => try self.setMouseShape(target, value),
.mouse_over_link => self.setMouseOverLink(target, value), .mouse_over_link => self.setMouseOverLink(target, value),
.toggle_tab_overview => self.toggleTabOverview(target), .toggle_tab_overview => self.toggleTabOverview(target),
.toggle_split_zoom => self.toggleSplitZoom(target),
.toggle_window_decorations => self.toggleWindowDecorations(target), .toggle_window_decorations => self.toggleWindowDecorations(target),
.quit_timer => self.quitTimer(value), .quit_timer => self.quitTimer(value),
// Unimplemented // Unimplemented
.close_all_windows, .close_all_windows,
.toggle_split_zoom,
.toggle_quick_terminal, .toggle_quick_terminal,
.toggle_visibility, .toggle_visibility,
.size_limit, .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( fn toggleWindowDecorations(
_: *App, _: *App,
target: apprt.Target, target: apprt.Target,

View File

@ -77,6 +77,7 @@ pub fn init(
}); });
errdefer surface.destroy(alloc); errdefer surface.destroy(alloc);
sibling.dimSurface(); sibling.dimSurface();
sibling.setSplitZoom(false);
// Create the actual GTKPaned, attach the proper children. // Create the actual GTKPaned, attach the proper children.
const orientation: c_uint = switch (new_split.direction) { 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. /// Update the paned children to represent the current state.
/// This should be called anytime the top/left or bottom/right /// This should be called anytime the top/left or bottom/right
/// element is changed. /// 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 // We have to set both to null. If we overwrite the pane with
// the same value, then GTK bugs out (the GL area unrealizes // the same value, then GTK bugs out (the GL area unrealizes
// and never rerealizes). // and never rerealizes).
@ -390,7 +391,15 @@ fn directionNext(self: *const Split, from: Side) ?struct {
} }
} }
fn removeChildren(self: *const Split) void { pub fn detachTopLeft(self: *const Split) void {
c.gtk_paned_set_start_child(@ptrCast(self.paned), null); c.gtk_paned_set_start_child(self.paned, null);
c.gtk_paned_set_end_child(@ptrCast(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();
} }

View File

@ -330,6 +330,9 @@ url_widget: ?URLWidget = null,
/// The overlay that shows resizing information. /// The overlay that shows resizing information.
resize_overlay: ResizeOverlay = .{}, 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 /// If non-null this is the widget on the overlay which dims the surface when it is unfocused
unfocused_widget: ?*c.GtkWidget = null, unfocused_widget: ?*c.GtkWidget = null,
@ -643,6 +646,8 @@ pub fn redraw(self: *Surface) void {
/// Close this surface. /// Close this surface.
pub fn close(self: *Surface, processActive: bool) void { pub fn close(self: *Surface, processActive: bool) void {
self.setSplitZoom(false);
// If we're not part of a window hierarchy, we never confirm // If we're not part of a window hierarchy, we never confirm
// so we can just directly remove ourselves and exit. // so we can just directly remove ourselves and exit.
const window = self.container.window() orelse { 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 { 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)); const widget = @as(*c.GtkWidget, @ptrCast(self.gl_area));
_ = c.gtk_widget_grab_focus(widget); _ = c.gtk_widget_grab_focus(widget);
@ -801,7 +815,7 @@ pub fn grabFocus(self: *Surface) void {
fn updateTitleLabels(self: *Surface) void { fn updateTitleLabels(self: *Surface) void {
// If we have no title, then we have nothing to update. // 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 we have a tab and are the focused child, then we have to update the tab
if (self.container.tab()) |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 { pub fn setTitle(self: *Surface, slice: [:0]const u8) !void {
const alloc = self.app.core_app.alloc; 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); errdefer alloc.free(copy);
if (self.title_text) |old| alloc.free(old); 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 { 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( pub fn setMouseShape(
@ -1875,3 +1906,41 @@ pub fn present(self: *Surface) void {
self.grabFocus(); 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);
}

View File

@ -12,7 +12,7 @@ const Config = @import("../../config.zig").Config;
/// This must be `inline` so that the comptime check noops conditional /// This must be `inline` so that the comptime check noops conditional
/// paths that are not enabled. /// paths that are not enabled.
pub inline fn enabled(config: *const Config) bool { pub inline fn enabled(config: *const Config) bool {
return build_options.libadwaita and return build_options.adwaita and
config.@"gtk-adwaita"; config.@"gtk-adwaita";
} }
@ -30,7 +30,7 @@ pub fn versionAtLeast(
comptime minor: u16, comptime minor: u16,
comptime micro: u16, comptime micro: u16,
) bool { ) 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, // If our header has lower versions than the given version,
// we can return false immediately. This prevents us from // we can return false immediately. This prevents us from

View File

@ -1,7 +1,7 @@
/// Imported C API directly from header files /// Imported C API directly from header files
pub const c = @cImport({ pub const c = @cImport({
@cInclude("gtk/gtk.h"); @cInclude("gtk/gtk.h");
if (@import("build_options").libadwaita) { if (@import("build_options").adwaita) {
@cInclude("libadwaita-1/adwaita.h"); @cInclude("libadwaita-1/adwaita.h");
} }

View File

@ -12,7 +12,7 @@ pub const fish_completions = comptimeGenerateFishCompletions();
fn comptimeGenerateFishCompletions() []const u8 { fn comptimeGenerateFishCompletions() []const u8 {
comptime { comptime {
@setEvalBranchQuota(17000); @setEvalBranchQuota(18000);
var counter = std.io.countingWriter(std.io.null_writer); var counter = std.io.countingWriter(std.io.null_writer);
try writeFishCompletions(&counter.writer()); try writeFishCompletions(&counter.writer());

View File

@ -26,7 +26,7 @@ pub fn genConfig(writer: anytype, cli: bool) !void {
\\ \\
); );
@setEvalBranchQuota(2000); @setEvalBranchQuota(3000);
inline for (@typeInfo(Config).Struct.fields) |field| { inline for (@typeInfo(Config).Struct.fields) |field| {
if (field.name[0] == '_') continue; if (field.name[0] == '_') continue;

View File

@ -21,7 +21,7 @@ const WasmTarget = @import("os/wasm/target.zig").Target;
pub const BuildConfig = struct { pub const BuildConfig = struct {
version: std.SemanticVersion = .{ .major = 0, .minor = 0, .patch = 0 }, version: std.SemanticVersion = .{ .major = 0, .minor = 0, .patch = 0 },
flatpak: bool = false, flatpak: bool = false,
libadwaita: bool = false, adwaita: bool = false,
app_runtime: apprt.Runtime = .none, app_runtime: apprt.Runtime = .none,
renderer: rendererpkg.Impl = .opengl, renderer: rendererpkg.Impl = .opengl,
font_backend: font.Backend = .freetype, font_backend: font.Backend = .freetype,
@ -40,7 +40,7 @@ pub const BuildConfig = struct {
// We need to break these down individual because addOption doesn't // We need to break these down individual because addOption doesn't
// support all types. // support all types.
step.addOption(bool, "flatpak", self.flatpak); 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(apprt.Runtime, "app_runtime", self.app_runtime);
step.addOption(font.Backend, "font_backend", self.font_backend); step.addOption(font.Backend, "font_backend", self.font_backend);
step.addOption(rendererpkg.Impl, "renderer", self.renderer); step.addOption(rendererpkg.Impl, "renderer", self.renderer);
@ -67,7 +67,7 @@ pub const BuildConfig = struct {
return .{ return .{
.version = options.app_version, .version = options.app_version,
.flatpak = options.flatpak, .flatpak = options.flatpak,
.libadwaita = options.libadwaita, .adwaita = options.adwaita,
.app_runtime = std.meta.stringToEnum(apprt.Runtime, @tagName(options.app_runtime)).?, .app_runtime = std.meta.stringToEnum(apprt.Runtime, @tagName(options.app_runtime)).?,
.font_backend = std.meta.stringToEnum(font.Backend, @tagName(options.font_backend)).?, .font_backend = std.meta.stringToEnum(font.Backend, @tagName(options.font_backend)).?,
.renderer = std.meta.stringToEnum(rendererpkg.Impl, @tagName(options.renderer)).?, .renderer = std.meta.stringToEnum(rendererpkg.Impl, @tagName(options.renderer)).?,

View File

@ -132,8 +132,8 @@ pub fn parse(
// track more error messages. // track more error messages.
error.OutOfMemory => return err, error.OutOfMemory => return err,
error.InvalidField => "unknown field", error.InvalidField => "unknown field",
error.ValueRequired => "value required", error.ValueRequired => formatValueRequired(T, arena_alloc, key) catch "value required",
error.InvalidValue => "invalid value", error.InvalidValue => formatInvalidValue(T, arena_alloc, key, value) catch "invalid value",
else => try std.fmt.allocPrintZ( else => try std.fmt.allocPrintZ(
arena_alloc, arena_alloc,
"unknown error {}", "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. /// Returns true if this type can track diagnostics.
fn canTrackDiags(comptime T: type) bool { fn canTrackDiags(comptime T: type) bool {
return @hasField(T, "_diagnostics"); return @hasField(T, "_diagnostics");

View File

@ -382,8 +382,8 @@ const Preview = struct {
self.tty.anyWriter(), self.tty.anyWriter(),
self.themes[self.filtered.items[self.current]].theme, self.themes[self.filtered.items[self.current]].theme,
alloc, alloc,
); )
if (key.matches('c', .{ .shift = true })) else if (key.matches('c', .{ .shift = true }))
try self.vx.copyToSystemClipboard( try self.vx.copyToSystemClipboard(
self.tty.anyWriter(), self.tty.anyWriter(),
self.themes[self.filtered.items[self.current]].path, self.themes[self.filtered.items[self.current]].path,

View File

@ -42,7 +42,7 @@ pub fn run(alloc: Allocator) !u8 {
gtk.gtk_get_minor_version(), gtk.gtk_get_minor_version(),
gtk.gtk_get_micro_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(" - libadwaita : enabled\n", .{});
try stdout.print(" build : {s}\n", .{ try stdout.print(" build : {s}\n", .{
gtk.ADW_VERSION_S, gtk.ADW_VERSION_S,

View File

@ -16,6 +16,7 @@ pub const CopyOnSelect = Config.CopyOnSelect;
pub const CustomShaderAnimation = Config.CustomShaderAnimation; pub const CustomShaderAnimation = Config.CustomShaderAnimation;
pub const FontSyntheticStyle = Config.FontSyntheticStyle; pub const FontSyntheticStyle = Config.FontSyntheticStyle;
pub const FontStyle = Config.FontStyle; pub const FontStyle = Config.FontStyle;
pub const FreetypeLoadFlags = Config.FreetypeLoadFlags;
pub const Keybinds = Config.Keybinds; pub const Keybinds = Config.Keybinds;
pub const MouseShiftCapture = Config.MouseShiftCapture; pub const MouseShiftCapture = Config.MouseShiftCapture;
pub const NonNativeFullscreen = Config.NonNativeFullscreen; pub const NonNativeFullscreen = Config.NonNativeFullscreen;

View File

@ -287,6 +287,29 @@ const c = @cImport({
/// terminals. Only new terminals will use the new configuration. /// terminals. Only new terminals will use the new configuration.
@"grapheme-width-method": GraphemeWidthMethod = .unicode, @"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 /// 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, /// 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. /// 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. /// * `dark` - Use the dark theme regardless of system theme.
/// * `ghostty` - Use the background and foreground colors specified in the /// * `ghostty` - Use the background and foreground colors specified in the
/// Ghostty configuration. This is only supported on Linux builds with /// 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 /// On macOS, if `macos-titlebar-style` is "tabs", the window theme will be
/// automatically set based on the luminosity of the terminal background color. /// 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. /// 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. /// 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`. /// back to `top`.
@"gtk-tabs-location": GtkTabsLocation = .top, @"gtk-tabs-location": GtkTabsLocation = .top,
/// Determines the appearance of the top and bottom bars when using the /// 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). /// by default).
/// ///
/// Valid values are: /// Valid values are:
@ -1642,7 +1665,7 @@ keybind: Keybinds = .{},
/// which is the old style. /// which is the old style.
@"gtk-wide-tabs": bool = true, @"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 /// will make `window-theme` work properly and will also allow Ghostty to
/// properly respond to system theme changes, light/dark mode changing, etc. /// properly respond to system theme changes, light/dark mode changing, etc.
/// This requires a GTK4 desktop with a GTK4 theme. /// This requires a GTK4 desktop with a GTK4 theme.
@ -1653,7 +1676,7 @@ keybind: Keybinds = .{},
/// expected. /// expected.
/// ///
/// This configuration only has an effect if Ghostty was built with /// This configuration only has an effect if Ghostty was built with
/// libadwaita support. /// Adwaita support.
@"gtk-adwaita": bool = true, @"gtk-adwaita": bool = true,
/// If `true` (default), applications running in the terminal can show desktop /// If `true` (default), applications running in the terminal can show desktop
@ -4565,6 +4588,17 @@ pub const GraphemeWidthMethod = enum {
unicode, 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 /// See linux-cgroup
pub const LinuxCgroup = enum { pub const LinuxCgroup = enum {
never, never,

View File

@ -1,4 +1,4 @@
const fastmem = @import("./fastmem.zig"); const fastmem = @import("../fastmem.zig");
const std = @import("std"); const std = @import("std");
const assert = std.debug.assert; const assert = std.debug.assert;

View File

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const assert = std.debug.assert; const assert = std.debug.assert;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const fastmem = @import("fastmem.zig"); const fastmem = @import("../fastmem.zig");
/// Returns a circular buffer containing type T. /// Returns a circular buffer containing type T.
pub fn CircBuf(comptime T: type, comptime default: T) type { pub fn CircBuf(comptime T: type, comptime default: T) type {

View File

@ -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);
}

19
src/datastruct/main.zig Normal file
View File

@ -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());
}

View File

@ -452,6 +452,12 @@ pub const LoadOptions = struct {
/// for this is owned by the user and is not freed by the collection. /// for this is owned by the user and is not freed by the collection.
metric_modifiers: Metrics.ModifierSet = .{}, 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 { pub fn deinit(self: *LoadOptions, alloc: Allocator) void {
_ = self; _ = self;
_ = alloc; _ = alloc;
@ -462,6 +468,7 @@ pub const LoadOptions = struct {
return .{ return .{
.size = self.size, .size = self.size,
.metric_modifiers = &self.metric_modifiers, .metric_modifiers = &self.metric_modifiers,
.freetype_load_flags = self.freetype_load_flags,
}; };
} }
}; };

View File

@ -168,6 +168,7 @@ fn collection(
.library = self.font_lib, .library = self.font_lib,
.size = size, .size = size,
.metric_modifiers = key.metric_modifiers, .metric_modifiers = key.metric_modifiers,
.freetype_load_flags = key.freetype_load_flags,
}; };
var c = Collection.init(); var c = Collection.init();
@ -427,6 +428,7 @@ pub const DerivedConfig = struct {
@"adjust-strikethrough-position": ?Metrics.Modifier, @"adjust-strikethrough-position": ?Metrics.Modifier,
@"adjust-strikethrough-thickness": ?Metrics.Modifier, @"adjust-strikethrough-thickness": ?Metrics.Modifier,
@"adjust-cursor-thickness": ?Metrics.Modifier, @"adjust-cursor-thickness": ?Metrics.Modifier,
@"freetype-load-flags": font.face.FreetypeLoadFlags,
/// Initialize a DerivedConfig. The config should be either a /// Initialize a DerivedConfig. The config should be either a
/// config.Config or another DerivedConfig to clone from. /// 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-position" = config.@"adjust-strikethrough-position",
.@"adjust-strikethrough-thickness" = config.@"adjust-strikethrough-thickness", .@"adjust-strikethrough-thickness" = config.@"adjust-strikethrough-thickness",
.@"adjust-cursor-thickness" = config.@"adjust-cursor-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 // This must be last so the arena contains all our allocations
// from above since Zig does assignment in order. // from above since Zig does assignment in order.
@ -500,6 +503,10 @@ pub const Key = struct {
/// font grid. /// font grid.
font_size: DesiredSize = .{ .points = 12 }, 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 style_offsets_len = std.enums.directEnumArrayLen(Style, 0);
const StyleOffsets = [style_offsets_len]usize; const StyleOffsets = [style_offsets_len]usize;
@ -618,6 +625,10 @@ pub const Key = struct {
.codepoint_map = codepoint_map, .codepoint_map = codepoint_map,
.metric_modifiers = metric_modifiers, .metric_modifiers = metric_modifiers,
.font_size = font_size, .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); for (self.descriptors) |d| d.hash(hasher);
self.codepoint_map.hash(hasher); self.codepoint_map.hash(hasher);
autoHash(hasher, self.metric_modifiers.count()); autoHash(hasher, self.metric_modifiers.count());
autoHash(hasher, self.freetype_load_flags);
if (self.metric_modifiers.count() > 0) { if (self.metric_modifiers.count() > 0) {
inline for (@typeInfo(Metrics.Key).Enum.fields) |field| { inline for (@typeInfo(Metrics.Key).Enum.fields) |field| {
const key = @field(Metrics.Key, field.name); const key = @field(Metrics.Key, field.name);

View File

@ -2,6 +2,7 @@ const std = @import("std");
const builtin = @import("builtin"); const builtin = @import("builtin");
const options = @import("main.zig").options; const options = @import("main.zig").options;
pub const Metrics = @import("face/Metrics.zig"); pub const Metrics = @import("face/Metrics.zig");
const config = @import("../config.zig");
const freetype = @import("face/freetype.zig"); const freetype = @import("face/freetype.zig");
const coretext = @import("face/coretext.zig"); const coretext = @import("face/coretext.zig");
pub const web_canvas = @import("face/web_canvas.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. /// using whatever platform method you can.
pub const default_dpi = if (builtin.os.tag == .macos) 72 else 96; 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. /// Options for initializing a font face.
pub const Options = struct { pub const Options = struct {
size: DesiredSize, size: DesiredSize,
metric_modifiers: ?*const Metrics.ModifierSet = null, metric_modifiers: ?*const Metrics.ModifierSet = null,
freetype_load_flags: FreetypeLoadFlags = freetype_load_flags_default,
}; };
/// The desired size for loading a font. /// The desired size for loading a font.

View File

@ -18,10 +18,16 @@ const Library = font.Library;
const convert = @import("freetype_convert.zig"); const convert = @import("freetype_convert.zig");
const fastmem = @import("../../fastmem.zig"); const fastmem = @import("../../fastmem.zig");
const quirks = @import("../../quirks.zig"); const quirks = @import("../../quirks.zig");
const config = @import("../../config.zig");
const log = std.log.scoped(.font_face); const log = std.log.scoped(.font_face);
pub const Face = struct { pub const Face = struct {
comptime {
// If we have the freetype backend, we should have load flags.
assert(font.face.FreetypeLoadFlags != void);
}
/// Our freetype library /// Our freetype library
lib: freetype.Library, lib: freetype.Library,
@ -34,6 +40,9 @@ pub const Face = struct {
/// Metrics for this font face. These are useful for renderers. /// Metrics for this font face. These are useful for renderers.
metrics: font.face.Metrics, metrics: font.face.Metrics,
/// Freetype load flags for this font face.
load_flags: font.face.FreetypeLoadFlags,
/// Set quirks.disableDefaultFontFeatures /// Set quirks.disableDefaultFontFeatures
quirks_disable_default_font_features: bool = false, quirks_disable_default_font_features: bool = false,
@ -77,6 +86,7 @@ pub const Face = struct {
.face = face, .face = face,
.hb_font = hb_font, .hb_font = hb_font,
.metrics = calcMetrics(face, opts.metric_modifiers), .metrics = calcMetrics(face, opts.metric_modifiers),
.load_flags = opts.freetype_load_flags,
}; };
result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result); 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 // This must be enabled for color faces though because those are
// often colored bitmaps, which we support. // often colored bitmaps, which we support.
.no_bitmap = !self.face.hasColor(), .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; const glyph = self.face.handle.*.glyph;

View File

@ -14,7 +14,7 @@ const std = @import("std");
const assert = std.debug.assert; const assert = std.debug.assert;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const font = @import("../main.zig"); 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); const log = std.log.scoped(.font_shaper_cache);

View File

@ -79,6 +79,17 @@ const Quads = packed struct(u4) {
br: bool = false, 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 /// Alignment of a figure within a cell
const Alignment = struct { const Alignment = struct {
horizontal: enum { 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 }), 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), 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), 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 // Not official box characters but special characters we hide
// in the high bits of a unicode codepoint. // in the high bits of a unicode codepoint.
@intFromEnum(Sprite.cursor_rect) => self.draw_cursor_rect(canvas), @intFromEnum(Sprite.cursor_rect) => self.draw_cursor_rect(canvas),
@ -1793,6 +2128,135 @@ fn draw_cell_diagonal(
) catch {}; ) 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( fn draw_circle(
self: Box, self: Box,
canvas: *font.sprite.Canvas, canvas: *font.sprite.Canvas,
@ -2118,12 +2582,13 @@ fn draw_edge_triangle(
try ctx.fill(canvas.alloc, path); try ctx.fill(canvas.alloc, path);
} }
fn draw_light_arc( fn draw_arc(
self: Box, self: Box,
canvas: *font.sprite.Canvas, canvas: *font.sprite.Canvas,
comptime corner: Corner, comptime corner: Corner,
comptime thickness: Thickness,
) !void { ) !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_width: f64 = @floatFromInt(self.width);
const float_height: f64 = @floatFromInt(self.height); const float_height: f64 = @floatFromInt(self.height);
const float_thick: f64 = @floatFromInt(thick_px); const float_thick: f64 = @floatFromInt(thick_px);
@ -2534,6 +2999,32 @@ fn testRenderAll(self: Box, alloc: Allocator, atlas: *font.Atlas) !void {
else => {}, 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" { test "render all sprites" {

View File

@ -263,6 +263,21 @@ const Kind = enum {
0x1FBCE...0x1FBEF, 0x1FBCE...0x1FBEF,
=> .box, => .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 // Powerline fonts
0xE0B0, 0xE0B0,
0xE0B4, 0xE0B4,

Binary file not shown.

View File

@ -317,8 +317,7 @@ pub const Action = union(enum) {
/// Focus on a split in a given direction. /// Focus on a split in a given direction.
goto_split: SplitFocusDirection, goto_split: SplitFocusDirection,
/// zoom/unzoom the current split. This is currently only supported /// zoom/unzoom the current split.
/// on macOS. Contributions welcome for other platforms.
toggle_split_zoom: void, toggle_split_zoom: void,
/// Resize the current split by moving the split divider in the given /// Resize the current split by moving the split divider in the given

View File

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const input = @import("../input.zig"); const input = @import("../input.zig");
const CircBuf = @import("../circ_buf.zig").CircBuf; const CircBuf = @import("../datastruct/main.zig").CircBuf;
const cimgui = @import("cimgui"); const cimgui = @import("cimgui");
/// Circular buffer of key events. /// Circular buffer of key events.

View File

@ -2,7 +2,7 @@ const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const cimgui = @import("cimgui"); const cimgui = @import("cimgui");
const terminal = @import("../terminal/main.zig"); 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"); const Surface = @import("../Surface.zig");
/// The stream handler for our inspector. /// The stream handler for our inspector.

View File

@ -168,7 +168,6 @@ pub const std_options: std.Options = .{
}; };
test { test {
_ = @import("circ_buf.zig");
_ = @import("pty.zig"); _ = @import("pty.zig");
_ = @import("Command.zig"); _ = @import("Command.zig");
_ = @import("font/main.zig"); _ = @import("font/main.zig");
@ -180,17 +179,11 @@ test {
_ = @import("surface_mouse.zig"); _ = @import("surface_mouse.zig");
// Libraries // Libraries
_ = @import("segmented_pool.zig");
_ = @import("crash/main.zig"); _ = @import("crash/main.zig");
_ = @import("datastruct/main.zig");
_ = @import("inspector/main.zig"); _ = @import("inspector/main.zig");
_ = @import("terminal/main.zig"); _ = @import("terminal/main.zig");
_ = @import("terminfo/main.zig"); _ = @import("terminfo/main.zig");
_ = @import("simd/main.zig"); _ = @import("simd/main.zig");
_ = @import("unicode/main.zig"); _ = @import("unicode/main.zig");
// TODO
_ = @import("blocking_queue.zig");
_ = @import("cache_table.zig");
_ = @import("config.zig");
_ = @import("lru.zig");
} }

View File

@ -9,7 +9,7 @@ const builtin = @import("builtin");
const xev = @import("xev"); const xev = @import("xev");
const macos = @import("macos"); const macos = @import("macos");
const BlockingQueue = @import("../blocking_queue.zig").BlockingQueue; const BlockingQueue = @import("../datastruct/main.zig").BlockingQueue;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const log = std.log.scoped(.cf_release_thread); const log = std.log.scoped(.cf_release_thread);

View File

@ -36,15 +36,16 @@ pub fn bufPrintHostnameFromFileUri(
// it's not a partial MAC-address. // it's not a partial MAC-address.
const port = uri.port orelse return host; 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 // MAC-address, and instead just a regular port so we return the plain
// URI hostname. // URI hostname.
if (port < 10 or port > 99) return host; if (port > 99) return host;
var fbs = std.io.fixedBufferStream(buf); var fbs = std.io.fixedBufferStream(buf);
try std.fmt.format( try std.fmt.format(
fbs.writer(), 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 }, .{ 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); 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" { 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. // 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"); const four_port_uri = try std.Uri.parse("file://has-a-port:1234");

View File

@ -1040,7 +1040,7 @@ pub fn updateFrame(
null, null,
); );
while (it.next()) |chunk| { while (it.next()) |chunk| {
var dirty_set = chunk.page.data.dirtyBitSet(); var dirty_set = chunk.node.data.dirtyBitSet();
dirty_set.unsetAll(); dirty_set.unsetAll();
} }
} }
@ -2364,7 +2364,7 @@ fn rebuildCells(
// True if this cell is selected // True if this cell is selected
const selected: bool = if (screen.selection) |sel| const selected: bool = if (screen.selection) |sel|
sel.contains(screen, .{ sel.contains(screen, .{
.page = row.page, .node = row.node,
.y = row.y, .y = row.y,
.x = @intCast( .x = @intCast(
// Spacer tails should show the selection // Spacer tails should show the selection
@ -2512,12 +2512,7 @@ fn rebuildCells(
); );
}; };
if (style.flags.overline) self.addOverline( if (style.flags.overline) self.addOverline(@intCast(x), @intCast(y), fg, alpha) catch |err| {
@intCast(x),
@intCast(y),
fg,
alpha
) catch |err| {
log.warn( log.warn(
"error adding overline to cell, will be invalid x={} y={}, err={}", "error adding overline to cell, will be invalid x={} y={}, err={}",
.{ x, y, err }, .{ x, y, err },

View File

@ -844,7 +844,7 @@ pub fn updateFrame(
null, null,
); );
while (it.next()) |chunk| { while (it.next()) |chunk| {
var dirty_set = chunk.page.data.dirtyBitSet(); var dirty_set = chunk.node.data.dirtyBitSet();
dirty_set.unsetAll(); dirty_set.unsetAll();
} }
} }
@ -1411,7 +1411,7 @@ pub fn rebuildCells(
// True if this cell is selected // True if this cell is selected
const selected: bool = if (screen.selection) |sel| const selected: bool = if (screen.selection) |sel|
sel.contains(screen, .{ sel.contains(screen, .{
.page = row.page, .node = row.node,
.y = row.y, .y = row.y,
.x = @intCast( .x = @intCast(
// Spacer tails should show the selection // Spacer tails should show the selection

View File

@ -9,7 +9,7 @@ const crash = @import("../crash/main.zig");
const renderer = @import("../renderer.zig"); const renderer = @import("../renderer.zig");
const apprt = @import("../apprt.zig"); const apprt = @import("../apprt.zig");
const configpkg = @import("../config.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 App = @import("../App.zig");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;

View File

@ -70,7 +70,7 @@ pub fn fgMode(
} }
// If we are at the end of the screen its definitely constrained // 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 // If we have a previous cell and it was PUA then we need to
// also constrain. This is so that multiple PUA glyphs align. // also constrain. This is so that multiple PUA glyphs align.

View File

@ -122,7 +122,7 @@ pub const Set = struct {
if (!mouse_cell.hyperlink) return; if (!mouse_cell.hyperlink) return;
// Get our hyperlink entry // 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 { const link_id = page.lookupHyperlink(mouse_cell) orelse {
log.warn("failed to find hyperlink for cell", .{}); log.warn("failed to find hyperlink for cell", .{});
return; return;
@ -165,7 +165,7 @@ pub const Set = struct {
for (row_pin.cells(.right), 0..) |*cell, x| { for (row_pin.cells(.right), 0..) |*cell, x| {
const match = match: { const match = match: {
if (cell.hyperlink) { 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; break :match cell_link_id == link_id;
} }
} }
@ -215,7 +215,7 @@ pub const Set = struct {
// Expand it to the left. // Expand it to the left.
var it = mouse_pin.cellIterator(.left_up, null); var it = mouse_pin.cellIterator(.left_up, null);
while (it.next()) |cell_pin| { 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 rac = cell_pin.rowAndCell();
const cell = rac.cell; const cell = rac.cell;
@ -241,7 +241,7 @@ pub const Set = struct {
// Expand it to the right // Expand it to the right
it = mouse_pin.cellIterator(.right_down, null); it = mouse_pin.cellIterator(.right_down, null);
while (it.next()) |cell_pin| { 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 rac = cell_pin.rowAndCell();
const cell = rac.cell; const cell = rac.cell;

File diff suppressed because it is too large Load Diff

View File

@ -203,7 +203,7 @@ pub fn init(
errdefer pages.deinit(); errdefer pages.deinit();
// Create our tracked pin for the cursor. // 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); errdefer pages.untrackPin(page_pin);
const page_rac = page_pin.rowAndCell(); 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(); const page_rac = page_pin.rowAndCell();
break :cursor .{ break :cursor .{
.x = 0, .x = 0,
@ -376,7 +376,7 @@ pub fn clonePool(
if (!sel.contains(self, clone_top)) break :sel null; 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: { const end_pin = pin_remap.get(ordered.br) orelse end: {
@ -414,21 +414,21 @@ pub fn clonePool(
/// cursor page. /// cursor page.
pub fn adjustCapacity( pub fn adjustCapacity(
self: *Screen, self: *Screen,
page: *PageList.List.Node, node: *PageList.List.Node,
adjustment: PageList.AdjustCapacity, adjustment: PageList.AdjustCapacity,
) PageList.AdjustCapacityError!*PageList.List.Node { ) PageList.AdjustCapacityError!*PageList.List.Node {
// If the page being modified isn't our cursor page then // If the page being modified isn't our cursor page then
// this is a quick operation because we have no additional // this is a quick operation because we have no additional
// accounting. // accounting.
if (page != self.cursor.page_pin.page) { if (node != self.cursor.page_pin.node) {
return try self.pages.adjustCapacity(page, adjustment); return try self.pages.adjustCapacity(node, adjustment);
} }
// We're modifying the cursor page. When we adjust the // We're modifying the cursor page. When we adjust the
// capacity below it will be short the ref count on our // capacity below it will be short the ref count on our
// current style and hyperlink, so we need to init those. // current style and hyperlink, so we need to init those.
const node = try self.pages.adjustCapacity(page, adjustment); const new_node = try self.pages.adjustCapacity(node, adjustment);
const new_page = &node.data; const new_page: *Page = &new_node.data;
// All additions below have unreachable catches because when // All additions below have unreachable catches because when
// we adjust cap we should have enough memory to fit the // 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. // So our page row/cell and so on are all off.
self.cursorReload(); self.cursorReload();
return node; return new_node;
} }
pub fn cursorCellRight(self: *Screen, n: size.CellCountInt) *pagepkg.Cell { 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 // so our cursor is in the correct place we just have to clear
// the cells. // the cells.
if (self.pages.rows == 1) { if (self.pages.rows == 1) {
const page: *Page = &self.cursor.page_pin.node.data;
self.clearCells( self.clearCells(
&self.cursor.page_pin.page.data, page,
self.cursor.page_row, 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); dirty.set(0);
} else { } else {
// eraseRow will shift everything below it up. // 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 // was on was pruned. In this case, grow() moves the pin to
// the top-left of the new page. This effectively moves it by // the top-left of the new page. This effectively moves it by
// one already, we just need to fix up the x value. // 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).? self.cursor.page_pin.down(1).?
else reuse: { else reuse: {
var pin = self.cursor.page_pin.*; 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 // Clear the new row so it gets our bg color. We only do this
// if we have a bg color at all. // if we have a bg color at all.
if (self.cursor.style.bg_color != .none) { if (self.cursor.style.bg_color != .none) {
const page: *Page = &page_pin.node.data;
self.clearCells( self.clearCells(
&page_pin.page.data, page,
self.cursor.page_row, 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 { } else {
// In this case, it means grow() didn't allocate a new page. // 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 // 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. // 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).?; self.cursor.page_pin.* = self.cursor.page_pin.down(1).?;
const pin = self.cursor.page_pin; 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 // 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 ]. // 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 // Go through each of the pages following our pin, shift all rows
// down by one, and copy the last row of the previous page. // down by one, and copy the last row of the previous page.
var current = self.pages.pages.last.?; 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 = current.prev.?;
const prev_page = &prev.data; const prev_page = &prev.data;
const cur_page = &current.data; const cur_page = &current.data;
@ -846,7 +848,7 @@ fn cursorScrollAboveRotate(self: *Screen) !void {
// Our current is our cursor page, we need to rotate down from // Our current is our cursor page, we need to rotate down from
// our cursor and clear our row. // our cursor and clear our row.
assert(current == self.cursor.page_pin.page); assert(current == self.cursor.page_pin.node);
const cur_page = &current.data; const cur_page = &current.data;
const cur_rows = cur_page.rows.ptr(cur_page.memory.ptr); 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]); 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 the other cursor had a hyperlink, add it to ours.
if (opts.hyperlink and other.hyperlink_id != 0) { if (opts.hyperlink and other.hyperlink_id != 0) {
// Get the hyperlink from the other cursor's page. // 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 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]; 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. // If our pin is on the same page, then we can just update the pin.
// We don't need to migrate any state. // 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; self.cursor.page_pin.* = new;
return; 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 we have a hyperlink then we need to release it from the old page.
if (self.cursor.hyperlink != null) { 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); 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. // 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 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) { if (cell.wide == .spacer_head) {
self.clearCells( self.clearCells(
&self.cursor.page_pin.page.data, &self.cursor.page_pin.node.data,
page_row, 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); var it = self.pages.pageIterator(.right_down, tl, bl);
while (it.next()) |chunk| { while (it.next()) |chunk| {
// Mark everything in this chunk as dirty // 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); dirty.setRangeValue(.{ .start = chunk.start, .end = chunk.end }, true);
for (chunk.rows()) |*row| { for (chunk.rows()) |*row| {
const cells_offset = row.cells; 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]; const cells = cells_multi[0..self.pages.cols];
// Clear all cells // Clear all cells
if (protected) { 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 // We need to preserve other row attributes since we only
// cleared unprotected cells. // cleared unprotected cells.
row.cells = cells_offset; row.cells = cells_offset;
} else { } else {
self.clearCells(&chunk.page.data, row, cells); self.clearCells(&chunk.node.data, row, cells);
row.* = .{ .cells = cells_offset }; row.* = .{ .cells = cells_offset };
} }
} }
@ -1294,8 +1296,8 @@ pub fn clearPrompt(self: *Screen) void {
var clear_it = top.rowIterator(.right_down, null); var clear_it = top.rowIterator(.right_down, null);
while (clear_it.next()) |p| { while (clear_it.next()) |p| {
const row = p.rowAndCell().row; const row = p.rowAndCell().row;
p.page.data.clearCells(row, 0, p.page.data.size.cols); p.node.data.clearCells(row, 0, p.node.data.size.cols);
p.page.data.assertIntegrity(); p.node.data.assertIntegrity();
} }
} }
} }
@ -1336,12 +1338,12 @@ pub fn splitCellBoundary(
self: *Screen, self: *Screen,
x: size.CellCountInt, x: size.CellCountInt,
) void { ) void {
const page = &self.cursor.page_pin.page.data; const page = &self.cursor.page_pin.node.data;
page.pauseIntegrityChecks(true); page.pauseIntegrityChecks(true);
defer page.pauseIntegrityChecks(false); 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 // `x` may be up to an INCLUDING `cols`, since that signifies splitting
// the boundary to the right of the final cell in the row. // 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| { if (self.cursor.page_pin.up(1)) |p_row| {
const p_rac = p_row.rowAndCell(); const p_rac = p_row.rowAndCell();
const p_cells = p_row.cells(.all); 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) { if (p_cell.wide == .spacer_head) {
self.clearCells( self.clearCells(
&p_row.page.data, &p_row.node.data,
p_rac.row, 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) { if (self.cursor.hyperlink_id != 0) {
// Note we do NOT use endHyperlink because we want to keep // Note we do NOT use endHyperlink because we want to keep
// our allocated self.cursor.hyperlink valid. // 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); page.hyperlink_set.release(page.memory, self.cursor.hyperlink_id);
self.cursor.hyperlink_id = 0; self.cursor.hyperlink_id = 0;
self.cursor.hyperlink = null; 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. /// Call this whenever you manually change the cursor style.
pub fn manualStyleUpdate(self: *Screen) !void { 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()}); // 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 // and double the style capacity for it if it was
// full. // full.
const node = try self.adjustCapacity( const node = try self.adjustCapacity(
self.cursor.page_pin.page, self.cursor.page_pin.node,
switch (err) { switch (err) {
error.OutOfMemory => .{ .styles = page.capacity.styles * 2 }, error.OutOfMemory => .{ .styles = page.capacity.styles * 2 },
error.NeedsRehash => .{}, error.NeedsRehash => .{},
@ -1749,8 +1751,8 @@ pub fn manualStyleUpdate(self: *Screen) !void {
/// Append a grapheme to the given cell within the current cursor row. /// Append a grapheme to the given cell within the current cursor row.
pub fn appendGrapheme(self: *Screen, cell: *Cell, cp: u21) !void { pub fn appendGrapheme(self: *Screen, cell: *Cell, cp: u21) !void {
defer self.cursor.page_pin.page.data.assertIntegrity(); defer self.cursor.page_pin.node.data.assertIntegrity();
self.cursor.page_pin.page.data.appendGrapheme( self.cursor.page_pin.node.data.appendGrapheme(
self.cursor.page_row, self.cursor.page_row,
cell, cell,
cp, 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 // Adjust our capacity. This will update our cursor page pin and
// force us to reload. // 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; const new_bytes = original_node.data.capacity.grapheme_bytes * 2;
_ = try self.adjustCapacity( _ = try self.adjustCapacity(
original_node, 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)), .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, self.cursor.page_row,
reloaded_cell, reloaded_cell,
cp, cp,
@ -1827,19 +1829,19 @@ pub fn startHyperlink(
// strings table is out of memory, adjust it up // strings table is out of memory, adjust it up
error.StringsOutOfMemory => _ = try self.adjustCapacity( error.StringsOutOfMemory => _ = try self.adjustCapacity(
self.cursor.page_pin.page, self.cursor.page_pin.node,
.{ .string_bytes = self.cursor.page_pin.page.data.capacity.string_bytes * 2 }, .{ .string_bytes = self.cursor.page_pin.node.data.capacity.string_bytes * 2 },
), ),
// hyperlink set is out of memory, adjust it up // hyperlink set is out of memory, adjust it up
error.SetOutOfMemory => _ = try self.adjustCapacity( error.SetOutOfMemory => _ = try self.adjustCapacity(
self.cursor.page_pin.page, self.cursor.page_pin.node,
.{ .hyperlink_bytes = self.cursor.page_pin.page.data.capacity.hyperlink_bytes * 2 }, .{ .hyperlink_bytes = self.cursor.page_pin.node.data.capacity.hyperlink_bytes * 2 },
), ),
// hyperlink set is too full, rehash it // hyperlink set is too full, rehash it
error.SetNeedsRehash => _ = try self.adjustCapacity( 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); errdefer link.deinit(self.alloc);
// Insert the hyperlink into page memory // 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.*); const id: hyperlink.Id = try page.insertHyperlink(link.*);
// Save it all // Save it all
@ -1893,7 +1895,7 @@ pub fn endHyperlink(self: *Screen) void {
// how RefCountedSet works). This causes some memory fragmentation but // how RefCountedSet works). This causes some memory fragmentation but
// is fine because if it is ever pruned the context deleted callback // is fine because if it is ever pruned the context deleted callback
// will be called. // 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); page.hyperlink_set.release(page.memory, self.cursor.hyperlink_id);
self.cursor.hyperlink.?.deinit(self.alloc); self.cursor.hyperlink.?.deinit(self.alloc);
self.alloc.destroy(self.cursor.hyperlink.?); self.alloc.destroy(self.cursor.hyperlink.?);
@ -1905,7 +1907,7 @@ pub fn endHyperlink(self: *Screen) void {
pub fn cursorSetHyperlink(self: *Screen) !void { pub fn cursorSetHyperlink(self: *Screen) !void {
assert(self.cursor.hyperlink_id != 0); 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( if (page.setHyperlink(
self.cursor.page_row, self.cursor.page_row,
self.cursor.page_cell, 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 // hyperlink_map is out of space, realloc the page to be larger
error.HyperlinkMapOutOfMemory => { error.HyperlinkMapOutOfMemory => {
_ = try self.adjustCapacity( _ = try self.adjustCapacity(
self.cursor.page_pin.page, self.cursor.page_pin.node,
.{ .hyperlink_bytes = page.capacity.hyperlink_bytes * 2 }, .{ .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| { while (page_it.next()) |chunk| {
const rows = chunk.rows(); const rows = chunk.rows();
for (rows, chunk.start..) |row, y| { 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) const start_x = if (row_count == 0 or sel_ordered.rectangle)
sel_start.x sel_start.x
@ -2048,20 +2050,20 @@ pub fn selectionString(self: *Screen, alloc: Allocator, opts: SelectionString) !
try strbuilder.appendSlice(buf[0..encode_len]); try strbuilder.appendSlice(buf[0..encode_len]);
if (mapbuilder) |*b| { if (mapbuilder) |*b| {
for (0..encode_len) |_| try b.append(.{ for (0..encode_len) |_| try b.append(.{
.page = chunk.page, .node = chunk.node,
.y = @intCast(y), .y = @intCast(y),
.x = @intCast(x), .x = @intCast(x),
}); });
} }
} }
if (cell.hasGrapheme()) { if (cell.hasGrapheme()) {
const cps = chunk.page.data.lookupGrapheme(cell).?; const cps = chunk.node.data.lookupGrapheme(cell).?;
for (cps) |cp| { for (cps) |cp| {
const encode_len = try std.unicode.utf8Encode(cp, &buf); const encode_len = try std.unicode.utf8Encode(cp, &buf);
try strbuilder.appendSlice(buf[0..encode_len]); try strbuilder.appendSlice(buf[0..encode_len]);
if (mapbuilder) |*b| { if (mapbuilder) |*b| {
for (0..encode_len) |_| try b.append(.{ for (0..encode_len) |_| try b.append(.{
.page = chunk.page, .node = chunk.node,
.y = @intCast(y), .y = @intCast(y),
.x = @intCast(x), .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 if (!is_final_row and
(!row.wrap or sel_ordered.rectangle)) (!row.wrap or sel_ordered.rectangle))
{ {
try strbuilder.append('\n'); try strbuilder.append('\n');
if (mapbuilder) |*b| try b.append(.{ if (mapbuilder) |*b| try b.append(.{
.page = chunk.page, .node = chunk.node,
.y = @intCast(y), .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(); const current_prompt = row.semantic_prompt.promptOrInput();
if (current_prompt != v) { if (current_prompt != v) {
var prev = p.up(1).?; 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; break :end_pin prev;
} }
} }
if (!row.wrap) { if (!row.wrap) {
var copy = p; var copy = p;
copy.x = p.page.data.size.cols - 1; copy.x = p.node.data.size.cols - 1;
break :end_pin copy; 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 // If we are going to the next row and it isn't wrapped, we
// return the previous. // 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; 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 // If we are going to the next row and it isn't wrapped, we
// return the previous. // 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; break :start prev;
} }
@ -2491,7 +2493,7 @@ pub fn selectOutput(self: *Screen, pin: Pin) ?Selection {
switch (row.semantic_prompt) { switch (row.semantic_prompt) {
.input, .prompt_continuation, .prompt => { .input, .prompt_continuation, .prompt => {
var copy = it_prev; 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; break :boundary copy;
}, },
else => {}, else => {},
@ -2504,10 +2506,10 @@ pub fn selectOutput(self: *Screen, pin: Pin) ?Selection {
it = it_prev.rowIterator(.left_up, null); it = it_prev.rowIterator(.left_up, null);
while (it.next()) |p| { while (it.next()) |p| {
const row = p.rowAndCell().row; const row = p.rowAndCell().row;
const cells = p.page.data.getCells(row); const cells = p.node.data.getCells(row);
if (Cell.hasTextAny(cells)) { if (Cell.hasTextAny(cells)) {
var copy = p; var copy = p;
copy.x = p.page.data.size.cols - 1; copy.x = p.node.data.size.cols - 1;
break :boundary copy; break :boundary copy;
} }
} }
@ -2598,7 +2600,7 @@ pub fn selectPrompt(self: *Screen, pin: Pin) ?Selection {
const end: Pin = end: { const end: Pin = end: {
var it = pin.rowIterator(.right_down, null); var it = pin.rowIterator(.right_down, null);
var it_prev = it.next().?; 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| { while (it.next()) |p| {
const row = p.rowAndCell().row; const row = p.rowAndCell().row;
switch (row.semantic_prompt) { switch (row.semantic_prompt) {
@ -2610,7 +2612,7 @@ pub fn selectPrompt(self: *Screen, pin: Pin) ?Selection {
} }
it_prev = p; 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; break :end it_prev;
@ -2764,7 +2766,7 @@ pub fn dumpString(
.codepoint_grapheme => { .codepoint_grapheme => {
try writer.print("{u}", .{cell.content.codepoint}); 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| { for (cps) |cp| {
try writer.print("{u}", .{cp}); try writer.print("{u}", .{cp});
} }
@ -2843,7 +2845,7 @@ pub fn testWriteString(self: *Screen, text: []const u8) !void {
break :cell cell; break :cell cell;
}; };
try self.cursor.page_pin.page.data.appendGrapheme( try self.cursor.page_pin.node.data.appendGrapheme(
self.cursor.page_row, self.cursor.page_row,
cell, cell,
c, c,
@ -2872,7 +2874,7 @@ pub fn testWriteString(self: *Screen, text: []const u8) !void {
// If we have a ref-counted style, increase. // If we have a ref-counted style, increase.
if (self.cursor.style_id != style.default_id) { 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; 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 we have a ref-counted style, increase twice.
if (self.cursor.style_id != style.default_id) { 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);
page.styles.use(page.memory, self.cursor.style_id); page.styles.use(page.memory, self.cursor.style_id);
self.cursor.page_row.styled = true; self.cursor.page_row.styled = true;
@ -3054,7 +3056,7 @@ test "Screen cursorCopy style deref" {
var s2 = try Screen.init(alloc, 10, 10, 0); var s2 = try Screen.init(alloc, 10, 10, 0);
defer s2.deinit(); defer s2.deinit();
const page = &s2.cursor.page_pin.page.data; const page = &s2.cursor.page_pin.node.data;
// Bold should create our style // Bold should create our style
try s2.setAttribute(.{ .bold = {} }); try s2.setAttribute(.{ .bold = {} });
@ -3110,12 +3112,12 @@ test "Screen cursorCopy style deref new page" {
// +-------------+ // +-------------+
// This should be PAGE 1 // 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. // It should be the last page in the list.
try testing.expectEqual(&s2.pages.pages.last.?.data, page); try testing.expectEqual(&s2.pages.pages.last.?.data, page);
// It should have a previous 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 // The cursor should be at 2, 9
try testing.expect(s2.cursor.x == 2); 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.expect(!s2.cursor.style.flags.bold);
try testing.expectEqual(@as(usize, 0), page.styles.count()); try testing.expectEqual(@as(usize, 0), page.styles.count());
// The page after the page the cursor is now in should be page 1. // 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 // The cursor should be at 0, 0
try testing.expect(s2.cursor.x == 0); try testing.expect(s2.cursor.x == 0);
try testing.expect(s2.cursor.y == 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); var s2 = try Screen.init(alloc, 10, 10, 0);
defer s2.deinit(); 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 s2.cursorCopy(s.cursor, .{});
try testing.expect(s2.cursor.style.flags.bold); try testing.expect(s2.cursor.style.flags.bold);
try testing.expectEqual(@as(usize, 1), page.styles.count()); 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); var s2 = try Screen.init(alloc, 10, 10, 0);
defer s2.deinit(); 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. // Create a hyperlink for the cursor.
try s2.startHyperlink("https://example.com/", null); try s2.startHyperlink("https://example.com/", null);
@ -3219,12 +3221,12 @@ test "Screen cursorCopy hyperlink deref new page" {
// +-------------+ // +-------------+
// This should be PAGE 1 // 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. // It should be the last page in the list.
try testing.expectEqual(&s2.pages.pages.last.?.data, page); try testing.expectEqual(&s2.pages.pages.last.?.data, page);
// It should have a previous 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 // The cursor should be at 2, 9
try testing.expect(s2.cursor.x == 2); 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.expectEqual(@as(usize, 0), page.hyperlink_set.count());
try testing.expect(s2.cursor.hyperlink_id == 0); try testing.expect(s2.cursor.hyperlink_id == 0);
// The page after the page the cursor is now in should be page 1. // 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 // The cursor should be at 0, 0
try testing.expect(s2.cursor.x == 0); try testing.expect(s2.cursor.x == 0);
try testing.expect(s2.cursor.y == 0); try testing.expect(s2.cursor.y == 0);
@ -3256,12 +3258,12 @@ test "Screen cursorCopy hyperlink copy" {
// Create a hyperlink for the cursor. // Create a hyperlink for the cursor.
try s.startHyperlink("https://example.com/", null); 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); try testing.expect(s.cursor.hyperlink_id != 0);
var s2 = try Screen.init(alloc, 10, 10, 0); var s2 = try Screen.init(alloc, 10, 10, 0);
defer s2.deinit(); 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.expectEqual(@as(usize, 0), page.hyperlink_set.count());
try testing.expect(s2.cursor.hyperlink_id == 0); try testing.expect(s2.cursor.hyperlink_id == 0);
@ -3281,12 +3283,12 @@ test "Screen cursorCopy hyperlink copy disabled" {
// Create a hyperlink for the cursor. // Create a hyperlink for the cursor.
try s.startHyperlink("https://example.com/", null); 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); try testing.expect(s.cursor.hyperlink_id != 0);
var s2 = try Screen.init(alloc, 10, 10, 0); var s2 = try Screen.init(alloc, 10, 10, 0);
defer s2.deinit(); 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.expectEqual(@as(usize, 0), page.hyperlink_set.count());
try testing.expect(s2.cursor.hyperlink_id == 0); 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); var s = try Screen.init(alloc, 80, 24, 1000);
defer s.deinit(); 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()); try testing.expectEqual(@as(usize, 0), page.styles.count());
// Set a new style // Set a new style
@ -3325,7 +3327,7 @@ test "Screen style reset to default" {
var s = try Screen.init(alloc, 80, 24, 1000); var s = try Screen.init(alloc, 80, 24, 1000);
defer s.deinit(); 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()); try testing.expectEqual(@as(usize, 0), page.styles.count());
// Set a new style // Set a new style
@ -3345,7 +3347,7 @@ test "Screen style reset with unset" {
var s = try Screen.init(alloc, 80, 24, 1000); var s = try Screen.init(alloc, 80, 24, 1000);
defer s.deinit(); 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()); try testing.expectEqual(@as(usize, 0), page.styles.count());
// Set a new style // Set a new style
@ -3402,7 +3404,7 @@ test "Screen clearRows active styled line" {
try s.setAttribute(.{ .unset = {} }); try s.setAttribute(.{ .unset = {} });
// We should have one style // 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()); try testing.expectEqual(@as(usize, 1), page.styles.count());
s.clearRows(.{ .active = .{} }, null, false); 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 // assertion fails then the bug is in the test: we should be scrolling
// above enough for a new page to show up. // 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); try testing.expect(start_page != page);
} }
// Scroll back to the previous page // Scroll back to the previous page
s.cursorUp(1); 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); try testing.expect(start_page == page);
} }
// Go back up, set a style // Go back up, set a style
try s.setAttribute(.{ .bold = {} }); 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( const styleval = page.styles.get(
page.memory, page.memory,
s.cursor.style_id, 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 // Go back down into the next page and we should have that style
s.cursorDown(1); s.cursorDown(1);
{ {
const page = &s.cursor.page_pin.page.data; const page = &s.cursor.page_pin.node.data;
const styleval = page.styles.get( const styleval = page.styles.get(
page.memory, page.memory,
s.cursor.style_id, 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 // assertion fails then the bug is in the test: we should be scrolling
// above enough for a new page to show up. // 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); try testing.expect(start_page != page);
} }
// Go back up, set a style // Go back up, set a style
try s.setAttribute(.{ .bold = {} }); 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( const styleval = page.styles.get(
page.memory, page.memory,
s.cursor.style_id, 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 // Go back down into the prev page and we should have that style
s.cursorUp(1); 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); try testing.expect(start_page == page);
const styleval = page.styles.get( 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 // assertion fails then the bug is in the test: we should be scrolling
// above enough for a new page to show up. // 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); try testing.expect(start_page != page);
} }
// Go back up, set a style // Go back up, set a style
try s.setAttribute(.{ .bold = {} }); 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( const styleval = page.styles.get(
page.memory, page.memory,
s.cursor.style_id, 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 // Go back down into the prev page and we should have that style
s.cursorAbsolute(1, 1); 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); try testing.expect(start_page == page);
const styleval = page.styles.get( const styleval = page.styles.get(
@ -4345,7 +4347,7 @@ test "Screen: scroll above same page but cursor on previous page" {
s.pages.clearDirty(); s.pages.clearDirty();
// Ensure we're still on the first page and have a second // 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); try testing.expect(s.pages.pages.first.?.next != null);
// At this point: // At this point:
@ -4403,7 +4405,7 @@ test "Screen: scroll above same page but cursor on previous page last row" {
s.pages.clearDirty(); s.pages.clearDirty();
// Ensure we're still on the first page and have a second // 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); try testing.expect(s.pages.pages.first.?.next != null);
// At this point: // At this point:
@ -4478,7 +4480,7 @@ test "Screen: scroll above creates new page" {
s.pages.clearDirty(); s.pages.clearDirty();
// Ensure we're still on the first page // 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(); try s.cursorScrollAbove();
{ {
@ -8150,7 +8152,7 @@ test "Screen: selectionString with zero width joiner" {
const cell = pin.rowAndCell().cell; const cell = pin.rowAndCell().cell;
try testing.expectEqual(@as(u21, 0x1F468), cell.content.codepoint); try testing.expectEqual(@as(u21, 0x1F468), cell.content.codepoint);
try testing.expectEqual(Cell.Wide.wide, cell.wide); 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); try testing.expectEqual(@as(usize, 1), cps.len);
} }
@ -8381,21 +8383,21 @@ test "Screen: hyperlink start/end" {
defer s.deinit(); defer s.deinit();
try testing.expect(s.cursor.hyperlink_id == 0); 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 testing.expectEqual(0, page.hyperlink_set.count());
} }
try s.startHyperlink("http://example.com", null); try s.startHyperlink("http://example.com", null);
try testing.expect(s.cursor.hyperlink_id != 0); 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()); try testing.expectEqual(1, page.hyperlink_set.count());
} }
s.endHyperlink(); s.endHyperlink();
try testing.expect(s.cursor.hyperlink_id == 0); 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 testing.expectEqual(0, page.hyperlink_set.count());
} }
} }
@ -8409,7 +8411,7 @@ test "Screen: hyperlink reuse" {
try testing.expect(s.cursor.hyperlink_id == 0); 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 testing.expectEqual(0, page.hyperlink_set.count());
} }
@ -8422,14 +8424,14 @@ test "Screen: hyperlink reuse" {
try s.startHyperlink("http://example.com", null); try s.startHyperlink("http://example.com", null);
try testing.expectEqual(id, s.cursor.hyperlink_id); 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()); try testing.expectEqual(1, page.hyperlink_set.count());
} }
s.endHyperlink(); s.endHyperlink();
try testing.expect(s.cursor.hyperlink_id == 0); 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 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 s.startHyperlink("http://example.com", null);
try testing.expect(s.cursor.hyperlink_id != 0); 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()); 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 s.resize(10, 10);
try testing.expect(s.cursor.hyperlink_id != 0); 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()); try testing.expectEqual(1, page.hyperlink_set.count());
} }
s.endHyperlink(); s.endHyperlink();
try testing.expect(s.cursor.hyperlink_id == 0); 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 testing.expectEqual(0, page.hyperlink_set.count());
} }
} }
@ -8489,8 +8491,8 @@ test "Screen: adjustCapacity cursor style ref count" {
// This forces the page to change. // This forces the page to change.
_ = try s.adjustCapacity( _ = try s.adjustCapacity(
s.cursor.page_pin.page, s.cursor.page_pin.node,
.{ .grapheme_bytes = s.cursor.page_pin.page.data.capacity.grapheme_bytes * 2 }, .{ .grapheme_bytes = s.cursor.page_pin.node.data.capacity.grapheme_bytes * 2 },
); );
// Our ref counts should still be the same // Our ref counts should still be the same

View File

@ -372,7 +372,7 @@ pub fn adjust(
var current = end_pin.*; var current = end_pin.*;
while (current.down(1)) |next| : (current = next) { while (current.down(1)) |next| : (current = next) {
const rac = next.rowAndCell(); 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)) { if (page.Cell.hasTextAny(cells)) {
end_pin.* = next; end_pin.* = next;
break; break;
@ -434,7 +434,7 @@ pub fn adjust(
); );
while (it.next()) |next| { while (it.next()) |next| {
const rac = next.rowAndCell(); 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)) { if (page.Cell.hasTextAny(cells)) {
end_pin.* = next; end_pin.* = next;
end_pin.x = @intCast(cells.len - 1); end_pin.x = @intCast(cells.len - 1);
@ -445,7 +445,7 @@ pub fn adjust(
.beginning_of_line => end_pin.x = 0, .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,
} }
} }

View File

@ -313,7 +313,7 @@ pub fn print(self: *Terminal, c: u21) !void {
var state: unicode.GraphemeBreakState = .{}; var state: unicode.GraphemeBreakState = .{};
var cp1: u21 = prev.cell.content.codepoint; var cp1: u21 = prev.cell.content.codepoint;
if (prev.cell.hasGrapheme()) { 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| { for (cps) |cp2| {
// log.debug("cp1={x} cp2={x}", .{ cp1, cp2 }); // log.debug("cp1={x} cp2={x}", .{ cp1, cp2 });
assert(!unicode.graphemeBreak(cp1, cp2, &state)); assert(!unicode.graphemeBreak(cp1, cp2, &state));
@ -567,7 +567,7 @@ fn printCell(
const spacer_cell = self.screen.cursorCellRight(1); const spacer_cell = self.screen.cursorCellRight(1);
self.screen.clearCells( self.screen.clearCells(
&self.screen.cursor.page_pin.page.data, &self.screen.cursor.page_pin.node.data,
self.screen.cursor.page_row, self.screen.cursor.page_row,
spacer_cell[0..1], spacer_cell[0..1],
); );
@ -588,7 +588,7 @@ fn printCell(
const wide_cell = self.screen.cursorCellLeft(1); const wide_cell = self.screen.cursorCellLeft(1);
self.screen.clearCells( self.screen.clearCells(
&self.screen.cursor.page_pin.page.data, &self.screen.cursor.page_pin.node.data,
self.screen.cursor.page_row, self.screen.cursor.page_row,
wide_cell[0..1], wide_cell[0..1],
); );
@ -607,7 +607,7 @@ fn printCell(
// If the prior value had graphemes, clear those // If the prior value had graphemes, clear those
if (cell.hasGrapheme()) { if (cell.hasGrapheme()) {
self.screen.cursor.page_pin.page.data.clearGrapheme( self.screen.cursor.page_pin.node.data.clearGrapheme(
self.screen.cursor.page_row, self.screen.cursor.page_row,
cell, cell,
); );
@ -617,7 +617,7 @@ fn printCell(
// cell's new style will be different after writing. // cell's new style will be different after writing.
const style_changed = cell.style_id != self.screen.cursor.style_id; const style_changed = cell.style_id != self.screen.cursor.style_id;
if (style_changed) { 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. // Release the old style.
if (cell.style_id != style.default_id) { if (cell.style_id != style.default_id) {
@ -639,7 +639,7 @@ fn printCell(
}; };
if (style_changed) { 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. // Use the new style.
if (cell.style_id != style.default_id) { if (cell.style_id != style.default_id) {
@ -664,7 +664,7 @@ fn printCell(
}; };
} else if (had_hyperlink) { } else if (had_hyperlink) {
// If the previous cell had a hyperlink then we need to clear it. // 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); 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_rac = off_p.rowAndCell();
const off_row: *Row = off_rac.row; const off_row: *Row = off_rac.row;
self.rowWillBeShifted(&cur_p.page.data, cur_row); self.rowWillBeShifted(&cur_p.node.data, cur_row);
self.rowWillBeShifted(&off_p.page.data, off_row); self.rowWillBeShifted(&off_p.node.data, off_row);
// If our scrolling region is full width, then we unset wrap. // If our scrolling region is full width, then we unset wrap.
if (!left_right) { 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 // If our page doesn't match, then we need to do a copy from
// one page to another. This is the slow path. // one page to another. This is the slow path.
if (src_p.page != dst_p.page) { if (src_p.node != dst_p.node) {
dst_p.page.data.clonePartialRowFrom( dst_p.node.data.clonePartialRowFrom(
&src_p.page.data, &src_p.node.data,
dst_row, dst_row,
src_row, src_row,
self.scrolling_region.left, self.scrolling_region.left,
self.scrolling_region.right + 1, self.scrolling_region.right + 1,
) catch |err| { ) catch |err| {
const cap = dst_p.page.data.capacity; const cap = dst_p.node.data.capacity;
// Adjust our page capacity to make // Adjust our page capacity to make
// room for we didn't have space for // room for we didn't have space for
_ = self.screen.adjustCapacity( _ = self.screen.adjustCapacity(
dst_p.page, dst_p.node,
switch (err) { switch (err) {
// Rehash the sets // Rehash the sets
error.StyleSetNeedsRehash, error.StyleSetNeedsRehash,
@ -1589,11 +1589,11 @@ pub fn insertLines(self: *Terminal, count: usize) void {
src_row.* = dst; src_row.* = dst;
// Ensure what we did didn't corrupt the page // Ensure what we did didn't corrupt the page
cur_p.page.data.assertIntegrity(); cur_p.node.data.assertIntegrity();
} else { } else {
// Left/right scroll margins we have to // Left/right scroll margins we have to
// copy cells, which is much slower... // copy cells, which is much slower...
const page = &cur_p.page.data; const page = &cur_p.node.data;
page.moveCells( page.moveCells(
src_row, src_row,
self.scrolling_region.left, self.scrolling_region.left,
@ -1605,7 +1605,7 @@ pub fn insertLines(self: *Terminal, count: usize) void {
} }
} else { } else {
// Clear the cells for this row, it has been shifted. // 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); const cells = page.getCells(cur_row);
self.screen.clearCells( self.screen.clearCells(
page, page,
@ -1698,8 +1698,8 @@ pub fn deleteLines(self: *Terminal, count: usize) void {
const off_rac = off_p.rowAndCell(); const off_rac = off_p.rowAndCell();
const off_row: *Row = off_rac.row; const off_row: *Row = off_rac.row;
self.rowWillBeShifted(&cur_p.page.data, cur_row); self.rowWillBeShifted(&cur_p.node.data, cur_row);
self.rowWillBeShifted(&off_p.page.data, off_row); self.rowWillBeShifted(&off_p.node.data, off_row);
// If our scrolling region is full width, then we unset wrap. // If our scrolling region is full width, then we unset wrap.
if (!left_right) { 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 // If our page doesn't match, then we need to do a copy from
// one page to another. This is the slow path. // one page to another. This is the slow path.
if (src_p.page != dst_p.page) { if (src_p.node != dst_p.node) {
dst_p.page.data.clonePartialRowFrom( dst_p.node.data.clonePartialRowFrom(
&src_p.page.data, &src_p.node.data,
dst_row, dst_row,
src_row, src_row,
self.scrolling_region.left, self.scrolling_region.left,
self.scrolling_region.right + 1, self.scrolling_region.right + 1,
) catch |err| { ) catch |err| {
const cap = dst_p.page.data.capacity; const cap = dst_p.node.data.capacity;
// Adjust our page capacity to make // Adjust our page capacity to make
// room for we didn't have space for // room for we didn't have space for
_ = self.screen.adjustCapacity( _ = self.screen.adjustCapacity(
dst_p.page, dst_p.node,
switch (err) { switch (err) {
// Rehash the sets // Rehash the sets
error.StyleSetNeedsRehash, error.StyleSetNeedsRehash,
@ -1782,11 +1782,11 @@ pub fn deleteLines(self: *Terminal, count: usize) void {
src_row.* = dst; src_row.* = dst;
// Ensure what we did didn't corrupt the page // Ensure what we did didn't corrupt the page
cur_p.page.data.assertIntegrity(); cur_p.node.data.assertIntegrity();
} else { } else {
// Left/right scroll margins we have to // Left/right scroll margins we have to
// copy cells, which is much slower... // copy cells, which is much slower...
const page = &cur_p.page.data; const page = &cur_p.node.data;
page.moveCells( page.moveCells(
src_row, src_row,
self.scrolling_region.left, self.scrolling_region.left,
@ -1798,7 +1798,7 @@ pub fn deleteLines(self: *Terminal, count: usize) void {
} }
} else { } else {
// Clear the cells for this row, it's from out of bounds. // 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); const cells = page.getCells(cur_row);
self.screen.clearCells( self.screen.clearCells(
page, page,
@ -1843,7 +1843,7 @@ pub fn insertBlanks(self: *Terminal, count: usize) void {
// left is just the cursor position but as a multi-pointer // left is just the cursor position but as a multi-pointer
const left: [*]Cell = @ptrCast(self.screen.cursor.page_cell); 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 // 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. // 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 // left is just the cursor position but as a multi-pointer
const left: [*]Cell = @ptrCast(self.screen.cursor.page_cell); 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. // Remaining cols from our cursor to the right margin.
const rem = self.scrolling_region.right - self.screen.cursor.x + 1; 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. // mode was not ISO we also always ignore protection attributes.
if (self.screen.protected_mode != .iso) { if (self.screen.protected_mode != .iso) {
self.screen.clearCells( self.screen.clearCells(
&self.screen.cursor.page_pin.page.data, &self.screen.cursor.page_pin.node.data,
self.screen.cursor.page_row, self.screen.cursor.page_row,
cells[0..end], cells[0..end],
); );
@ -2003,7 +2003,7 @@ pub fn eraseChars(self: *Terminal, count_req: usize) void {
} }
self.screen.clearUnprotectedCells( self.screen.clearUnprotectedCells(
&self.screen.cursor.page_pin.page.data, &self.screen.cursor.page_pin.node.data,
self.screen.cursor.page_row, self.screen.cursor.page_row,
cells[0..end], cells[0..end],
); );
@ -2075,7 +2075,7 @@ pub fn eraseLine(
// to fill the entire line. // to fill the entire line.
if (!protected) { if (!protected) {
self.screen.clearCells( self.screen.clearCells(
&self.screen.cursor.page_pin.page.data, &self.screen.cursor.page_pin.node.data,
self.screen.cursor.page_row, self.screen.cursor.page_row,
cells[start..end], cells[start..end],
); );
@ -2083,7 +2083,7 @@ pub fn eraseLine(
} }
self.screen.clearUnprotectedCells( self.screen.clearUnprotectedCells(
&self.screen.cursor.page_pin.page.data, &self.screen.cursor.page_pin.node.data,
self.screen.cursor.page_row, self.screen.cursor.page_row,
cells[start..end], cells[start..end],
); );
@ -2257,7 +2257,7 @@ pub fn decaln(self: *Terminal) !void {
// Fill with Es by moving the cursor but reset it after. // Fill with Es by moving the cursor but reset it after.
while (true) { 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 row = self.screen.cursor.page_row;
const cells_multi: [*]Cell = row.cells.ptr(page.memory); const cells_multi: [*]Cell = row.cells.ptr(page.memory);
const cells = cells_multi[0..page.size.cols]; 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 try t.print(0x1F600); // Smiley face
// verify we have styles in our style map // 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()); 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 // 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()); 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 try t.print(0x1F600); // Smiley face
// verify we have styles in our style map // 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()); 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 // 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()); 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.expectEqual(@as(u21, 0x1F468), cell.content.codepoint);
try testing.expect(cell.hasGrapheme()); try testing.expect(cell.hasGrapheme());
try testing.expectEqual(Cell.Wide.wide, cell.wide); 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); 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.expectEqual(@as(u21, 0), cell.content.codepoint);
try testing.expect(!cell.hasGrapheme()); try testing.expect(!cell.hasGrapheme());
try testing.expectEqual(Cell.Wide.spacer_tail, cell.wide); 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 } }).?; 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.expectEqual(@as(u21, 0x1F469), cell.content.codepoint);
try testing.expect(cell.hasGrapheme()); try testing.expect(cell.hasGrapheme());
try testing.expectEqual(Cell.Wide.wide, cell.wide); 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); 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.expectEqual(@as(u21, 0), cell.content.codepoint);
try testing.expect(!cell.hasGrapheme()); try testing.expect(!cell.hasGrapheme());
try testing.expectEqual(Cell.Wide.spacer_tail, cell.wide); 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 } }).?; 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.expectEqual(@as(u21, 0x1F467), cell.content.codepoint);
try testing.expect(!cell.hasGrapheme()); try testing.expect(!cell.hasGrapheme());
try testing.expectEqual(Cell.Wide.wide, cell.wide); 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 } }).?; 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.expectEqual(@as(u21, 0), cell.content.codepoint);
try testing.expect(!cell.hasGrapheme()); try testing.expect(!cell.hasGrapheme());
try testing.expectEqual(Cell.Wide.spacer_tail, cell.wide); 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 } })); 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.expectEqual(@as(u21, 0x2764), cell.content.codepoint);
try testing.expect(cell.hasGrapheme()); try testing.expect(cell.hasGrapheme());
try testing.expectEqual(Cell.Wide.narrow, cell.wide); 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); 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.expectEqual(@as(u21, 0x1F468), cell.content.codepoint);
try testing.expect(cell.hasGrapheme()); try testing.expect(cell.hasGrapheme());
try testing.expectEqual(Cell.Wide.wide, cell.wide); 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); 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.expectEqual(@as(u21, 0x26C8), cell.content.codepoint);
try testing.expect(cell.hasGrapheme()); try testing.expect(cell.hasGrapheme());
try testing.expectEqual(Cell.Wide.narrow, cell.wide); 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); 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.expectEqual(@as(u21, 0x2764), cell.content.codepoint);
try testing.expect(cell.hasGrapheme()); try testing.expect(cell.hasGrapheme());
try testing.expectEqual(Cell.Wide.wide, cell.wide); 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); 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.expectEqual(@as(u21, 0x2764), cell.content.codepoint);
try testing.expect(cell.hasGrapheme()); try testing.expect(cell.hasGrapheme());
try testing.expectEqual(Cell.Wide.wide, cell.wide); 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); 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.expectEqual(@as(u21, 0x2764), cell.content.codepoint);
try testing.expect(cell.hasGrapheme()); try testing.expect(cell.hasGrapheme());
try testing.expectEqual(Cell.Wide.wide, cell.wide); 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); 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); try testing.expectEqual(@as(usize, 2), t.screen.cursor.x);
// We should have one cell with graphemes // 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()); try testing.expectEqual(@as(usize, 1), page.graphemeCount());
// Move back and overwrite wide // 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); try testing.expectEqual(@as(usize, 2), t.screen.cursor.x);
// We should have one cell with graphemes // 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()); try testing.expectEqual(@as(usize, 1), page.graphemeCount());
// Move back and overwrite wide // Move back and overwrite wide
@ -3971,7 +3971,7 @@ test "Terminal: print with hyperlink" {
try testing.expect(row.hyperlink); try testing.expect(row.hyperlink);
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(cell.hyperlink); 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); 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); try testing.expect(row.hyperlink);
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(cell.hyperlink); 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); try testing.expectEqual(@as(hyperlink.Id, 1), id);
} }
@ -4025,7 +4025,7 @@ test "Terminal: print and end hyperlink" {
try testing.expect(row.hyperlink); try testing.expect(row.hyperlink);
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(cell.hyperlink); 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); try testing.expectEqual(@as(hyperlink.Id, 1), id);
} }
for (3..6) |x| { for (3..6) |x| {
@ -4060,7 +4060,7 @@ test "Terminal: print and change hyperlink" {
} }).?; } }).?;
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(cell.hyperlink); 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); try testing.expectEqual(@as(hyperlink.Id, 1), id);
} }
for (3..6) |x| { for (3..6) |x| {
@ -4070,7 +4070,7 @@ test "Terminal: print and change hyperlink" {
} }).?; } }).?;
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(cell.hyperlink); 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); try testing.expectEqual(@as(hyperlink.Id, 2), id);
} }
@ -4094,7 +4094,7 @@ test "Terminal: overwrite hyperlink" {
.x = @intCast(x), .x = @intCast(x),
.y = 0, .y = 0,
} }).?; } }).?;
const page = &list_cell.page.data; const page = &list_cell.node.data;
const row = list_cell.row; const row = list_cell.row;
try testing.expect(!row.hyperlink); try testing.expect(!row.hyperlink);
const cell = list_cell.cell; const cell = list_cell.cell;
@ -4865,7 +4865,7 @@ test "Terminal: insertLines handles style refs" {
try t.setAttribute(.{ .unset = {} }); try t.setAttribute(.{ .unset = {} });
// verify we have styles in our style map // 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()); try testing.expectEqual(@as(usize, 1), page.styles.count());
t.setCursorPos(2, 2); t.setCursorPos(2, 2);
@ -5233,9 +5233,9 @@ test "Terminal: scrollUp moves hyperlink" {
try testing.expect(row.hyperlink); try testing.expect(row.hyperlink);
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(cell.hyperlink); 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); 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()); try testing.expectEqual(1, page.hyperlink_set.count());
} }
for (0..3) |x| { for (0..3) |x| {
@ -5247,7 +5247,7 @@ test "Terminal: scrollUp moves hyperlink" {
try testing.expect(!row.hyperlink); try testing.expect(!row.hyperlink);
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(!cell.hyperlink); 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); try testing.expect(id == null);
} }
} }
@ -5284,7 +5284,7 @@ test "Terminal: scrollUp clears hyperlink" {
try testing.expect(!row.hyperlink); try testing.expect(!row.hyperlink);
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(!cell.hyperlink); 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); try testing.expect(id == null);
} }
} }
@ -5386,7 +5386,7 @@ test "Terminal: scrollUp left/right scroll region hyperlink" {
} }).?; } }).?;
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(!cell.hyperlink); 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); try testing.expect(id == null);
} }
for (1..4) |x| { for (1..4) |x| {
@ -5398,9 +5398,9 @@ test "Terminal: scrollUp left/right scroll region hyperlink" {
try testing.expect(row.hyperlink); try testing.expect(row.hyperlink);
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(cell.hyperlink); 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); 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()); try testing.expectEqual(1, page.hyperlink_set.count());
} }
for (4..6) |x| { for (4..6) |x| {
@ -5410,7 +5410,7 @@ test "Terminal: scrollUp left/right scroll region hyperlink" {
} }).?; } }).?;
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(!cell.hyperlink); 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); try testing.expect(id == null);
} }
} }
@ -5426,9 +5426,9 @@ test "Terminal: scrollUp left/right scroll region hyperlink" {
try testing.expect(row.hyperlink); try testing.expect(row.hyperlink);
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(cell.hyperlink); 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); 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()); try testing.expectEqual(1, page.hyperlink_set.count());
} }
for (1..4) |x| { for (1..4) |x| {
@ -5438,7 +5438,7 @@ test "Terminal: scrollUp left/right scroll region hyperlink" {
} }).?; } }).?;
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(!cell.hyperlink); 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); try testing.expect(id == null);
} }
for (4..6) |x| { for (4..6) |x| {
@ -5450,9 +5450,9 @@ test "Terminal: scrollUp left/right scroll region hyperlink" {
try testing.expect(row.hyperlink); try testing.expect(row.hyperlink);
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(cell.hyperlink); 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); 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()); try testing.expectEqual(1, page.hyperlink_set.count());
} }
} }
@ -5596,9 +5596,9 @@ test "Terminal: scrollDown hyperlink moves" {
try testing.expect(row.hyperlink); try testing.expect(row.hyperlink);
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(cell.hyperlink); 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); 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()); try testing.expectEqual(1, page.hyperlink_set.count());
} }
for (0..3) |x| { for (0..3) |x| {
@ -5610,7 +5610,7 @@ test "Terminal: scrollDown hyperlink moves" {
try testing.expect(!row.hyperlink); try testing.expect(!row.hyperlink);
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(!cell.hyperlink); 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); try testing.expect(id == null);
} }
} }
@ -5720,9 +5720,9 @@ test "Terminal: scrollDown left/right scroll region hyperlink" {
try testing.expect(row.hyperlink); try testing.expect(row.hyperlink);
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(cell.hyperlink); 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); 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()); try testing.expectEqual(1, page.hyperlink_set.count());
} }
for (1..4) |x| { for (1..4) |x| {
@ -5732,7 +5732,7 @@ test "Terminal: scrollDown left/right scroll region hyperlink" {
} }).?; } }).?;
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(!cell.hyperlink); 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); try testing.expect(id == null);
} }
for (4..6) |x| { for (4..6) |x| {
@ -5744,9 +5744,9 @@ test "Terminal: scrollDown left/right scroll region hyperlink" {
try testing.expect(row.hyperlink); try testing.expect(row.hyperlink);
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(cell.hyperlink); 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); 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()); 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; const cell = list_cell.cell;
try testing.expect(!cell.hyperlink); 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); try testing.expect(id == null);
} }
for (1..4) |x| { for (1..4) |x| {
@ -5772,9 +5772,9 @@ test "Terminal: scrollDown left/right scroll region hyperlink" {
try testing.expect(row.hyperlink); try testing.expect(row.hyperlink);
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(cell.hyperlink); 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); 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()); try testing.expectEqual(1, page.hyperlink_set.count());
} }
for (4..6) |x| { for (4..6) |x| {
@ -5784,7 +5784,7 @@ test "Terminal: scrollDown left/right scroll region hyperlink" {
} }).?; } }).?;
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(!cell.hyperlink); 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); try testing.expect(id == null);
} }
} }
@ -6018,7 +6018,7 @@ test "Terminal: eraseChars handles refcounted styles" {
try t.print('C'); try t.print('C');
// verify we have styles in our style map // 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()); try testing.expectEqual(@as(usize, 1), page.styles.count());
t.setCursorPos(1, 1); t.setCursorPos(1, 1);
@ -6095,7 +6095,7 @@ test "Terminal: eraseChars wide char boundary conditions" {
t.setCursorPos(1, 2); t.setCursorPos(1, 2);
t.eraseChars(3); 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); const str = try t.plainString(alloc);
@ -6122,7 +6122,7 @@ test "Terminal: eraseChars wide char wrap boundary conditions" {
t.setCursorPos(2, 2); t.setCursorPos(2, 2);
t.eraseChars(3); 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); const str = try t.plainString(alloc);
@ -6425,7 +6425,7 @@ test "Terminal: index scrolling with hyperlink" {
try testing.expect(row.hyperlink); try testing.expect(row.hyperlink);
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(cell.hyperlink); 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); try testing.expectEqual(@as(hyperlink.Id, 1), id);
} }
{ {
@ -6437,7 +6437,7 @@ test "Terminal: index scrolling with hyperlink" {
try testing.expect(!row.hyperlink); try testing.expect(!row.hyperlink);
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(!cell.hyperlink); 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); try testing.expect(id == null);
} }
} }
@ -6599,7 +6599,7 @@ test "Terminal: index bottom of scroll region with hyperlinks" {
try testing.expect(row.hyperlink); try testing.expect(row.hyperlink);
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(cell.hyperlink); 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); 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); try testing.expect(!row.hyperlink);
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(!cell.hyperlink); 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); try testing.expect(id == null);
} }
} }
@ -6648,9 +6648,9 @@ test "Terminal: index bottom of scroll region clear hyperlinks" {
try testing.expect(!row.hyperlink); try testing.expect(!row.hyperlink);
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(!cell.hyperlink); 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); 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()); try testing.expectEqual(0, page.hyperlink_set.count());
} }
} }
@ -7972,7 +7972,7 @@ test "Terminal: bold style" {
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expectEqual(@as(u21, 'A'), cell.content.codepoint); try testing.expectEqual(@as(u21, 'A'), cell.content.codepoint);
try testing.expect(cell.style_id != 0); 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); 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 // 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()); 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 // 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()); try testing.expectEqual(@as(usize, 1), page.styles.count());
} }
@ -8390,7 +8390,7 @@ test "Terminal: insertBlanks deleting graphemes" {
try t.print(0x1F467); try t.print(0x1F467);
// We should have one cell with graphemes // 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()); try testing.expectEqual(@as(usize, 1), page.graphemeCount());
t.setCursorPos(1, 1); t.setCursorPos(1, 1);
@ -8426,7 +8426,7 @@ test "Terminal: insertBlanks shift graphemes" {
try t.print(0x1F467); try t.print(0x1F467);
// We should have one cell with graphemes // 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()); try testing.expectEqual(@as(usize, 1), page.graphemeCount());
t.setCursorPos(1, 1); t.setCursorPos(1, 1);
@ -8494,7 +8494,7 @@ test "Terminal: insertBlanks shifts hyperlinks" {
try testing.expect(row.hyperlink); try testing.expect(row.hyperlink);
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(cell.hyperlink); 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); try testing.expectEqual(@as(hyperlink.Id, 1), id);
} }
for (0..2) |x| { for (0..2) |x| {
@ -8504,7 +8504,7 @@ test "Terminal: insertBlanks shifts hyperlinks" {
} }).?; } }).?;
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(!cell.hyperlink); 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); try testing.expect(id == null);
} }
} }
@ -8534,7 +8534,7 @@ test "Terminal: insertBlanks pushes hyperlink off end completely" {
try testing.expect(!row.hyperlink); try testing.expect(!row.hyperlink);
const cell = list_cell.cell; const cell = list_cell.cell;
try testing.expect(!cell.hyperlink); 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); try testing.expect(id == null);
} }
} }
@ -9036,7 +9036,7 @@ test "Terminal: deleteChars wide char boundary conditions" {
t.setCursorPos(1, 2); t.setCursorPos(1, 2);
t.deleteChars(3); 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); const str = try t.plainString(alloc);
@ -9088,7 +9088,7 @@ test "Terminal: deleteChars wide char wrap boundary conditions" {
t.setCursorPos(2, 2); t.setCursorPos(2, 2);
t.deleteChars(3); 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); const str = try t.plainString(alloc);
@ -9127,7 +9127,7 @@ test "Terminal: deleteChars wide char across right margin" {
t.setCursorPos(1, 2); t.setCursorPos(1, 2);
t.deleteChars(1); 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 // NOTE: This behavior is slightly inconsistent with xterm. xterm
// _visually_ splits the wide character (half the wide character shows // _visually_ splits the wide character (half the wide character shows

View File

@ -20,7 +20,7 @@ const shell_integration = @import("shell_integration.zig");
const terminal = @import("../terminal/main.zig"); const terminal = @import("../terminal/main.zig");
const termio = @import("../termio.zig"); const termio = @import("../termio.zig");
const Command = @import("../Command.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 ptypkg = @import("../pty.zig");
const Pty = ptypkg.Pty; const Pty = ptypkg.Pty;
const EnvMap = std.process.EnvMap; const EnvMap = std.process.EnvMap;

View File

@ -15,7 +15,6 @@ const posix = std.posix;
const termio = @import("../termio.zig"); const termio = @import("../termio.zig");
const Command = @import("../Command.zig"); const Command = @import("../Command.zig");
const Pty = @import("../pty.zig").Pty; const Pty = @import("../pty.zig").Pty;
const SegmentedPool = @import("../segmented_pool.zig").SegmentedPool;
const StreamHandler = @import("stream_handler.zig").StreamHandler; const StreamHandler = @import("stream_handler.zig").StreamHandler;
const terminal = @import("../terminal/main.zig"); const terminal = @import("../terminal/main.zig");
const terminfo = @import("../terminfo/main.zig"); const terminfo = @import("../terminfo/main.zig");

View File

@ -17,7 +17,7 @@ const builtin = @import("builtin");
const xev = @import("xev"); const xev = @import("xev");
const crash = @import("../crash/main.zig"); const crash = @import("../crash/main.zig");
const termio = @import("../termio.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 Allocator = std.mem.Allocator;
const log = std.log.scoped(.io_thread); const log = std.log.scoped(.io_thread);

View File

@ -12,7 +12,6 @@ const shell_integration = @import("shell_integration.zig");
const terminal = @import("../terminal/main.zig"); const terminal = @import("../terminal/main.zig");
const termio = @import("../termio.zig"); const termio = @import("../termio.zig");
const Command = @import("../Command.zig"); const Command = @import("../Command.zig");
const SegmentedPool = @import("../segmented_pool.zig").SegmentedPool;
const Pty = @import("../pty.zig").Pty; const Pty = @import("../pty.zig").Pty;
// The preallocation size for the write request pool. This should be big // The preallocation size for the write request pool. This should be big

View File

@ -5,7 +5,7 @@ const Allocator = std.mem.Allocator;
const xev = @import("xev"); const xev = @import("xev");
const renderer = @import("../renderer.zig"); const renderer = @import("../renderer.zig");
const termio = @import("../termio.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); const log = std.log.scoped(.io_writer);