mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
Merge remote-tracking branch 'origin/main' into feature-cursor-height
This commit is contained in:
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@ -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
|
||||||
|
@ -789,7 +789,14 @@ Below is an example:
|
|||||||
#
|
#
|
||||||
# Instead, either run `nix flake update` or `nixos-rebuild build`
|
# Instead, either run `nix flake update` or `nixos-rebuild build`
|
||||||
# as the current user, and then run `sudo nixos-rebuild switch`.
|
# as the current user, and then run `sudo nixos-rebuild switch`.
|
||||||
ghostty.url = "git+ssh://git@github.com/ghostty-org/ghostty";
|
ghostty = {
|
||||||
|
url = "git+ssh://git@github.com/ghostty-org/ghostty";
|
||||||
|
|
||||||
|
# NOTE: The below 2 lines are only required on nixos-unstable,
|
||||||
|
# if you're on stable, they may break your build
|
||||||
|
inputs.nixpkgs-stable.follows = "nixpkgs";
|
||||||
|
inputs.nixpkgs-unstable.follows = "nixpkgs";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { nixpkgs, ghostty, ... }: {
|
outputs = { nixpkgs, ghostty, ... }: {
|
||||||
|
34
build.zig
34
build.zig
@ -10,6 +10,7 @@ const font = @import("src/font/main.zig");
|
|||||||
const renderer = @import("src/renderer.zig");
|
const renderer = @import("src/renderer.zig");
|
||||||
const terminfo = @import("src/terminfo/main.zig");
|
const terminfo = @import("src/terminfo/main.zig");
|
||||||
const config_vim = @import("src/config/vim.zig");
|
const config_vim = @import("src/config/vim.zig");
|
||||||
|
const config_sublime_syntax = @import("src/config/sublime_syntax.zig");
|
||||||
const fish_completions = @import("src/build/fish_completions.zig");
|
const fish_completions = @import("src/build/fish_completions.zig");
|
||||||
const build_config = @import("src/build_config.zig");
|
const build_config = @import("src/build_config.zig");
|
||||||
const BuildConfig = build_config.BuildConfig;
|
const BuildConfig = build_config.BuildConfig;
|
||||||
@ -91,12 +92,18 @@ 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 pie = b.option(
|
||||||
|
bool,
|
||||||
|
"pie",
|
||||||
|
"Build a Position Independent Executable",
|
||||||
|
) orelse false;
|
||||||
|
|
||||||
const conformance = b.option(
|
const conformance = b.option(
|
||||||
[]const u8,
|
[]const u8,
|
||||||
"conformance",
|
"conformance",
|
||||||
@ -281,6 +288,9 @@ pub fn build(b: *std.Build) !void {
|
|||||||
|
|
||||||
// Exe
|
// Exe
|
||||||
if (exe_) |exe| {
|
if (exe_) |exe| {
|
||||||
|
// Set PIE if requested
|
||||||
|
if (pie) exe.pie = true;
|
||||||
|
|
||||||
// Add the shared dependencies
|
// Add the shared dependencies
|
||||||
_ = try addDeps(b, exe, config);
|
_ = try addDeps(b, exe, config);
|
||||||
|
|
||||||
@ -515,6 +525,22 @@ pub fn build(b: *std.Build) !void {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sublime syntax highlighting for bat cli tool
|
||||||
|
// NOTE: The current implementation requires symlinking the generated
|
||||||
|
// 'ghostty.sublime-syntax' file from zig-out to the '~.config/bat/syntaxes'
|
||||||
|
// directory. The syntax then needs to be mapped to the correct language in
|
||||||
|
// the config file within the '~.config/bat' directory
|
||||||
|
// (ex: --map-syntax "/Users/user/.config/ghostty/config:Ghostty Config").
|
||||||
|
{
|
||||||
|
const wf = b.addWriteFiles();
|
||||||
|
_ = wf.add("ghostty.sublime-syntax", config_sublime_syntax.syntax);
|
||||||
|
b.installDirectory(.{
|
||||||
|
.source_dir = wf.getDirectory(),
|
||||||
|
.install_dir = .prefix,
|
||||||
|
.install_subdir = "share/bat/syntaxes",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Documentation
|
// Documentation
|
||||||
if (emit_docs) {
|
if (emit_docs) {
|
||||||
try buildDocumentation(b, config);
|
try buildDocumentation(b, config);
|
||||||
@ -1304,7 +1330,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");
|
||||||
|
@ -49,8 +49,8 @@
|
|||||||
// Other
|
// Other
|
||||||
.apple_sdk = .{ .path = "./pkg/apple-sdk" },
|
.apple_sdk = .{ .path = "./pkg/apple-sdk" },
|
||||||
.iterm2_themes = .{
|
.iterm2_themes = .{
|
||||||
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/b4a9c4d.tar.gz",
|
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/da56d590c4237c96d81cc5ed987ea098eebefdf6.tar.gz",
|
||||||
.hash = "122056fbb29863ec1678b7954fb76b1533ad8c581a34577c1b2efe419e29e05596df",
|
.hash = "1220fac17a112b0dd11ec85e5b31a30f05bfaed897c03f31285276544db30d010c41",
|
||||||
},
|
},
|
||||||
.vaxis = .{
|
.vaxis = .{
|
||||||
.url = "git+https://github.com/rockorager/libvaxis?ref=main#a1b43d24653670d612b91f0855b165e6c987b809",
|
.url = "git+https://github.com/rockorager/libvaxis?ref=main#a1b43d24653670d612b91f0855b165e6c987b809",
|
||||||
|
@ -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 */,
|
||||||
|
@ -704,12 +704,6 @@ extension Ghostty {
|
|||||||
|
|
||||||
/// Special case handling for some control keys
|
/// Special case handling for some control keys
|
||||||
override func performKeyEquivalent(with event: NSEvent) -> Bool {
|
override func performKeyEquivalent(with event: NSEvent) -> Bool {
|
||||||
// Only process keys when Control is the only modifier
|
|
||||||
if (!event.modifierFlags.contains(.control) ||
|
|
||||||
!event.modifierFlags.isDisjoint(with: [.shift, .command, .option])) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only process key down events
|
// Only process key down events
|
||||||
if (event.type != .keyDown) {
|
if (event.type != .keyDown) {
|
||||||
return false
|
return false
|
||||||
@ -722,11 +716,23 @@ extension Ghostty {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only process keys when Control is active. All known issues we're
|
||||||
|
// resolving happen only in this scenario. This probably isn't fully robust
|
||||||
|
// but we can broaden the scope as we find more cases.
|
||||||
|
if (!event.modifierFlags.contains(.control)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
let equivalent: String
|
let equivalent: String
|
||||||
switch (event.charactersIgnoringModifiers) {
|
switch (event.charactersIgnoringModifiers) {
|
||||||
case "/":
|
case "/":
|
||||||
// Treat C-/ as C-_. We do this because C-/ makes macOS make a beep
|
// Treat C-/ as C-_. We do this because C-/ makes macOS make a beep
|
||||||
// sound and we don't like the beep sound.
|
// sound and we don't like the beep sound.
|
||||||
|
if (!event.modifierFlags.contains(.control) ||
|
||||||
|
!event.modifierFlags.isDisjoint(with: [.shift, .command, .option])) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
equivalent = "_"
|
equivalent = "_"
|
||||||
|
|
||||||
case "\r":
|
case "\r":
|
||||||
@ -742,7 +748,7 @@ extension Ghostty {
|
|||||||
let newEvent = NSEvent.keyEvent(
|
let newEvent = NSEvent.keyEvent(
|
||||||
with: .keyDown,
|
with: .keyDown,
|
||||||
location: event.locationInWindow,
|
location: event.locationInWindow,
|
||||||
modifierFlags: .control,
|
modifierFlags: event.modifierFlags,
|
||||||
timestamp: event.timestamp,
|
timestamp: event.timestamp,
|
||||||
windowNumber: event.windowNumber,
|
windowNumber: event.windowNumber,
|
||||||
context: nil,
|
context: nil,
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
# This file is auto-generated! check build-support/check-zig-cache-hash.sh for
|
# This file is auto-generated! check build-support/check-zig-cache-hash.sh for
|
||||||
# more details.
|
# more details.
|
||||||
"sha256-dNGDbVaPxDbIrDUkDwjzeRHHVcX4KnWKciXiTp1c7lE="
|
"sha256-fTNqNTfElvZPxJiNQJ/RxrSMCiKZPU3705CY7fznKhY="
|
||||||
|
@ -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");
|
||||||
@ -66,6 +66,11 @@ font_grid_set: font.SharedGridSet,
|
|||||||
last_notification_time: ?std.time.Instant = null,
|
last_notification_time: ?std.time.Instant = null,
|
||||||
last_notification_digest: u64 = 0,
|
last_notification_digest: u64 = 0,
|
||||||
|
|
||||||
|
/// Set to false once we've created at least one surface. This
|
||||||
|
/// never goes true again. This can be used by surfaces to determine
|
||||||
|
/// if they are the first surface.
|
||||||
|
first: bool = true,
|
||||||
|
|
||||||
pub const CreateError = Allocator.Error || font.SharedGridSet.InitError;
|
pub const CreateError = Allocator.Error || font.SharedGridSet.InitError;
|
||||||
|
|
||||||
/// Initialize the main app instance. This creates the main window, sets
|
/// Initialize the main app instance. This creates the main window, sets
|
||||||
|
@ -474,13 +474,19 @@ pub fn init(
|
|||||||
.config = derived_config,
|
.config = derived_config,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The command we're going to execute
|
||||||
|
const command: ?[]const u8 = if (app.first)
|
||||||
|
config.@"initial-command" orelse config.command
|
||||||
|
else
|
||||||
|
config.command;
|
||||||
|
|
||||||
// Start our IO implementation
|
// Start our IO implementation
|
||||||
// This separate block ({}) is important because our errdefers must
|
// This separate block ({}) is important because our errdefers must
|
||||||
// be scoped here to be valid.
|
// be scoped here to be valid.
|
||||||
{
|
{
|
||||||
// Initialize our IO backend
|
// Initialize our IO backend
|
||||||
var io_exec = try termio.Exec.init(alloc, .{
|
var io_exec = try termio.Exec.init(alloc, .{
|
||||||
.command = config.command,
|
.command = command,
|
||||||
.shell_integration = config.@"shell-integration",
|
.shell_integration = config.@"shell-integration",
|
||||||
.shell_integration_features = config.@"shell-integration-features",
|
.shell_integration_features = config.@"shell-integration-features",
|
||||||
.working_directory = config.@"working-directory",
|
.working_directory = config.@"working-directory",
|
||||||
@ -618,9 +624,9 @@ pub fn init(
|
|||||||
// For xdg-terminal-exec execution we special-case and set the window
|
// For xdg-terminal-exec execution we special-case and set the window
|
||||||
// title to the command being executed. This allows window managers
|
// title to the command being executed. This allows window managers
|
||||||
// to set custom styling based on the command being executed.
|
// to set custom styling based on the command being executed.
|
||||||
const command = config.command orelse break :xdg;
|
const v = command orelse break :xdg;
|
||||||
if (command.len > 0) {
|
if (v.len > 0) {
|
||||||
const title = alloc.dupeZ(u8, command) catch |err| {
|
const title = alloc.dupeZ(u8, v) catch |err| {
|
||||||
log.warn(
|
log.warn(
|
||||||
"error copying command for title, title will not be set err={}",
|
"error copying command for title, title will not be set err={}",
|
||||||
.{err},
|
.{err},
|
||||||
@ -635,6 +641,9 @@ pub fn init(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We are no longer the first surface
|
||||||
|
app.first = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Surface) void {
|
pub fn deinit(self: *Surface) void {
|
||||||
@ -3106,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);
|
||||||
|
@ -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,
|
||||||
|
@ -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 (direction) {
|
const orientation: c_uint = switch (direction) {
|
||||||
@ -258,7 +259,7 @@ pub fn grabFocus(self: *Split) void {
|
|||||||
/// Update the paned children to represent the current state.
|
/// 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).
|
||||||
@ -372,7 +373,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();
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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());
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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)).?,
|
||||||
|
@ -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");
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
@ -289,6 +289,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.
|
||||||
@ -515,7 +538,26 @@ palette: Palette = .{},
|
|||||||
/// arguments are provided, the command will be executed using `/bin/sh -c`.
|
/// arguments are provided, the command will be executed using `/bin/sh -c`.
|
||||||
/// Ghostty does not do any shell command parsing.
|
/// Ghostty does not do any shell command parsing.
|
||||||
///
|
///
|
||||||
/// If you're using the `ghostty` CLI there is also a shortcut to run a command
|
/// This command will be used for all new terminal surfaces, i.e. new windows,
|
||||||
|
/// tabs, etc. If you want to run a command only for the first terminal surface
|
||||||
|
/// created when Ghostty starts, use the `initial-command` configuration.
|
||||||
|
///
|
||||||
|
/// Ghostty supports the common `-e` flag for executing a command with
|
||||||
|
/// arguments. For example, `ghostty -e fish --with --custom --args`.
|
||||||
|
/// This flag sets the `initial-command` configuration, see that for more
|
||||||
|
/// information.
|
||||||
|
command: ?[]const u8 = null,
|
||||||
|
|
||||||
|
/// This is the same as "command", but only applies to the first terminal
|
||||||
|
/// surface created when Ghostty starts. Subsequent terminal surfaces will use
|
||||||
|
/// the `command` configuration.
|
||||||
|
///
|
||||||
|
/// After the first terminal surface is created (or closed), there is no
|
||||||
|
/// way to run this initial command again automatically. As such, setting
|
||||||
|
/// this at runtime works but will only affect the next terminal surface
|
||||||
|
/// if it is the first one ever created.
|
||||||
|
///
|
||||||
|
/// If you're using the `ghostty` CLI there is also a shortcut to set this
|
||||||
/// with arguments directly: you can use the `-e` flag. For example: `ghostty -e
|
/// with arguments directly: you can use the `-e` flag. For example: `ghostty -e
|
||||||
/// fish --with --custom --args`. The `-e` flag automatically forces some
|
/// fish --with --custom --args`. The `-e` flag automatically forces some
|
||||||
/// other behaviors as well:
|
/// other behaviors as well:
|
||||||
@ -527,7 +569,7 @@ palette: Palette = .{},
|
|||||||
/// process will exit when the command exits. Additionally, the
|
/// process will exit when the command exits. Additionally, the
|
||||||
/// `quit-after-last-window-closed-delay` is unset.
|
/// `quit-after-last-window-closed-delay` is unset.
|
||||||
///
|
///
|
||||||
command: ?[]const u8 = null,
|
@"initial-command": ?[]const u8 = null,
|
||||||
|
|
||||||
/// If true, keep the terminal open after the command exits. Normally, the
|
/// If true, keep the terminal open after the command exits. Normally, the
|
||||||
/// terminal window closes when the running command (such as a shell) exits.
|
/// terminal window closes when the running command (such as a shell) exits.
|
||||||
@ -923,7 +965,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.
|
||||||
@ -1601,12 +1643,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:
|
||||||
@ -1625,7 +1667,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.
|
||||||
@ -1636,7 +1678,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
|
||||||
@ -2358,7 +2400,7 @@ pub fn loadCliArgs(self: *Config, alloc_gpa: Allocator) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.@"_xdg-terminal-exec" = true;
|
self.@"_xdg-terminal-exec" = true;
|
||||||
self.command = command.items[0 .. command.items.len - 1];
|
self.@"initial-command" = command.items[0 .. command.items.len - 1];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2757,7 +2799,7 @@ pub fn parseManuallyHook(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.command = command.items[0 .. command.items.len - 1];
|
self.@"initial-command" = command.items[0 .. command.items.len - 1];
|
||||||
|
|
||||||
// See "command" docs for the implied configurations and why.
|
// See "command" docs for the implied configurations and why.
|
||||||
self.@"gtk-single-instance" = .false;
|
self.@"gtk-single-instance" = .false;
|
||||||
@ -2947,7 +2989,7 @@ test "parse e: command only" {
|
|||||||
|
|
||||||
var it: TestIterator = .{ .data = &.{"foo"} };
|
var it: TestIterator = .{ .data = &.{"foo"} };
|
||||||
try testing.expect(!try cfg.parseManuallyHook(alloc, "-e", &it));
|
try testing.expect(!try cfg.parseManuallyHook(alloc, "-e", &it));
|
||||||
try testing.expectEqualStrings("foo", cfg.command.?);
|
try testing.expectEqualStrings("foo", cfg.@"initial-command".?);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "parse e: command and args" {
|
test "parse e: command and args" {
|
||||||
@ -2958,7 +3000,7 @@ test "parse e: command and args" {
|
|||||||
|
|
||||||
var it: TestIterator = .{ .data = &.{ "echo", "foo", "bar baz" } };
|
var it: TestIterator = .{ .data = &.{ "echo", "foo", "bar baz" } };
|
||||||
try testing.expect(!try cfg.parseManuallyHook(alloc, "-e", &it));
|
try testing.expect(!try cfg.parseManuallyHook(alloc, "-e", &it));
|
||||||
try testing.expectEqualStrings("echo foo bar baz", cfg.command.?);
|
try testing.expectEqualStrings("echo foo bar baz", cfg.@"initial-command".?);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "clone default" {
|
test "clone default" {
|
||||||
@ -4548,6 +4590,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,
|
||||||
|
51
src/config/sublime_syntax.zig
Normal file
51
src/config/sublime_syntax.zig
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Config = @import("Config.zig");
|
||||||
|
|
||||||
|
const Template = struct {
|
||||||
|
const header =
|
||||||
|
\\%YAML 1.2
|
||||||
|
\\---
|
||||||
|
\\# See http://www.sublimetext.com/docs/syntax.html
|
||||||
|
\\name: Ghostty Config
|
||||||
|
\\file_extensions:
|
||||||
|
\\ - ghostty
|
||||||
|
\\scope: source.ghostty
|
||||||
|
\\
|
||||||
|
\\contexts:
|
||||||
|
\\ main:
|
||||||
|
\\ # Comments
|
||||||
|
\\ - match: '#.*$'
|
||||||
|
\\ scope: comment.line.number-sign.ghostty
|
||||||
|
\\
|
||||||
|
\\ # Keywords
|
||||||
|
\\ - match: '\b(
|
||||||
|
;
|
||||||
|
const footer =
|
||||||
|
\\)\b'
|
||||||
|
\\ scope: keyword.other.ghostty
|
||||||
|
\\
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Check if a field is internal (starts with underscore)
|
||||||
|
fn isInternal(name: []const u8) bool {
|
||||||
|
return name.len > 0 and name[0] == '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate keywords from Config fields
|
||||||
|
fn generateKeywords() []const u8 {
|
||||||
|
@setEvalBranchQuota(5000);
|
||||||
|
var keywords: []const u8 = "";
|
||||||
|
const config_fields = @typeInfo(Config).Struct.fields;
|
||||||
|
|
||||||
|
for (config_fields) |field| {
|
||||||
|
if (isInternal(field.name)) continue;
|
||||||
|
if (keywords.len > 0) keywords = keywords ++ "|";
|
||||||
|
keywords = keywords ++ field.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return keywords;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Complete Sublime syntax file content
|
||||||
|
pub const syntax = Template.header ++ generateKeywords() ++ Template.footer;
|
@ -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;
|
@ -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 {
|
181
src/datastruct/intrusive_linked_list.zig
Normal file
181
src/datastruct/intrusive_linked_list.zig
Normal 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
19
src/datastruct/main.zig
Normal 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());
|
||||||
|
}
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -57,6 +57,11 @@ pub const CoreText = struct {
|
|||||||
/// The initialized font
|
/// The initialized font
|
||||||
font: *macos.text.Font,
|
font: *macos.text.Font,
|
||||||
|
|
||||||
|
/// Variations to apply to this font. We apply the variations to the
|
||||||
|
/// search descriptor but sometimes when the font collection is
|
||||||
|
/// made the variation axes are reset so we have to reapply them.
|
||||||
|
variations: []const font.face.Variation,
|
||||||
|
|
||||||
pub fn deinit(self: *CoreText) void {
|
pub fn deinit(self: *CoreText) void {
|
||||||
self.font.release();
|
self.font.release();
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
@ -194,7 +199,10 @@ fn loadCoreText(
|
|||||||
) !Face {
|
) !Face {
|
||||||
_ = lib;
|
_ = lib;
|
||||||
const ct = self.ct.?;
|
const ct = self.ct.?;
|
||||||
return try Face.initFontCopy(ct.font, opts);
|
var face = try Face.initFontCopy(ct.font, opts);
|
||||||
|
errdefer face.deinit();
|
||||||
|
try face.setVariations(ct.variations, opts);
|
||||||
|
return face;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loadCoreTextFreetype(
|
fn loadCoreTextFreetype(
|
||||||
@ -236,43 +244,7 @@ fn loadCoreTextFreetype(
|
|||||||
//std.log.warn("path={s}", .{path_slice});
|
//std.log.warn("path={s}", .{path_slice});
|
||||||
var face = try Face.initFile(lib, buf[0..path_slice.len :0], 0, opts);
|
var face = try Face.initFile(lib, buf[0..path_slice.len :0], 0, opts);
|
||||||
errdefer face.deinit();
|
errdefer face.deinit();
|
||||||
|
try face.setVariations(ct.variations, opts);
|
||||||
// If our ct font has variations, apply them to the face.
|
|
||||||
if (ct.font.copyAttribute(.variation)) |variations| vars: {
|
|
||||||
defer variations.release();
|
|
||||||
if (variations.getCount() == 0) break :vars;
|
|
||||||
|
|
||||||
// This configuration is just used for testing so we don't want to
|
|
||||||
// have to pass a full allocator through so use the stack. We
|
|
||||||
// shouldn't have a lot of variations and if we do we should use
|
|
||||||
// another mechanism.
|
|
||||||
//
|
|
||||||
// On macOS the default stack size for a thread is 512KB and the main
|
|
||||||
// thread gets megabytes so 16KB is a safe stack allocation.
|
|
||||||
var data: [1024 * 16]u8 = undefined;
|
|
||||||
var fba = std.heap.FixedBufferAllocator.init(&data);
|
|
||||||
const alloc = fba.allocator();
|
|
||||||
|
|
||||||
var face_vars = std.ArrayList(font.face.Variation).init(alloc);
|
|
||||||
const kav = try variations.getKeysAndValues(alloc);
|
|
||||||
for (kav.keys, kav.values) |key, value| {
|
|
||||||
const num: *const macos.foundation.Number = @ptrCast(key.?);
|
|
||||||
const val: *const macos.foundation.Number = @ptrCast(value.?);
|
|
||||||
|
|
||||||
var num_i32: i32 = undefined;
|
|
||||||
if (!num.getValue(.sint32, &num_i32)) continue;
|
|
||||||
|
|
||||||
var val_f64: f64 = undefined;
|
|
||||||
if (!val.getValue(.float64, &val_f64)) continue;
|
|
||||||
|
|
||||||
try face_vars.append(.{
|
|
||||||
.id = @bitCast(num_i32),
|
|
||||||
.value = val_f64,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try face.setVariations(face_vars.items, opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
return face;
|
return face;
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
@ -428,6 +429,7 @@ pub const DerivedConfig = struct {
|
|||||||
@"adjust-strikethrough-thickness": ?Metrics.Modifier,
|
@"adjust-strikethrough-thickness": ?Metrics.Modifier,
|
||||||
@"adjust-cursor-thickness": ?Metrics.Modifier,
|
@"adjust-cursor-thickness": ?Metrics.Modifier,
|
||||||
@"adjust-cursor-height": ?Metrics.Modifier,
|
@"adjust-cursor-height": ?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.
|
||||||
@ -463,6 +465,7 @@ pub const DerivedConfig = struct {
|
|||||||
.@"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",
|
||||||
.@"adjust-cursor-height" = config.@"adjust-cursor-height",
|
.@"adjust-cursor-height" = config.@"adjust-cursor-height",
|
||||||
|
.@"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.
|
||||||
@ -502,6 +505,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;
|
||||||
|
|
||||||
@ -621,6 +628,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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -650,6 +661,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);
|
||||||
|
@ -79,7 +79,7 @@ pub const Descriptor = struct {
|
|||||||
|
|
||||||
// This is not correct, but we don't currently depend on the
|
// This is not correct, but we don't currently depend on the
|
||||||
// hash value being different based on decimal values of variations.
|
// hash value being different based on decimal values of variations.
|
||||||
autoHash(hasher, @as(u64, @intFromFloat(variation.value)));
|
autoHash(hasher, @as(i64, @intFromFloat(variation.value)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,21 +235,7 @@ pub const Descriptor = struct {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build our descriptor from attrs
|
return try macos.text.FontDescriptor.createWithAttributes(@ptrCast(attrs));
|
||||||
var desc = try macos.text.FontDescriptor.createWithAttributes(@ptrCast(attrs));
|
|
||||||
errdefer desc.release();
|
|
||||||
|
|
||||||
// Variations are built by copying the descriptor. I don't know a way
|
|
||||||
// to set it on attrs directly.
|
|
||||||
for (self.variations) |v| {
|
|
||||||
const id = try macos.foundation.Number.create(.int, @ptrCast(&v.id));
|
|
||||||
defer id.release();
|
|
||||||
const next = try desc.createCopyWithVariation(id, v.value);
|
|
||||||
desc.release();
|
|
||||||
desc = next;
|
|
||||||
}
|
|
||||||
|
|
||||||
return desc;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -384,6 +370,7 @@ pub const CoreText = struct {
|
|||||||
return DiscoverIterator{
|
return DiscoverIterator{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.list = zig_list,
|
.list = zig_list,
|
||||||
|
.variations = desc.variations,
|
||||||
.i = 0,
|
.i = 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -420,6 +407,7 @@ pub const CoreText = struct {
|
|||||||
return DiscoverIterator{
|
return DiscoverIterator{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.list = list,
|
.list = list,
|
||||||
|
.variations = desc.variations,
|
||||||
.i = 0,
|
.i = 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -443,6 +431,7 @@ pub const CoreText = struct {
|
|||||||
return DiscoverIterator{
|
return DiscoverIterator{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.list = list,
|
.list = list,
|
||||||
|
.variations = desc.variations,
|
||||||
.i = 0,
|
.i = 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -682,30 +671,29 @@ pub const CoreText = struct {
|
|||||||
break :style .unmatched;
|
break :style .unmatched;
|
||||||
defer style.release();
|
defer style.release();
|
||||||
|
|
||||||
|
// Get our style string
|
||||||
|
var buf: [128]u8 = undefined;
|
||||||
|
const style_str = style.cstring(&buf, .utf8) orelse break :style .unmatched;
|
||||||
|
|
||||||
// If we have a specific desired style, attempt to search for that.
|
// If we have a specific desired style, attempt to search for that.
|
||||||
if (desc.style) |desired_style| {
|
if (desc.style) |desired_style| {
|
||||||
var buf: [128]u8 = undefined;
|
|
||||||
const style_str = style.cstring(&buf, .utf8) orelse break :style .unmatched;
|
|
||||||
|
|
||||||
// Matching style string gets highest score
|
// Matching style string gets highest score
|
||||||
if (std.mem.eql(u8, desired_style, style_str)) break :style .match;
|
if (std.mem.eql(u8, desired_style, style_str)) break :style .match;
|
||||||
|
} else if (!desc.bold and !desc.italic) {
|
||||||
// Otherwise the score is based on the length of the style string.
|
// If we do not, and we have no symbolic traits, then we try
|
||||||
// Shorter styles are scored higher.
|
// to find "regular" (or no style). If we have symbolic traits
|
||||||
break :style @enumFromInt(100 -| style_str.len);
|
// we do nothing but we can improve scoring by taking that into
|
||||||
|
// account, too.
|
||||||
|
if (std.mem.eql(u8, "Regular", style_str)) {
|
||||||
|
break :style .match;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we do not, and we have no symbolic traits, then we try
|
// Otherwise the score is based on the length of the style string.
|
||||||
// to find "regular" (or no style). If we have symbolic traits
|
// Shorter styles are scored higher. This is a heuristic that
|
||||||
// we do nothing but we can improve scoring by taking that into
|
// if we don't have a desired style then shorter tends to be
|
||||||
// account, too.
|
// more often the "regular" style.
|
||||||
if (!desc.bold and !desc.italic) {
|
break :style @enumFromInt(100 -| style_str.len);
|
||||||
var buf: [128]u8 = undefined;
|
|
||||||
const style_str = style.cstring(&buf, .utf8) orelse break :style .unmatched;
|
|
||||||
if (std.mem.eql(u8, "Regular", style_str)) break :style .match;
|
|
||||||
}
|
|
||||||
|
|
||||||
break :style .unmatched;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
score_acc.traits = traits: {
|
score_acc.traits = traits: {
|
||||||
@ -721,6 +709,7 @@ pub const CoreText = struct {
|
|||||||
pub const DiscoverIterator = struct {
|
pub const DiscoverIterator = struct {
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
list: []const *macos.text.FontDescriptor,
|
list: []const *macos.text.FontDescriptor,
|
||||||
|
variations: []const Variation,
|
||||||
i: usize,
|
i: usize,
|
||||||
|
|
||||||
pub fn deinit(self: *DiscoverIterator) void {
|
pub fn deinit(self: *DiscoverIterator) void {
|
||||||
@ -756,7 +745,10 @@ pub const CoreText = struct {
|
|||||||
defer self.i += 1;
|
defer self.i += 1;
|
||||||
|
|
||||||
return DeferredFace{
|
return DeferredFace{
|
||||||
.ct = .{ .font = font },
|
.ct = .{
|
||||||
|
.font = font,
|
||||||
|
.variations = self.variations,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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.
|
||||||
|
@ -229,6 +229,9 @@ pub const Face = struct {
|
|||||||
vs: []const font.face.Variation,
|
vs: []const font.face.Variation,
|
||||||
opts: font.face.Options,
|
opts: font.face.Options,
|
||||||
) !void {
|
) !void {
|
||||||
|
// If we have no variations, we don't need to do anything.
|
||||||
|
if (vs.len == 0) return;
|
||||||
|
|
||||||
// Create a new font descriptor with all the variations set.
|
// Create a new font descriptor with all the variations set.
|
||||||
var desc = self.font.copyDescriptor();
|
var desc = self.font.copyDescriptor();
|
||||||
defer desc.release();
|
defer desc.release();
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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" {
|
||||||
|
@ -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,
|
||||||
|
BIN
src/font/sprite/testdata/Box.ppm
vendored
BIN
src/font/sprite/testdata/Box.ppm
vendored
Binary file not shown.
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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");
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
151
src/os/hostname.zig
Normal file
151
src/os/hostname.zig
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const posix = std.posix;
|
||||||
|
|
||||||
|
pub const HostnameParsingError = error{
|
||||||
|
NoHostnameInUri,
|
||||||
|
NoSpaceLeft,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Print the hostname from a file URI into a buffer.
|
||||||
|
pub fn bufPrintHostnameFromFileUri(
|
||||||
|
buf: []u8,
|
||||||
|
uri: std.Uri,
|
||||||
|
) HostnameParsingError![]const u8 {
|
||||||
|
// Get the raw string of the URI. Its unclear to me if the various
|
||||||
|
// tags of this enum guarantee no percent-encoding so we just
|
||||||
|
// check all of it. This isn't a performance critical path.
|
||||||
|
const host_component = uri.host orelse return error.NoHostnameInUri;
|
||||||
|
const host: []const u8 = switch (host_component) {
|
||||||
|
.raw => |v| v,
|
||||||
|
.percent_encoded => |v| v,
|
||||||
|
};
|
||||||
|
|
||||||
|
// When the "Private Wi-Fi address" setting is toggled on macOS the hostname
|
||||||
|
// is set to a random mac address, e.g. '12:34:56:78:90:ab'.
|
||||||
|
// The URI will be parsed as if the last set of digits is a port number, so
|
||||||
|
// we need to make sure that part is included when it's set.
|
||||||
|
|
||||||
|
// We're only interested in special port handling when the current hostname is a
|
||||||
|
// partial MAC address that's potentially missing the last component.
|
||||||
|
// If that's not the case we just return the plain URI hostname directly.
|
||||||
|
// NOTE: This implementation is not sufficient to verify a valid mac address, but
|
||||||
|
// it's probably sufficient for this specific purpose.
|
||||||
|
if (host.len != 14 or std.mem.count(u8, host, ":") != 4) return host;
|
||||||
|
|
||||||
|
// If we don't have a port then we can return the hostname as-is because
|
||||||
|
// it's not a partial MAC-address.
|
||||||
|
const port = uri.port orelse return host;
|
||||||
|
|
||||||
|
// If the port is not a 1 or 2-digit number we're not looking at a partial
|
||||||
|
// MAC-address, and instead just a regular port so we return the plain
|
||||||
|
// URI hostname.
|
||||||
|
if (port > 99) return host;
|
||||||
|
|
||||||
|
var fbs = std.io.fixedBufferStream(buf);
|
||||||
|
try std.fmt.format(
|
||||||
|
fbs.writer(),
|
||||||
|
// Make sure "port" is always 2 digits, prefixed with a 0 when "port" is a 1-digit number.
|
||||||
|
"{s}:{d:0>2}",
|
||||||
|
.{ host, port },
|
||||||
|
);
|
||||||
|
|
||||||
|
return fbs.getWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const LocalHostnameValidationError = error{
|
||||||
|
PermissionDenied,
|
||||||
|
Unexpected,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Checks if a hostname is local to the current machine. This matches
|
||||||
|
/// both "localhost" and the current hostname of the machine (as returned
|
||||||
|
/// by `gethostname`).
|
||||||
|
pub fn isLocalHostname(hostname: []const u8) LocalHostnameValidationError!bool {
|
||||||
|
// A 'localhost' hostname is always considered local.
|
||||||
|
if (std.mem.eql(u8, "localhost", hostname)) return true;
|
||||||
|
|
||||||
|
// If hostname is not "localhost" it must match our hostname.
|
||||||
|
var buf: [posix.HOST_NAME_MAX]u8 = undefined;
|
||||||
|
const ourHostname = try posix.gethostname(&buf);
|
||||||
|
return std.mem.eql(u8, hostname, ourHostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "bufPrintHostnameFromFileUri succeeds with ascii hostname" {
|
||||||
|
const uri = try std.Uri.parse("file://localhost/");
|
||||||
|
|
||||||
|
var buf: [posix.HOST_NAME_MAX]u8 = undefined;
|
||||||
|
const actual = try bufPrintHostnameFromFileUri(&buf, uri);
|
||||||
|
try std.testing.expectEqualStrings("localhost", actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "bufPrintHostnameFromFileUri succeeds with hostname as mac address" {
|
||||||
|
const uri = try std.Uri.parse("file://12:34:56:78:90:12");
|
||||||
|
|
||||||
|
var buf: [posix.HOST_NAME_MAX]u8 = undefined;
|
||||||
|
const actual = try bufPrintHostnameFromFileUri(&buf, uri);
|
||||||
|
try std.testing.expectEqualStrings("12:34:56:78:90:12", actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "bufPrintHostnameFromFileUri succeeds with hostname as a mac address and the last section is < 10" {
|
||||||
|
const uri = try std.Uri.parse("file://12:34:56:78:90:05");
|
||||||
|
|
||||||
|
var buf: [posix.HOST_NAME_MAX]u8 = undefined;
|
||||||
|
const actual = try bufPrintHostnameFromFileUri(&buf, uri);
|
||||||
|
try std.testing.expectEqualStrings("12:34:56:78:90:05", actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "bufPrintHostnameFromFileUri returns only hostname when there is a port component in the URI" {
|
||||||
|
// First: try with a non-2-digit port, to test general port handling.
|
||||||
|
const four_port_uri = try std.Uri.parse("file://has-a-port:1234");
|
||||||
|
|
||||||
|
var four_port_buf: [posix.HOST_NAME_MAX]u8 = undefined;
|
||||||
|
const four_port_actual = try bufPrintHostnameFromFileUri(&four_port_buf, four_port_uri);
|
||||||
|
try std.testing.expectEqualStrings("has-a-port", four_port_actual);
|
||||||
|
|
||||||
|
// Second: try with a 2-digit port to test mac-address handling.
|
||||||
|
const two_port_uri = try std.Uri.parse("file://has-a-port:12");
|
||||||
|
|
||||||
|
var two_port_buf: [posix.HOST_NAME_MAX]u8 = undefined;
|
||||||
|
const two_port_actual = try bufPrintHostnameFromFileUri(&two_port_buf, two_port_uri);
|
||||||
|
try std.testing.expectEqualStrings("has-a-port", two_port_actual);
|
||||||
|
|
||||||
|
// Third: try with a mac-address that has a port-component added to it to test mac-address handling.
|
||||||
|
const mac_with_port_uri = try std.Uri.parse("file://12:34:56:78:90:12:1234");
|
||||||
|
|
||||||
|
var mac_with_port_buf: [posix.HOST_NAME_MAX]u8 = undefined;
|
||||||
|
const mac_with_port_actual = try bufPrintHostnameFromFileUri(&mac_with_port_buf, mac_with_port_uri);
|
||||||
|
try std.testing.expectEqualStrings("12:34:56:78:90:12", mac_with_port_actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "bufPrintHostnameFromFileUri returns NoHostnameInUri error when hostname is missing from uri" {
|
||||||
|
const uri = try std.Uri.parse("file:///");
|
||||||
|
|
||||||
|
var buf: [posix.HOST_NAME_MAX]u8 = undefined;
|
||||||
|
const actual = bufPrintHostnameFromFileUri(&buf, uri);
|
||||||
|
try std.testing.expectError(HostnameParsingError.NoHostnameInUri, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "bufPrintHostnameFromFileUri returns NoSpaceLeft error when provided buffer has insufficient size" {
|
||||||
|
const uri = try std.Uri.parse("file://12:34:56:78:90:12/");
|
||||||
|
|
||||||
|
var buf: [5]u8 = undefined;
|
||||||
|
const actual = bufPrintHostnameFromFileUri(&buf, uri);
|
||||||
|
try std.testing.expectError(HostnameParsingError.NoSpaceLeft, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "isLocalHostname returns true when provided hostname is localhost" {
|
||||||
|
try std.testing.expect(try isLocalHostname("localhost"));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "isLocalHostname returns true when hostname is local" {
|
||||||
|
var buf: [posix.HOST_NAME_MAX]u8 = undefined;
|
||||||
|
const localHostname = try posix.gethostname(&buf);
|
||||||
|
try std.testing.expect(try isLocalHostname(localHostname));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "isLocalHostname returns false when hostname is not local" {
|
||||||
|
try std.testing.expectEqual(
|
||||||
|
false,
|
||||||
|
try isLocalHostname("not-the-local-hostname"),
|
||||||
|
);
|
||||||
|
}
|
@ -17,6 +17,7 @@ const resourcesdir = @import("resourcesdir.zig");
|
|||||||
// Namespaces
|
// Namespaces
|
||||||
pub const args = @import("args.zig");
|
pub const args = @import("args.zig");
|
||||||
pub const cgroup = @import("cgroup.zig");
|
pub const cgroup = @import("cgroup.zig");
|
||||||
|
pub const hostname = @import("hostname.zig");
|
||||||
pub const passwd = @import("passwd.zig");
|
pub const passwd = @import("passwd.zig");
|
||||||
pub const xdg = @import("xdg.zig");
|
pub const xdg = @import("xdg.zig");
|
||||||
pub const windows = @import("windows.zig");
|
pub const windows = @import("windows.zig");
|
||||||
|
@ -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 },
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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.
|
||||||
|
@ -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
@ -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 = ¤t.data;
|
const cur_page = ¤t.data;
|
||||||
@ -846,7 +848,7 @@ fn cursorScrollAboveRotate(self: *Screen) !void {
|
|||||||
|
|
||||||
// Our current is our cursor page, we need to rotate down from
|
// Our 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 = ¤t.data;
|
const cur_page = ¤t.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
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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");
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ const xev = @import("xev");
|
|||||||
const apprt = @import("../apprt.zig");
|
const apprt = @import("../apprt.zig");
|
||||||
const build_config = @import("../build_config.zig");
|
const build_config = @import("../build_config.zig");
|
||||||
const configpkg = @import("../config.zig");
|
const configpkg = @import("../config.zig");
|
||||||
|
const internal_os = @import("../os/main.zig");
|
||||||
const renderer = @import("../renderer.zig");
|
const renderer = @import("../renderer.zig");
|
||||||
const termio = @import("../termio.zig");
|
const termio = @import("../termio.zig");
|
||||||
const terminal = @import("../terminal/main.zig");
|
const terminal = @import("../terminal/main.zig");
|
||||||
@ -1048,31 +1049,38 @@ pub const StreamHandler = struct {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RFC 793 defines port numbers as 16-bit numbers. 5 digits is sufficient to represent
|
||||||
|
// the maximum since 2^16 - 1 = 65_535.
|
||||||
|
// See https://www.rfc-editor.org/rfc/rfc793#section-3.1.
|
||||||
|
const PORT_NUMBER_MAX_DIGITS = 5;
|
||||||
|
// Make sure there is space for a max length hostname + the max number of digits.
|
||||||
|
var host_and_port_buf: [posix.HOST_NAME_MAX + PORT_NUMBER_MAX_DIGITS]u8 = undefined;
|
||||||
|
const hostname_from_uri = internal_os.hostname.bufPrintHostnameFromFileUri(
|
||||||
|
&host_and_port_buf,
|
||||||
|
uri,
|
||||||
|
) catch |err| switch (err) {
|
||||||
|
error.NoHostnameInUri => {
|
||||||
|
log.warn("OSC 7 uri must contain a hostname: {}", .{err});
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
error.NoSpaceLeft => |e| {
|
||||||
|
log.warn("failed to get full hostname for OSC 7 validation: {}", .{e});
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// OSC 7 is a little sketchy because anyone can send any value from
|
// OSC 7 is a little sketchy because anyone can send any value from
|
||||||
// any host (such an SSH session). The best practice terminals follow
|
// any host (such an SSH session). The best practice terminals follow
|
||||||
// is to valid the hostname to be local.
|
// is to valid the hostname to be local.
|
||||||
const host_valid = host_valid: {
|
const host_valid = internal_os.hostname.isLocalHostname(
|
||||||
const host_component = uri.host orelse break :host_valid false;
|
hostname_from_uri,
|
||||||
|
) catch |err| switch (err) {
|
||||||
// Get the raw string of the URI. Its unclear to me if the various
|
error.PermissionDenied,
|
||||||
// tags of this enum guarantee no percent-encoding so we just
|
error.Unexpected,
|
||||||
// check all of it. This isn't a performance critical path.
|
=> {
|
||||||
const host = switch (host_component) {
|
|
||||||
.raw => |v| v,
|
|
||||||
.percent_encoded => |v| v,
|
|
||||||
};
|
|
||||||
if (host.len == 0 or std.mem.eql(u8, "localhost", host)) {
|
|
||||||
break :host_valid true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, it must match our hostname.
|
|
||||||
var buf: [posix.HOST_NAME_MAX]u8 = undefined;
|
|
||||||
const hostname = posix.gethostname(&buf) catch |err| {
|
|
||||||
log.warn("failed to get hostname for OSC 7 validation: {}", .{err});
|
log.warn("failed to get hostname for OSC 7 validation: {}", .{err});
|
||||||
break :host_valid false;
|
return;
|
||||||
};
|
},
|
||||||
|
|
||||||
break :host_valid std.mem.eql(u8, host, hostname);
|
|
||||||
};
|
};
|
||||||
if (!host_valid) {
|
if (!host_valid) {
|
||||||
log.warn("OSC 7 host must be local", .{});
|
log.warn("OSC 7 host must be local", .{});
|
||||||
|
Reference in New Issue
Block a user