diff --git a/.github/workflows/build-core.yml b/.github/workflows/build-core.yml index 6464254a2..3c1173e05 100644 --- a/.github/workflows/build-core.yml +++ b/.github/workflows/build-core.yml @@ -8,7 +8,7 @@ on: jobs: build: name: Core - Build and Test - runs-on: ubuntu-latest + runs-on: macos-latest steps: - name: Checkout code uses: actions/checkout@v4 @@ -21,7 +21,7 @@ jobs: - name: Setup Zig uses: goto-bus-stop/setup-zig@v2 with: - version: 0.14.1 + version: 0.15.2 - name: Install dependencies run: bun install @@ -34,4 +34,4 @@ jobs: - name: Run tests run: | cd packages/core - bun run test + bun run test:js diff --git a/.github/workflows/build-native.yml b/.github/workflows/build-native.yml index 0c3327524..40ae24475 100644 --- a/.github/workflows/build-native.yml +++ b/.github/workflows/build-native.yml @@ -14,7 +14,7 @@ on: default: false env: - ZIG_VERSION: 0.14.1 + ZIG_VERSION: 0.15.2 jobs: build-native: @@ -39,7 +39,10 @@ jobs: run: bun install - name: Build packages (cross-compile for all platforms) - run: bun run build + run: | + cd packages/core + bun run build:native --all + bun run build:lib - name: Verify build outputs run: | diff --git a/.github/workflows/build-react.yml b/.github/workflows/build-react.yml index 68fdd4cfd..a83e6b5d9 100644 --- a/.github/workflows/build-react.yml +++ b/.github/workflows/build-react.yml @@ -8,7 +8,7 @@ on: jobs: build: name: React - Build and Test - runs-on: ubuntu-latest + runs-on: macos-latest steps: - name: Checkout code uses: actions/checkout@v4 @@ -21,7 +21,7 @@ jobs: - name: Setup Zig uses: goto-bus-stop/setup-zig@v2 with: - version: 0.14.1 + version: 0.15.2 - name: Install dependencies run: bun install diff --git a/.github/workflows/build-solid.yml b/.github/workflows/build-solid.yml index a606f2264..2e3be1dbf 100644 --- a/.github/workflows/build-solid.yml +++ b/.github/workflows/build-solid.yml @@ -8,7 +8,7 @@ on: jobs: build: name: Solid - Build and Test - runs-on: ubuntu-latest + runs-on: macos-latest steps: - name: Checkout code uses: actions/checkout@v4 @@ -21,7 +21,7 @@ jobs: - name: Setup Zig uses: goto-bus-stop/setup-zig@v2 with: - version: 0.14.1 + version: 0.15.2 - name: Install dependencies run: bun install diff --git a/.zig-version b/.zig-version index 930e3000b..4312e0d0c 100644 --- a/.zig-version +++ b/.zig-version @@ -1 +1 @@ -0.14.1 +0.15.2 diff --git a/packages/core/scripts/build.ts b/packages/core/scripts/build.ts index 624cd5099..45c8b98da 100644 --- a/packages/core/scripts/build.ts +++ b/packages/core/scripts/build.ts @@ -40,6 +40,7 @@ const args = process.argv.slice(2) const buildLib = args.find((arg) => arg === "--lib") const buildNative = args.find((arg) => arg === "--native") const isDev = args.includes("--dev") +const buildAll = args.includes("--all") // Build for all platforms (requires macOS or cross-compilation setup) const variants: Variant[] = [ { platform: "darwin", arch: "x64" }, @@ -78,16 +79,17 @@ if (missingRequired.length > 0) { } if (buildNative) { - console.log(`Building native ${isDev ? "dev" : "prod"} binaries...`) + console.log(`Building native ${isDev ? "dev" : "prod"} binaries${buildAll ? " for all platforms" : ""}...`) - const zigBuild: SpawnSyncReturns = spawnSync( - "zig", - ["build", `-Doptimize=${isDev ? "Debug" : "ReleaseFast"}`], - { - cwd: join(rootDir, "src", "zig"), - stdio: "inherit", - }, - ) + const zigArgs = ["build", `-Doptimize=${isDev ? "Debug" : "ReleaseFast"}`] + if (buildAll) { + zigArgs.push("-Dall") + } + + const zigBuild: SpawnSyncReturns = spawnSync("zig", zigArgs, { + cwd: join(rootDir, "src", "zig"), + stdio: "inherit", + }) if (zigBuild.error) { console.error("Error: Zig is not installed or not in PATH") @@ -124,16 +126,10 @@ if (buildNative) { } if (copiedFiles === 0) { - console.error(`Error: No dynamic libraries found for ${platform}-${arch} in ${libDir}`) - console.error(`Expected to find files like: libopentui.so, libopentui.dylib, opentui.dll`) - console.error(`Found files in ${libDir}:`) - if (existsSync(libDir)) { - const files = spawnSync("ls", ["-la", libDir], { stdio: "pipe" }) - if (files.stdout) console.error(files.stdout.toString()) - } else { - console.error("Directory does not exist") - } - process.exit(1) + // Skip platforms that weren't built (e.g., macOS when cross-compiling from Linux) + console.log(`Skipping ${platform}-${arch}: no libraries found (cross-compilation may not be supported)`) + rmSync(nativeDir, { recursive: true, force: true }) + continue } const indexTsContent = `const module = await import("./${libraryFileName}", { with: { type: "file" } }) diff --git a/packages/core/src/zig/ansi.zig b/packages/core/src/zig/ansi.zig index 12a6edeec..bef215abd 100644 --- a/packages/core/src/zig/ansi.zig +++ b/packages/core/src/zig/ansi.zig @@ -21,15 +21,15 @@ pub const ANSI = struct { // Direct writing to any writer - the most efficient option pub fn moveToOutput(writer: anytype, x: u32, y: u32) AnsiError!void { - std.fmt.format(writer, "\x1b[{d};{d}H", .{ y, x }) catch return AnsiError.WriteFailed; + writer.print("\x1b[{d};{d}H", .{ y, x }) catch return AnsiError.WriteFailed; } pub fn fgColorOutput(writer: anytype, r: u8, g: u8, b: u8) AnsiError!void { - std.fmt.format(writer, "\x1b[38;2;{d};{d};{d}m", .{ r, g, b }) catch return AnsiError.WriteFailed; + writer.print("\x1b[38;2;{d};{d};{d}m", .{ r, g, b }) catch return AnsiError.WriteFailed; } pub fn bgColorOutput(writer: anytype, r: u8, g: u8, b: u8) AnsiError!void { - std.fmt.format(writer, "\x1b[48;2;{d};{d};{d}m", .{ r, g, b }) catch return AnsiError.WriteFailed; + writer.print("\x1b[48;2;{d};{d};{d}m", .{ r, g, b }) catch return AnsiError.WriteFailed; } // Text attribute constants @@ -51,11 +51,11 @@ pub const ANSI = struct { pub const cursorUnderlineBlink = "\x1b[3 q"; pub fn cursorColorOutputWriter(writer: anytype, r: u8, g: u8, b: u8) AnsiError!void { - std.fmt.format(writer, "\x1b]12;#{x:0>2}{x:0>2}{x:0>2}\x07", .{ r, g, b }) catch return AnsiError.WriteFailed; + writer.print("\x1b]12;#{x:0>2}{x:0>2}{x:0>2}\x07", .{ r, g, b }) catch return AnsiError.WriteFailed; } pub fn explicitWidthOutput(writer: anytype, width: u32, text: []const u8) AnsiError!void { - std.fmt.format(writer, "\x1b]66;w={d};{s}\x1b\\", .{ width, text }) catch return AnsiError.WriteFailed; + writer.print("\x1b]66;w={d};{s}\x1b\\", .{ width, text }) catch return AnsiError.WriteFailed; } pub const resetCursorColor = "\x1b]112\x07"; @@ -131,12 +131,15 @@ pub const ANSI = struct { pub const setTerminalTitle = "\x1b]0;{s}\x07"; pub fn setTerminalTitleOutput(writer: anytype, title: []const u8) AnsiError!void { - std.fmt.format(writer, setTerminalTitle, .{title}) catch return AnsiError.WriteFailed; + writer.print(setTerminalTitle, .{title}) catch return AnsiError.WriteFailed; } pub fn makeRoomForRendererOutput(writer: anytype, height: u32) AnsiError!void { if (height > 1) { - writer.writeByteNTimes('\n', height - 1) catch return AnsiError.WriteFailed; + var i: u32 = 0; + while (i < height - 1) : (i += 1) { + writer.writeByte('\n') catch return AnsiError.WriteFailed; + } } } }; diff --git a/packages/core/src/zig/buffer.zig b/packages/core/src/zig/buffer.zig index b67d9bda5..3265c333e 100644 --- a/packages/core/src/zig/buffer.zig +++ b/packages/core/src/zig/buffer.zig @@ -138,8 +138,8 @@ pub const OptimizedBuffer = struct { link_tracker: link.LinkTracker, width_method: utf8.WidthMethod, id: []const u8, - scissor_stack: std.ArrayList(ClipRect), - opacity_stack: std.ArrayList(f32), + scissor_stack: std.ArrayListUnmanaged(ClipRect), + opacity_stack: std.ArrayListUnmanaged(f32), const InitOptions = struct { respectAlpha: bool = false, @@ -163,11 +163,11 @@ pub const OptimizedBuffer = struct { const owned_id = allocator.dupe(u8, options.id) catch return BufferError.OutOfMemory; errdefer allocator.free(owned_id); - var scissor_stack = std.ArrayList(ClipRect).init(allocator); - errdefer scissor_stack.deinit(); + var scissor_stack: std.ArrayListUnmanaged(ClipRect) = .{}; + errdefer scissor_stack.deinit(allocator); - var opacity_stack = std.ArrayList(f32).init(allocator); - errdefer opacity_stack.deinit(); + var opacity_stack: std.ArrayListUnmanaged(f32) = .{}; + errdefer opacity_stack.deinit(allocator); const lp = options.link_pool orelse link.initGlobalLinkPool(allocator); @@ -217,8 +217,8 @@ pub const OptimizedBuffer = struct { } pub fn deinit(self: *OptimizedBuffer) void { - self.opacity_stack.deinit(); - self.scissor_stack.deinit(); + self.opacity_stack.deinit(self.allocator); + self.scissor_stack.deinit(self.allocator); self.link_tracker.deinit(); self.grapheme_tracker.deinit(); self.allocator.free(self.buffer.char); @@ -301,7 +301,7 @@ pub const OptimizedBuffer = struct { } } - try self.scissor_stack.append(rect); + try self.scissor_stack.append(self.allocator, rect); } pub fn popScissorRect(self: *OptimizedBuffer) void { @@ -324,7 +324,7 @@ pub const OptimizedBuffer = struct { pub fn pushOpacity(self: *OptimizedBuffer, opacity: f32) !void { const current = self.getCurrentOpacity(); const effective = current * std.math.clamp(opacity, 0.0, 1.0); - try self.opacity_stack.append(effective); + try self.opacity_stack.append(self.allocator, effective); } /// Pop an opacity value from the stack @@ -841,11 +841,11 @@ pub const OptimizedBuffer = struct { const is_ascii_only = utf8.isAsciiOnly(text); - var grapheme_list = std.ArrayList(utf8.GraphemeInfo).init(self.allocator); - defer grapheme_list.deinit(); + var grapheme_list: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer grapheme_list.deinit(self.allocator); const tab_width: u8 = 2; - try utf8.findGraphemeInfo(text, tab_width, is_ascii_only, self.width_method, &grapheme_list); + try utf8.findGraphemeInfo(text, tab_width, is_ascii_only, self.width_method, self.allocator, &grapheme_list); const specials = grapheme_list.items; var advance_cells: u32 = 0; diff --git a/packages/core/src/zig/build.zig b/packages/core/src/zig/build.zig index 68ad4b839..054db3b1c 100644 --- a/packages/core/src/zig/build.zig +++ b/packages/core/src/zig/build.zig @@ -8,11 +8,28 @@ const SupportedZigVersion = struct { }; const SUPPORTED_ZIG_VERSIONS = [_]SupportedZigVersion{ - .{ .major = 0, .minor = 14, .patch = 0 }, - .{ .major = 0, .minor = 14, .patch = 1 }, - // .{ .major = 0, .minor = 15, .patch = 0 }, + .{ .major = 0, .minor = 15, .patch = 2 }, }; +const SupportedTarget = struct { + zig_target: []const u8, + output_name: []const u8, + description: []const u8, +}; + +// Note: Linux targets use -gnu suffix to avoid PIC errors with ghostty's C++ deps (simdutf, highway) +const SUPPORTED_TARGETS = [_]SupportedTarget{ + .{ .zig_target = "x86_64-linux-gnu", .output_name = "x86_64-linux", .description = "Linux x86_64" }, + .{ .zig_target = "aarch64-linux-gnu", .output_name = "aarch64-linux", .description = "Linux aarch64" }, + .{ .zig_target = "x86_64-macos", .output_name = "x86_64-macos", .description = "macOS x86_64 (Intel)" }, + .{ .zig_target = "aarch64-macos", .output_name = "aarch64-macos", .description = "macOS aarch64 (Apple Silicon)" }, + .{ .zig_target = "x86_64-windows-gnu", .output_name = "x86_64-windows", .description = "Windows x86_64" }, + .{ .zig_target = "aarch64-windows-gnu", .output_name = "aarch64-windows", .description = "Windows aarch64" }, +}; + +const LIB_NAME = "opentui"; +const ROOT_SOURCE_FILE = "lib.zig"; + /// Apply dependencies to a module fn applyDependencies(b: *std.Build, module: *std.Build.Module, optimize: std.builtin.OptimizeMode, target: std.Build.ResolvedTarget) void { // Add uucode for grapheme break detection @@ -25,25 +42,12 @@ fn applyDependencies(b: *std.Build, module: *std.Build.Module, optimize: std.bui })) |uucode_dep| { module.addImport("uucode", uucode_dep.module("uucode")); } -} - -const SupportedTarget = struct { - cpu_arch: std.Target.Cpu.Arch, - os_tag: std.Target.Os.Tag, - description: []const u8, -}; -const SUPPORTED_TARGETS = [_]SupportedTarget{ - .{ .cpu_arch = .x86_64, .os_tag = .linux, .description = "Linux x86_64" }, - .{ .cpu_arch = .x86_64, .os_tag = .macos, .description = "macOS x86_64 (Intel)" }, - .{ .cpu_arch = .aarch64, .os_tag = .macos, .description = "macOS aarch64 (Apple Silicon)" }, - .{ .cpu_arch = .x86_64, .os_tag = .windows, .description = "Windows x86_64" }, - .{ .cpu_arch = .aarch64, .os_tag = .windows, .description = "Windows aarch64" }, - .{ .cpu_arch = .aarch64, .os_tag = .linux, .description = "Linux aarch64" }, -}; - -const LIB_NAME = "opentui"; -const ROOT_SOURCE_FILE = "lib.zig"; + // Add ghostty for terminal emulation + if (b.lazyDependency("ghostty", .{ .target = target, .optimize = optimize })) |ghostty_dep| { + module.addImport("ghostty-vt", ghostty_dep.module("ghostty-vt")); + } +} fn checkZigVersion() void { const current_version = builtin.zig_version; @@ -81,145 +85,150 @@ fn checkZigVersion() void { pub fn build(b: *std.Build) void { checkZigVersion(); - const optimize = b.option(std.builtin.OptimizeMode, "optimize", "Optimization level (Debug, ReleaseFast, ReleaseSafe, ReleaseSmall)") orelse .Debug; - const target_option = b.option([]const u8, "target", "Build for specific target (e.g., 'x86_64-linux'). If not specified, builds for all supported targets."); + const optimize = b.standardOptimizeOption(.{}); + const target_option = b.option([]const u8, "target", "Build for specific target (e.g., 'x86_64-linux-gnu')."); + const build_all = b.option(bool, "all", "Build for all supported targets") orelse false; if (target_option) |target_str| { + // Build single target buildSingleTarget(b, target_str, optimize) catch |err| { std.debug.print("Error building target '{s}': {}\n", .{ target_str, err }); std.process.exit(1); }; - } else { + } else if (build_all) { + // Build all supported targets buildAllTargets(b, optimize); + } else { + // Build for native target only (default) + buildNativeTarget(b, optimize); } - // Add test step - const test_step = b.step("test", "Run all tests"); - const test_target_query = std.Target.Query{ - .cpu_arch = builtin.cpu.arch, - .os_tag = builtin.os.tag, - }; - const test_target = b.resolveTargetQuery(test_target_query); - - // Run tests using the test index file - const test_exe = b.addTest(.{ + // Test step (native only) + const test_step = b.step("test", "Run unit tests"); + const native_target = b.resolveTargetQuery(.{}); + const test_mod = b.createModule(.{ .root_source_file = b.path("test.zig"), - .target = test_target, - .filter = b.option([]const u8, "test-filter", "Skip tests that do not match filter"), + .target = native_target, + .optimize = .Debug, }); - - applyDependencies(b, test_exe.root_module, .Debug, test_target); - - const run_test = b.addRunArtifact(test_exe); + applyDependencies(b, test_mod, .Debug, native_target); + const run_test = b.addRunArtifact(b.addTest(.{ + .root_module = test_mod, + .filters = if (b.option([]const u8, "test-filter", "Skip tests that do not match filter")) |f| &.{f} else &.{}, + })); test_step.dependOn(&run_test.step); - // Add bench step + // Bench step (native only) const bench_step = b.step("bench", "Run benchmarks"); - const bench_target_query = std.Target.Query{ - .cpu_arch = builtin.cpu.arch, - .os_tag = builtin.os.tag, - }; - const bench_target = b.resolveTargetQuery(bench_target_query); - - const bench_exe = b.addExecutable(.{ - .name = "opentui-bench", + const bench_mod = b.createModule(.{ .root_source_file = b.path("bench.zig"), - .target = bench_target, + .target = native_target, .optimize = optimize, }); - - applyDependencies(b, bench_exe.root_module, optimize, bench_target); - + applyDependencies(b, bench_mod, optimize, native_target); + const bench_exe = b.addExecutable(.{ + .name = "opentui-bench", + .root_module = bench_mod, + }); const run_bench = b.addRunArtifact(bench_exe); if (b.args) |args| { run_bench.addArgs(args); } bench_step.dependOn(&run_bench.step); - // Add debug step for standalone debugging + // Debug step (native only) const debug_step = b.step("debug", "Run debug executable"); - const debug_exe = b.addExecutable(.{ - .name = "opentui-debug", + const debug_mod = b.createModule(.{ .root_source_file = b.path("debug-view.zig"), - .target = test_target, + .target = native_target, .optimize = .Debug, }); - - applyDependencies(b, debug_exe.root_module, .Debug, test_target); - + applyDependencies(b, debug_mod, .Debug, native_target); + const debug_exe = b.addExecutable(.{ + .name = "opentui-debug", + .root_module = debug_mod, + }); const run_debug = b.addRunArtifact(debug_exe); debug_step.dependOn(&run_debug.step); } fn buildAllTargets(b: *std.Build, optimize: std.builtin.OptimizeMode) void { for (SUPPORTED_TARGETS) |supported_target| { - const target_query = std.Target.Query{ - .cpu_arch = supported_target.cpu_arch, - .os_tag = supported_target.os_tag, - }; - - buildTargetFromQuery(b, target_query, supported_target.description, optimize) catch |err| { + buildTarget(b, supported_target.zig_target, supported_target.output_name, supported_target.description, optimize) catch |err| { std.debug.print("Failed to build target {s}: {}\n", .{ supported_target.description, err }); continue; }; } } +fn buildNativeTarget(b: *std.Build, optimize: std.builtin.OptimizeMode) void { + // Find the matching supported target for the native platform + const native_arch = @tagName(builtin.cpu.arch); + const native_os = @tagName(builtin.os.tag); + + for (SUPPORTED_TARGETS) |supported_target| { + // Check if this target matches the native platform + if (std.mem.indexOf(u8, supported_target.zig_target, native_arch) != null and + std.mem.indexOf(u8, supported_target.zig_target, native_os) != null) + { + buildTarget(b, supported_target.zig_target, supported_target.output_name, supported_target.description, optimize) catch |err| { + std.debug.print("Failed to build native target {s}: {}\n", .{ supported_target.description, err }); + }; + return; + } + } + + std.debug.print("No matching supported target for native platform ({s}-{s})\n", .{ native_arch, native_os }); +} + fn buildSingleTarget(b: *std.Build, target_str: []const u8, optimize: std.builtin.OptimizeMode) !void { - const target_query = try std.Target.Query.parse(.{ .arch_os_abi = target_str }); + // Check if it matches a known target, use its output_name + for (SUPPORTED_TARGETS) |supported_target| { + if (std.mem.eql(u8, target_str, supported_target.zig_target)) { + try buildTarget(b, supported_target.zig_target, supported_target.output_name, supported_target.description, optimize); + return; + } + } + // Custom target - use target string as output name const description = try std.fmt.allocPrint(b.allocator, "Custom target: {s}", .{target_str}); - try buildTargetFromQuery(b, target_query, description, optimize); + try buildTarget(b, target_str, target_str, description, optimize); } -fn buildTargetFromQuery( +fn buildTarget( b: *std.Build, - target_query: std.Target.Query, + zig_target: []const u8, + output_name: []const u8, description: []const u8, optimize: std.builtin.OptimizeMode, ) !void { + const target_query = try std.Target.Query.parse(.{ .arch_os_abi = zig_target }); const target = b.resolveTargetQuery(target_query); - var target_output: *std.Build.Step.Compile = undefined; - const module = b.addModule(LIB_NAME, .{ + const module = b.createModule(.{ .root_source_file = b.path(ROOT_SOURCE_FILE), .target = target, .optimize = optimize, - .link_libc = false, }); applyDependencies(b, module, optimize, target); - target_output = b.addLibrary(.{ + const lib = b.addLibrary(.{ .name = LIB_NAME, .root_module = module, .linkage = .dynamic, }); - const target_name = try createTargetName(b.allocator, target.result); - defer b.allocator.free(target_name); - - const install_dir = b.addInstallArtifact(target_output, .{ + const install_dir = b.addInstallArtifact(lib, .{ .dest_dir = .{ .override = .{ - .custom = try std.fmt.allocPrint(b.allocator, "../lib/{s}", .{target_name}), + .custom = try std.fmt.allocPrint(b.allocator, "../lib/{s}", .{output_name}), }, }, }); - const build_step_name = try std.fmt.allocPrint(b.allocator, "build-{s}", .{target_name}); + const build_step_name = try std.fmt.allocPrint(b.allocator, "build-{s}", .{output_name}); const build_step = b.step(build_step_name, try std.fmt.allocPrint(b.allocator, "Build for {s}", .{description})); build_step.dependOn(&install_dir.step); b.getInstallStep().dependOn(&install_dir.step); } - -fn createTargetName(allocator: std.mem.Allocator, target: std.Target) ![]u8 { - return std.fmt.allocPrint( - allocator, - "{s}-{s}", - .{ - @tagName(target.cpu.arch), - @tagName(target.os.tag), - }, - ); -} diff --git a/packages/core/src/zig/build.zig.zon b/packages/core/src/zig/build.zig.zon index 0f8b4bc91..061e6ae3d 100644 --- a/packages/core/src/zig/build.zig.zon +++ b/packages/core/src/zig/build.zig.zon @@ -2,11 +2,15 @@ .name = .opentui, .version = "0.1.11", .fingerprint = 0x5445027d063f5083, - .minimum_zig_version = "0.14.1", + .minimum_zig_version = "0.15.2", .dependencies = .{ .uucode = .{ - .url = "https://github.com/jacobsandlund/uucode/archive/refs/tags/v0.1.0-zig-0.14.tar.gz", - .hash = "uucode-0.1.0-ZZjBPpAFQABNCvd9cVPBg4I7233Ays-NWfWphPNqGbyE", + .url = "https://github.com/jacobsandlund/uucode/archive/refs/tags/v0.1.0.tar.gz", + .hash = "uucode-0.1.0-ZZjBPvoGQACkgWDKIrtI8CQcSXIufU3Kvty-pIfh02i2", + }, + .ghostty = .{ + .url = "git+https://github.com/ghostty-org/ghostty.git#fbed63b0474ce0fff66859c1563a0359589ef179", + .hash = "ghostty-1.3.0-dev-5UdBC_dRRAS-mGtQa1JGeQPgcubWBeell0hGr_47fW75", }, }, .paths = .{ diff --git a/packages/core/src/zig/edit-buffer.zig b/packages/core/src/zig/edit-buffer.zig index 276ad0699..e016cfb35 100644 --- a/packages/core/src/zig/edit-buffer.zig +++ b/packages/core/src/zig/edit-buffer.zig @@ -270,7 +270,7 @@ pub const EditBuffer = struct { const base_start = chunk_ref.start; var result = try self.tb.textToSegments(self.allocator, bytes, base_mem_id, base_start, false); - defer result.segments.deinit(); + defer result.segments.deinit(result.allocator); const inserted_width = result.total_width; diff --git a/packages/core/src/zig/event-bus.zig b/packages/core/src/zig/event-bus.zig index 873c8dcc4..f1effbd34 100644 --- a/packages/core/src/zig/event-bus.zig +++ b/packages/core/src/zig/event-bus.zig @@ -1,8 +1,8 @@ const std = @import("std"); -var global_event_callback: ?*const fn (namePtr: [*]const u8, nameLen: usize, dataPtr: [*]const u8, dataLen: usize) callconv(.C) void = null; +var global_event_callback: ?*const fn (namePtr: [*]const u8, nameLen: usize, dataPtr: [*]const u8, dataLen: usize) callconv(.c) void = null; -pub fn setEventCallback(callback: ?*const fn (namePtr: [*]const u8, nameLen: usize, dataPtr: [*]const u8, dataLen: usize) callconv(.C) void) void { +pub fn setEventCallback(callback: ?*const fn (namePtr: [*]const u8, nameLen: usize, dataPtr: [*]const u8, dataLen: usize) callconv(.c) void) void { global_event_callback = callback; } diff --git a/packages/core/src/zig/lib.zig b/packages/core/src/zig/lib.zig index 8e25d2265..75f1dbd1b 100644 --- a/packages/core/src/zig/lib.zig +++ b/packages/core/src/zig/lib.zig @@ -16,17 +16,18 @@ const utf8 = @import("utf8.zig"); const logger = @import("logger.zig"); const event_bus = @import("event-bus.zig"); const utils = @import("utils.zig"); +const ghostty = @import("ghostty-vt"); pub const OptimizedBuffer = buffer.OptimizedBuffer; pub const CliRenderer = renderer.CliRenderer; pub const Terminal = terminal.Terminal; pub const RGBA = buffer.RGBA; -export fn setLogCallback(callback: ?*const fn (level: u8, msgPtr: [*]const u8, msgLen: usize) callconv(.C) void) void { +export fn setLogCallback(callback: ?*const fn (level: u8, msgPtr: [*]const u8, msgLen: usize) callconv(.c) void) void { logger.setLogCallback(callback); } -export fn setEventCallback(callback: ?*const fn (namePtr: [*]const u8, nameLen: usize, dataPtr: [*]const u8, dataLen: usize) callconv(.C) void) void { +export fn setEventCallback(callback: ?*const fn (namePtr: [*]const u8, nameLen: usize, dataPtr: [*]const u8, dataLen: usize) callconv(.c) void) void { event_bus.setEventCallback(callback); } @@ -218,9 +219,9 @@ export fn clearTerminal(rendererPtr: *renderer.CliRenderer) void { export fn setTerminalTitle(rendererPtr: *renderer.CliRenderer, titlePtr: [*]const u8, titleLen: usize) void { const title = titlePtr[0..titleLen]; - var bufferedWriter = &rendererPtr.stdoutWriter; - const writer = bufferedWriter.writer(); - rendererPtr.terminal.setTerminalTitle(writer.any(), title); + var stdoutWriter = std.fs.File.stdout().writer(&rendererPtr.stdoutBuffer); + const writer = &stdoutWriter.interface; + rendererPtr.terminal.setTerminalTitle(writer, title); } // Buffer functions @@ -1372,11 +1373,11 @@ export fn encodeUnicode( const is_ascii_only = utf8.isAsciiOnly(text); // Find grapheme info - var grapheme_list = std.ArrayList(utf8.GraphemeInfo).init(std.heap.page_allocator); - defer grapheme_list.deinit(); + var grapheme_list: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer grapheme_list.deinit(std.heap.page_allocator); const tab_width: u8 = 2; - utf8.findGraphemeInfo(text, tab_width, is_ascii_only, wMethod, &grapheme_list) catch return false; + utf8.findGraphemeInfo(text, tab_width, is_ascii_only, wMethod, std.heap.page_allocator, &grapheme_list) catch return false; const specials = grapheme_list.items; // Allocate output array @@ -1510,3 +1511,8 @@ export fn bufferDrawChar( const rgbaBg = utils.f32PtrToRGBA(bg); bufferPtr.drawChar(char, x, y, rgbaFg, rgbaBg, attributes) catch {}; } + +// Temp: ensures ghostty-vt gets bundled into lib to test build works +export fn ghosttyGetTerminalSize() usize { + return @sizeOf(ghostty.Terminal); +} diff --git a/packages/core/src/zig/logger.zig b/packages/core/src/zig/logger.zig index faaf19795..b9b72c07e 100644 --- a/packages/core/src/zig/logger.zig +++ b/packages/core/src/zig/logger.zig @@ -7,9 +7,9 @@ pub const LogLevel = enum(u8) { debug = 3, }; -var global_log_callback: ?*const fn (level: u8, msgPtr: [*]const u8, msgLen: usize) callconv(.C) void = null; +var global_log_callback: ?*const fn (level: u8, msgPtr: [*]const u8, msgLen: usize) callconv(.c) void = null; -pub fn setLogCallback(callback: ?*const fn (level: u8, msgPtr: [*]const u8, msgLen: usize) callconv(.C) void) void { +pub fn setLogCallback(callback: ?*const fn (level: u8, msgPtr: [*]const u8, msgLen: usize) callconv(.c) void) void { global_log_callback = callback; } diff --git a/packages/core/src/zig/renderer.zig b/packages/core/src/zig/renderer.zig index 436835f19..cd76f7e43 100644 --- a/packages/core/src/zig/renderer.zig +++ b/packages/core/src/zig/renderer.zig @@ -69,18 +69,18 @@ pub const CliRenderer = struct { frameCallbackTime: ?f64, }, statSamples: struct { - lastFrameTime: std.ArrayList(f64), - renderTime: std.ArrayList(f64), - overallFrameTime: std.ArrayList(f64), - bufferResetTime: std.ArrayList(f64), - stdoutWriteTime: std.ArrayList(f64), - cellsUpdated: std.ArrayList(u32), - frameCallbackTime: std.ArrayList(f64), + lastFrameTime: std.ArrayListUnmanaged(f64), + renderTime: std.ArrayListUnmanaged(f64), + overallFrameTime: std.ArrayListUnmanaged(f64), + bufferResetTime: std.ArrayListUnmanaged(f64), + stdoutWriteTime: std.ArrayListUnmanaged(f64), + cellsUpdated: std.ArrayListUnmanaged(u32), + frameCallbackTime: std.ArrayListUnmanaged(f64), }, lastRenderTime: i64, allocator: Allocator, renderThread: ?std.Thread = null, - stdoutWriter: std.io.BufferedWriter(4096, std.fs.File.Writer), + stdoutBuffer: [4096]u8, debugOverlay: struct { enabled: bool, corner: DebugOverlayCorner, @@ -130,7 +130,9 @@ pub const CliRenderer = struct { return data.len; } - pub fn writer() std.io.Writer(void, error{BufferFull}, write) { + // TODO: std.io.GenericWriter is deprecated, however the "correct" option seems to be much more involved + // So I have simply used GenericWriter here, and then the proper migration can be done later + pub fn writer() std.io.GenericWriter(void, error{BufferFull}, write) { return .{ .context = {} }; } }; @@ -141,35 +143,22 @@ pub const CliRenderer = struct { const currentBuffer = try OptimizedBuffer.init(allocator, width, height, .{ .pool = pool, .width_method = .unicode, .id = "current buffer" }); const nextBuffer = try OptimizedBuffer.init(allocator, width, height, .{ .pool = pool, .width_method = .unicode, .id = "next buffer" }); - const stdoutWriter = if (testing) blk: { - // In testing mode, use /dev/null to discard output - const devnull = std.fs.openFileAbsolute("/dev/null", .{ .mode = .write_only }) catch { - // Fallback to stdout if /dev/null can't be opened - logger.warn("Failed to open /dev/null, falling back to stdout\n", .{}); - break :blk std.io.BufferedWriter(4096, std.fs.File.Writer){ .unbuffered_writer = std.io.getStdOut().writer() }; - }; - break :blk std.io.BufferedWriter(4096, std.fs.File.Writer){ .unbuffered_writer = devnull.writer() }; - } else blk: { - const stdout = std.io.getStdOut(); - break :blk std.io.BufferedWriter(4096, std.fs.File.Writer){ .unbuffered_writer = stdout.writer() }; - }; - // stat sample arrays - var lastFrameTime = std.ArrayList(f64).init(allocator); - var renderTime = std.ArrayList(f64).init(allocator); - var overallFrameTime = std.ArrayList(f64).init(allocator); - var bufferResetTime = std.ArrayList(f64).init(allocator); - var stdoutWriteTime = std.ArrayList(f64).init(allocator); - var cellsUpdated = std.ArrayList(u32).init(allocator); - var frameCallbackTimes = std.ArrayList(f64).init(allocator); - - try lastFrameTime.ensureTotalCapacity(STAT_SAMPLE_CAPACITY); - try renderTime.ensureTotalCapacity(STAT_SAMPLE_CAPACITY); - try overallFrameTime.ensureTotalCapacity(STAT_SAMPLE_CAPACITY); - try bufferResetTime.ensureTotalCapacity(STAT_SAMPLE_CAPACITY); - try stdoutWriteTime.ensureTotalCapacity(STAT_SAMPLE_CAPACITY); - try cellsUpdated.ensureTotalCapacity(STAT_SAMPLE_CAPACITY); - try frameCallbackTimes.ensureTotalCapacity(STAT_SAMPLE_CAPACITY); + var lastFrameTime: std.ArrayListUnmanaged(f64) = .{}; + var renderTime: std.ArrayListUnmanaged(f64) = .{}; + var overallFrameTime: std.ArrayListUnmanaged(f64) = .{}; + var bufferResetTime: std.ArrayListUnmanaged(f64) = .{}; + var stdoutWriteTime: std.ArrayListUnmanaged(f64) = .{}; + var cellsUpdated: std.ArrayListUnmanaged(u32) = .{}; + var frameCallbackTimes: std.ArrayListUnmanaged(f64) = .{}; + + try lastFrameTime.ensureTotalCapacity(allocator, STAT_SAMPLE_CAPACITY); + try renderTime.ensureTotalCapacity(allocator, STAT_SAMPLE_CAPACITY); + try overallFrameTime.ensureTotalCapacity(allocator, STAT_SAMPLE_CAPACITY); + try bufferResetTime.ensureTotalCapacity(allocator, STAT_SAMPLE_CAPACITY); + try stdoutWriteTime.ensureTotalCapacity(allocator, STAT_SAMPLE_CAPACITY); + try cellsUpdated.ensureTotalCapacity(allocator, STAT_SAMPLE_CAPACITY); + try frameCallbackTimes.ensureTotalCapacity(allocator, STAT_SAMPLE_CAPACITY); const hitGridSize = width * height; const currentHitGrid = try allocator.alloc(u32, hitGridSize); @@ -217,7 +206,7 @@ pub const CliRenderer = struct { }, .lastRenderTime = std.time.microTimestamp(), .allocator = allocator, - .stdoutWriter = stdoutWriter, + .stdoutBuffer = undefined, .currentHitGrid = currentHitGrid, .nextHitGrid = nextHitGrid, .hitGridWidth = width, @@ -251,13 +240,13 @@ pub const CliRenderer = struct { self.nextRenderBuffer.deinit(); // Free stat sample arrays - self.statSamples.lastFrameTime.deinit(); - self.statSamples.renderTime.deinit(); - self.statSamples.overallFrameTime.deinit(); - self.statSamples.bufferResetTime.deinit(); - self.statSamples.stdoutWriteTime.deinit(); - self.statSamples.cellsUpdated.deinit(); - self.statSamples.frameCallbackTime.deinit(); + self.statSamples.lastFrameTime.deinit(self.allocator); + self.statSamples.renderTime.deinit(self.allocator); + self.statSamples.overallFrameTime.deinit(self.allocator); + self.statSamples.bufferResetTime.deinit(self.allocator); + self.statSamples.stdoutWriteTime.deinit(self.allocator); + self.statSamples.cellsUpdated.deinit(self.allocator); + self.statSamples.frameCallbackTime.deinit(self.allocator); self.allocator.free(self.currentHitGrid); self.allocator.free(self.nextHitGrid); @@ -269,8 +258,8 @@ pub const CliRenderer = struct { self.useAlternateScreen = useAlternateScreen; self.terminalSetup = true; - var bufferedWriter = &self.stdoutWriter; - const writer = bufferedWriter.writer(); + var stdoutWriter = std.fs.File.stdout().writer(&self.stdoutBuffer); + const writer = &stdoutWriter.interface; self.terminal.queryTerminalSend(writer) catch { logger.warn("Failed to query terminal capabilities", .{}); @@ -280,8 +269,8 @@ pub const CliRenderer = struct { } fn setupTerminalWithoutDetection(self: *CliRenderer, useAlternateScreen: bool) void { - var bufferedWriter = &self.stdoutWriter; - const writer = bufferedWriter.writer(); + var stdoutWriter = std.fs.File.stdout().writer(&self.stdoutBuffer); + const writer = &stdoutWriter.interface; writer.writeAll(ansi.ANSI.saveCursorState) catch {}; @@ -295,7 +284,7 @@ pub const CliRenderer = struct { const useKitty = self.terminal.opts.kitty_keyboard_flags > 0; self.terminal.enableDetectedFeatures(writer, useKitty) catch {}; - bufferedWriter.flush() catch {}; + writer.flush() catch {}; } pub fn suspendRenderer(self: *CliRenderer) void { @@ -311,16 +300,17 @@ pub const CliRenderer = struct { pub fn performShutdownSequence(self: *CliRenderer) void { if (!self.terminalSetup) return; - const direct = self.stdoutWriter.writer(); + var stdoutWriter = std.fs.File.stdout().writer(&self.stdoutBuffer); + const direct = &stdoutWriter.interface; self.terminal.resetState(direct) catch { logger.warn("Failed to reset terminal state", .{}); }; if (self.useAlternateScreen) { - self.stdoutWriter.flush() catch {}; + direct.flush() catch {}; } else if (self.renderOffset == 0) { direct.writeAll("\x1b[H\x1b[J") catch {}; - self.stdoutWriter.flush() catch {}; + direct.flush() catch {}; } else if (self.renderOffset > 0) { // Currently still handled in typescript // const consoleEndLine = self.height - self.renderOffset; @@ -335,22 +325,22 @@ pub const CliRenderer = struct { direct.writeAll(ansi.ANSI.defaultCursorStyle) catch {}; // Workaround for Ghostty not showing the cursor after shutdown for some reason direct.writeAll(ansi.ANSI.showCursor) catch {}; - self.stdoutWriter.flush() catch {}; - std.time.sleep(10 * std.time.ns_per_ms); + direct.flush() catch {}; + std.Thread.sleep(10 * std.time.ns_per_ms); direct.writeAll(ansi.ANSI.showCursor) catch {}; - self.stdoutWriter.flush() catch {}; - std.time.sleep(10 * std.time.ns_per_ms); + direct.flush() catch {}; + std.Thread.sleep(10 * std.time.ns_per_ms); } - fn addStatSample(comptime T: type, samples: *std.ArrayList(T), value: T) void { - samples.append(value) catch return; + fn addStatSample(self: *CliRenderer, comptime T: type, samples: *std.ArrayListUnmanaged(T), value: T) void { + samples.append(self.allocator, value) catch return; if (samples.items.len > MAX_STAT_SAMPLES) { _ = samples.orderedRemove(0); } } - fn getStatAverage(comptime T: type, samples: *const std.ArrayList(T)) T { + fn getStatAverage(comptime T: type, samples: *const std.ArrayListUnmanaged(T)) T { if (samples.items.len == 0) { return 0; } @@ -406,8 +396,8 @@ pub const CliRenderer = struct { self.renderStats.fps = fps; self.renderStats.frameCallbackTime = frameCallbackTime; - addStatSample(f64, &self.statSamples.overallFrameTime, time); - addStatSample(f64, &self.statSamples.frameCallbackTime, frameCallbackTime); + self.addStatSample(f64, &self.statSamples.overallFrameTime, time); + self.addStatSample(f64, &self.statSamples.frameCallbackTime, frameCallbackTime); } pub fn updateMemoryStats(self: *CliRenderer, heapUsed: u32, heapTotal: u32, arrayBuffers: u32) void { @@ -474,10 +464,12 @@ pub const CliRenderer = struct { const outputLen = self.currentOutputLen; const writeStart = std.time.microTimestamp(); - if (outputLen > 0) { - var bufferedWriter = &self.stdoutWriter; - bufferedWriter.writer().writeAll(outputData[0..outputLen]) catch {}; - bufferedWriter.flush() catch {}; + // Skip stdout writes in testing mode to avoid blocking + if (outputLen > 0 and !self.testing) { + var stdoutWriter = std.fs.File.stdout().writer(&self.stdoutBuffer); + const w = &stdoutWriter.interface; + w.writeAll(outputData[0..outputLen]) catch {}; + w.flush() catch {}; } // Signal that rendering is complete @@ -521,26 +513,30 @@ pub const CliRenderer = struct { self.renderMutex.unlock(); } else { const writeStart = std.time.microTimestamp(); - var bufferedWriter = &self.stdoutWriter; - bufferedWriter.writer().writeAll(outputBuffer[0..outputBufferLen]) catch {}; - bufferedWriter.flush() catch {}; + // Skip stdout writes in testing mode to avoid blocking + if (!self.testing) { + var stdoutWriter = std.fs.File.stdout().writer(&self.stdoutBuffer); + const w = &stdoutWriter.interface; + w.writeAll(outputBuffer[0..outputBufferLen]) catch {}; + w.flush() catch {}; + } self.renderStats.stdoutWriteTime = @as(f64, @floatFromInt(std.time.microTimestamp() - writeStart)); } self.renderStats.lastFrameTime = deltaTime * 1000.0; self.renderStats.frameCount += 1; - addStatSample(f64, &self.statSamples.lastFrameTime, deltaTime * 1000.0); + self.addStatSample(f64, &self.statSamples.lastFrameTime, deltaTime * 1000.0); if (self.renderStats.renderTime) |rt| { - addStatSample(f64, &self.statSamples.renderTime, rt); + self.addStatSample(f64, &self.statSamples.renderTime, rt); } if (self.renderStats.bufferResetTime) |brt| { - addStatSample(f64, &self.statSamples.bufferResetTime, brt); + self.addStatSample(f64, &self.statSamples.bufferResetTime, brt); } if (self.renderStats.stdoutWriteTime) |swt| { - addStatSample(f64, &self.statSamples.stdoutWriteTime, swt); + self.addStatSample(f64, &self.statSamples.stdoutWriteTime, swt); } - addStatSample(u32, &self.statSamples.cellsUpdated, self.renderStats.cellsUpdated); + self.addStatSample(u32, &self.statSamples.cellsUpdated, self.renderStats.cellsUpdated); } pub fn getNextBuffer(self: *CliRenderer) *OptimizedBuffer { @@ -790,9 +786,10 @@ pub const CliRenderer = struct { } pub fn clearTerminal(self: *CliRenderer) void { - var bufferedWriter = &self.stdoutWriter; - bufferedWriter.writer().writeAll(ansi.ANSI.clearAndHome) catch {}; - bufferedWriter.flush() catch {}; + var stdoutWriter = std.fs.File.stdout().writer(&self.stdoutBuffer); + const w = &stdoutWriter.interface; + w.writeAll(ansi.ANSI.clearAndHome) catch {}; + w.flush() catch {}; } pub fn addToHitGrid(self: *CliRenderer, x: i32, y: i32, width: u32, height: u32, id: u32) void { @@ -834,7 +831,9 @@ pub const CliRenderer = struct { const file = std.fs.cwd().createFile(filename, .{}) catch return; defer file.close(); - const writer = file.writer(); + var fileBuffer: [4096]u8 = undefined; + var fileWriter = file.writer(&fileBuffer); + const writer = &fileWriter.interface; for (0..self.hitGridHeight) |y| { for (0..self.hitGridWidth) |x| { @@ -846,6 +845,7 @@ pub const CliRenderer = struct { } writer.writeByte('\n') catch return; } + writer.flush() catch {}; } fn dumpSingleBuffer(self: *CliRenderer, buffer: *OptimizedBuffer, buffer_name: []const u8, timestamp: i64) void { @@ -860,7 +860,9 @@ pub const CliRenderer = struct { const file = std.fs.cwd().createFile(filename, .{}) catch return; defer file.close(); - const writer = file.writer(); + var fileBuffer: [4096]u8 = undefined; + var fileWriter = file.writer(&fileBuffer); + const writer = &fileWriter.interface; writer.print("{s} Buffer ({d}x{d}):\n", .{ buffer_name, self.width, self.height }) catch return; writer.writeAll("Characters:\n") catch return; @@ -886,6 +888,7 @@ pub const CliRenderer = struct { } writer.writeByte('\n') catch return; } + writer.flush() catch {}; } pub fn getLastOutputForTest(_: *CliRenderer) []const u8 { @@ -909,7 +912,9 @@ pub const CliRenderer = struct { const file = std.fs.cwd().createFile(filename, .{}) catch return; defer file.close(); - const writer = file.writer(); + var fileBuffer: [4096]u8 = undefined; + var fileWriter = file.writer(&fileBuffer); + const writer = &fileWriter.interface; writer.print("Stdout Buffer Output (timestamp: {d}):\n", .{timestamp}) catch return; writer.writeAll("Last Rendered ANSI Output:\n") catch return; @@ -927,6 +932,7 @@ pub const CliRenderer = struct { writer.writeAll("\n================\n") catch return; writer.print("Buffer size: {d} bytes\n", .{lastLen}) catch return; writer.print("Active buffer: {s}\n", .{if (activeBuffer == .A) "A" else "B"}) catch return; + writer.flush() catch {}; } pub fn dumpBuffers(self: *CliRenderer, timestamp: i64) void { @@ -937,46 +943,46 @@ pub const CliRenderer = struct { pub fn enableMouse(self: *CliRenderer, enableMovement: bool) void { _ = enableMovement; // TODO: Use this to control motion tracking levels - var bufferedWriter = &self.stdoutWriter; - const writer = bufferedWriter.writer(); + var stdoutWriter = std.fs.File.stdout().writer(&self.stdoutBuffer); + const writer = &stdoutWriter.interface; self.terminal.setMouseMode(writer, true) catch {}; - bufferedWriter.flush() catch {}; + writer.flush() catch {}; } pub fn queryPixelResolution(self: *CliRenderer) void { - var bufferedWriter = &self.stdoutWriter; - const writer = bufferedWriter.writer(); + var stdoutWriter = std.fs.File.stdout().writer(&self.stdoutBuffer); + const writer = &stdoutWriter.interface; writer.writeAll(ansi.ANSI.queryPixelSize) catch {}; - bufferedWriter.flush() catch {}; + writer.flush() catch {}; } pub fn disableMouse(self: *CliRenderer) void { - var bufferedWriter = &self.stdoutWriter; - const writer = bufferedWriter.writer(); + var stdoutWriter = std.fs.File.stdout().writer(&self.stdoutBuffer); + const writer = &stdoutWriter.interface; self.terminal.setMouseMode(writer, false) catch {}; - bufferedWriter.flush() catch {}; + writer.flush() catch {}; } pub fn enableKittyKeyboard(self: *CliRenderer, flags: u8) void { - var bufferedWriter = &self.stdoutWriter; - const writer = bufferedWriter.writer(); + var stdoutWriter = std.fs.File.stdout().writer(&self.stdoutBuffer); + const writer = &stdoutWriter.interface; self.terminal.setKittyKeyboard(writer, true, flags) catch {}; - bufferedWriter.flush() catch {}; + writer.flush() catch {}; } pub fn disableKittyKeyboard(self: *CliRenderer) void { - var bufferedWriter = &self.stdoutWriter; - const writer = bufferedWriter.writer(); + var stdoutWriter = std.fs.File.stdout().writer(&self.stdoutBuffer); + const writer = &stdoutWriter.interface; self.terminal.setKittyKeyboard(writer, false, 0) catch {}; - bufferedWriter.flush() catch {}; + writer.flush() catch {}; } pub fn getTerminalCapabilities(self: *CliRenderer) Terminal.Capabilities { @@ -985,7 +991,8 @@ pub const CliRenderer = struct { pub fn processCapabilityResponse(self: *CliRenderer, response: []const u8) void { self.terminal.processCapabilityResponse(response); - const writer = self.stdoutWriter.writer(); + var stdoutWriter = std.fs.File.stdout().writer(&self.stdoutBuffer); + const writer = &stdoutWriter.interface; const useKitty = self.terminal.opts.kitty_keyboard_flags > 0; self.terminal.enableDetectedFeatures(writer, useKitty) catch {}; } diff --git a/packages/core/src/zig/rope.zig b/packages/core/src/zig/rope.zig index 5b2ed891b..8e3945087 100644 --- a/packages/core/src/zig/rope.zig +++ b/packages/core/src/zig/rope.zig @@ -38,13 +38,13 @@ pub fn Rope(comptime T: type) type { }; pub const MarkerCache = if (marker_enabled) struct { // Flat arrays of positions for each marker type - positions: std.AutoHashMap(std.meta.Tag(T), std.ArrayList(MarkerPosition)), + positions: std.AutoHashMap(std.meta.Tag(T), std.ArrayListUnmanaged(MarkerPosition)), version: u64, // Rope version when cache was built allocator: Allocator, pub fn init(allocator: Allocator) MarkerCache { return .{ - .positions = std.AutoHashMap(std.meta.Tag(T), std.ArrayList(MarkerPosition)).init(allocator), + .positions = std.AutoHashMap(std.meta.Tag(T), std.ArrayListUnmanaged(MarkerPosition)).init(allocator), .version = std.math.maxInt(u64), // Sentinel: cache is invalid until first rebuild .allocator = allocator, }; @@ -53,7 +53,7 @@ pub fn Rope(comptime T: type) type { pub fn deinit(self: *MarkerCache) void { var iter = self.positions.valueIterator(); while (iter.next()) |list| { - list.deinit(); + list.deinit(self.allocator); } self.positions.deinit(); } @@ -291,13 +291,13 @@ pub fn Rope(comptime T: type) type { }; } - fn collect(self: *const Node, list: *std.ArrayList(*const Node)) !void { + fn collect(self: *const Node, list: *std.ArrayListUnmanaged(*const Node), allocator: Allocator) !void { switch (self.*) { .branch => |*b| { - try b.left.collect(list); - try b.right.collect(list); + try b.left.collect(list, allocator); + try b.right.collect(list, allocator); }, - .leaf => try list.append(self), + .leaf => try list.append(allocator, self), } } @@ -318,11 +318,11 @@ pub fn Rope(comptime T: type) type { pub fn rebalance(self: *const Node, allocator: Allocator, tmp_allocator: Allocator) !*const Node { if (self.is_balanced()) return self; - var leaves = std.ArrayList(*const Node).init(tmp_allocator); - defer leaves.deinit(); + var leaves: std.ArrayListUnmanaged(*const Node) = .{}; + defer leaves.deinit(tmp_allocator); - try leaves.ensureTotalCapacity(self.count()); - try self.collect(&leaves); + try leaves.ensureTotalCapacity(tmp_allocator, self.count()); + try self.collect(&leaves, tmp_allocator); return try merge_leaves(leaves.items, allocator); } @@ -524,12 +524,13 @@ pub fn Rope(comptime T: type) type { return try initWithConfig(allocator, config); } - var leaves = try std.ArrayList(*const Node).initCapacity(allocator, items.len); - defer leaves.deinit(); + var leaves: std.ArrayListUnmanaged(*const Node) = .{}; + defer leaves.deinit(allocator); + try leaves.ensureTotalCapacity(allocator, items.len); for (items) |item| { const leaf = try Node.new_leaf(allocator, item); - try leaves.append(leaf); + try leaves.append(allocator, leaf); } const root = try Node.merge_leaves(leaves.items, allocator); @@ -669,7 +670,8 @@ pub fn Rope(comptime T: type) type { if (start >= end) return &[_]T{}; const SliceContext = struct { - items: std.ArrayList(T), + items: std.ArrayListUnmanaged(T), + allocator: Allocator, start: u32, end: u32, current_index: u32 = 0, @@ -678,7 +680,7 @@ pub fn Rope(comptime T: type) type { _ = idx; const context = @as(*@This(), @ptrCast(@alignCast(ctx))); if (context.current_index >= context.start and context.current_index < context.end) { - context.items.append(data.*) catch |e| return .{ .err = e }; + context.items.append(context.allocator, data.*) catch |e| return .{ .err = e }; } context.current_index += 1; if (context.current_index >= context.end) { @@ -689,14 +691,15 @@ pub fn Rope(comptime T: type) type { }; var context = SliceContext{ - .items = std.ArrayList(T).init(allocator), + .items = .{}, + .allocator = allocator, .start = start, .end = end, }; - errdefer context.items.deinit(); + errdefer context.items.deinit(allocator); try self.walk(&context, SliceContext.walker); - return context.items.toOwnedSlice(); + return context.items.toOwnedSlice(allocator); } pub fn delete_range(self: *Self, start: u32, end: u32) !void { @@ -727,47 +730,49 @@ pub fn Rope(comptime T: type) type { pub fn to_array(self: *const Self, allocator: Allocator) ![]T { const ToArrayContext = struct { - items: std.ArrayList(T), + items: std.ArrayListUnmanaged(T), + allocator: Allocator, fn walker(ctx: *anyopaque, data: *const T, idx: u32) Node.WalkerResult { _ = idx; const context = @as(*@This(), @ptrCast(@alignCast(ctx))); - context.items.append(data.*) catch |e| return .{ .err = e }; + context.items.append(context.allocator, data.*) catch |e| return .{ .err = e }; return .{}; } }; var context = ToArrayContext{ - .items = std.ArrayList(T).init(allocator), + .items = .{}, + .allocator = allocator, }; - errdefer context.items.deinit(); + errdefer context.items.deinit(allocator); try self.walk(&context, ToArrayContext.walker); - return context.items.toOwnedSlice(); + return context.items.toOwnedSlice(allocator); } pub fn toText(self: *const Self, allocator: Allocator) ![]u8 { - var buffer = std.ArrayList(u8).init(allocator); - errdefer buffer.deinit(); + var buffer: std.ArrayListUnmanaged(u8) = .{}; + errdefer buffer.deinit(allocator); - try buffer.appendSlice("[root"); - try nodeToText(self.root, &buffer); - try buffer.append(']'); + try buffer.appendSlice(allocator, "[root"); + try nodeToText(self.root, &buffer, allocator); + try buffer.append(allocator, ']'); - return buffer.toOwnedSlice(); + return buffer.toOwnedSlice(allocator); } - fn nodeToText(node: *const Node, buffer: *std.ArrayList(u8)) !void { + fn nodeToText(node: *const Node, buffer: *std.ArrayListUnmanaged(u8), allocator: Allocator) !void { switch (node.*) { .branch => |*b| { - try buffer.appendSlice("[branch"); - try nodeToText(b.left, buffer); - try nodeToText(b.right, buffer); - try buffer.append(']'); + try buffer.appendSlice(allocator, "[branch"); + try nodeToText(b.left, buffer, allocator); + try nodeToText(b.right, buffer, allocator); + try buffer.append(allocator, ']'); }, .leaf => |*l| { if (l.is_sentinel) { - try buffer.appendSlice("[empty]"); + try buffer.appendSlice(allocator, "[empty]"); return; } @@ -775,31 +780,31 @@ pub fn Rope(comptime T: type) type { const tag = std.meta.activeTag(l.data); const tag_name = @tagName(tag); - try buffer.append('['); - try buffer.appendSlice(tag_name); + try buffer.append(allocator, '['); + try buffer.appendSlice(allocator, tag_name); if (@hasDecl(T, "Metrics")) { const metrics = l.metrics(); - try buffer.append(':'); - try std.fmt.format(buffer.writer(), "w{d}", .{metrics.weight()}); + try buffer.append(allocator, ':'); + try buffer.writer(allocator).print("w{d}", .{metrics.weight()}); if (@hasDecl(T.Metrics, "total_width")) { - try std.fmt.format(buffer.writer(), ",tw{d}", .{metrics.custom.total_width}); + try buffer.writer(allocator).print(",tw{d}", .{metrics.custom.total_width}); } if (@hasDecl(T.Metrics, "total_bytes")) { - try std.fmt.format(buffer.writer(), ",b{d}", .{metrics.custom.total_bytes}); + try buffer.writer(allocator).print(",b{d}", .{metrics.custom.total_bytes}); } } - try buffer.append(']'); + try buffer.append(allocator, ']'); } else { - try buffer.appendSlice("[leaf"); + try buffer.appendSlice(allocator, "[leaf"); if (@hasDecl(T, "Metrics")) { const metrics = l.metrics(); - try buffer.append(':'); - try std.fmt.format(buffer.writer(), "w{d}", .{metrics.weight()}); + try buffer.append(allocator, ':'); + try buffer.writer(allocator).print("w{d}", .{metrics.weight()}); } - try buffer.append(']'); + try buffer.append(allocator, ']'); } }, } @@ -925,12 +930,13 @@ pub fn Rope(comptime T: type) type { // Handle insertion if (action.insert_between.len > 0) { - var leaves = try std.ArrayList(*const Node).initCapacity(self.allocator, action.insert_between.len); - defer leaves.deinit(); + var leaves: std.ArrayListUnmanaged(*const Node) = .{}; + defer leaves.deinit(self.allocator); + try leaves.ensureTotalCapacity(self.allocator, action.insert_between.len); for (action.insert_between) |item| { const leaf = try Node.new_leaf(self.allocator, item); - try leaves.append(leaf); + try leaves.append(self.allocator, leaf); } const insert_root = try Node.merge_leaves(leaves.items, self.allocator); @@ -1115,12 +1121,13 @@ pub fn Rope(comptime T: type) type { return; } - var leaves = try std.ArrayList(*const Node).initCapacity(self.allocator, items.len); - defer leaves.deinit(); + var leaves: std.ArrayListUnmanaged(*const Node) = .{}; + defer leaves.deinit(self.allocator); + try leaves.ensureTotalCapacity(self.allocator, items.len); for (items) |item| { const leaf = try Node.new_leaf(self.allocator, item); - try leaves.append(leaf); + try leaves.append(self.allocator, leaf); } self.root = try Node.merge_leaves(leaves.items, self.allocator); @@ -1164,10 +1171,10 @@ pub fn Rope(comptime T: type) type { return .{ .keep_walking = false, .err = e }; }; if (!gop.found_existing) { - gop.value_ptr.* = std.ArrayList(MarkerPosition).init(context.cache.allocator); + gop.value_ptr.* = .{}; } - gop.value_ptr.append(.{ + gop.value_ptr.append(context.cache.allocator, .{ .leaf_index = context.current_leaf, .global_weight = context.current_weight, }) catch |e| { diff --git a/packages/core/src/zig/syntax-style.zig b/packages/core/src/zig/syntax-style.zig index 1d8434a19..198954f26 100644 --- a/packages/core/src/zig/syntax-style.zig +++ b/packages/core/src/zig/syntax-style.zig @@ -109,7 +109,7 @@ pub const SyntaxStyle = struct { for (ids, 0..) |id, i| { if (i > 0) writer.writeByte(':') catch return SyntaxStyleError.OutOfMemory; - std.fmt.formatInt(id, 10, .lower, .{}, writer) catch return SyntaxStyleError.OutOfMemory; + writer.print("{d}", .{id}) catch return SyntaxStyleError.OutOfMemory; } const cache_key = cache_key_stream.getWritten(); diff --git a/packages/core/src/zig/test.zig b/packages/core/src/zig/test.zig index fbae51eec..cf5d76d65 100644 --- a/packages/core/src/zig/test.zig +++ b/packages/core/src/zig/test.zig @@ -58,5 +58,6 @@ comptime { _ = terminal_tests; _ = mem_registry_tests; _ = memory_leak_regression_tests; + // _ = example_tests; } diff --git a/packages/core/src/zig/tests/buffer_test.zig b/packages/core/src/zig/tests/buffer_test.zig index 86b11c22c..a9a0bf7f4 100644 --- a/packages/core/src/zig/tests/buffer_test.zig +++ b/packages/core/src/zig/tests/buffer_test.zig @@ -251,14 +251,14 @@ test "OptimizedBuffer - large text buffer with wrapping repeated render" { var view = try TextBufferView.init(std.testing.allocator, tb); defer view.deinit(); - var text_builder = std.ArrayList(u8).init(std.testing.allocator); - defer text_builder.deinit(); + var text_builder: std.ArrayListUnmanaged(u8) = .{}; + defer text_builder.deinit(std.testing.allocator); var line: u32 = 0; while (line < 20) : (line += 1) { - try text_builder.appendSlice("Line "); - try std.fmt.format(text_builder.writer(), "{d}", .{line}); - try text_builder.appendSlice(": 🌟 ζ΅‹θ―• 🎨 Test πŸš€\n"); + try text_builder.appendSlice(std.testing.allocator, "Line "); + try text_builder.writer(std.testing.allocator).print("{d}", .{line}); + try text_builder.appendSlice(std.testing.allocator, ": 🌟 ζ΅‹θ―• 🎨 Test πŸš€\n"); } try tb.setText(text_builder.items); @@ -416,12 +416,12 @@ test "OptimizedBuffer - stress test with many graphemes" { var view = try TextBufferView.init(std.testing.allocator, tb); defer view.deinit(); - var text_builder = std.ArrayList(u8).init(std.testing.allocator); - defer text_builder.deinit(); + var text_builder: std.ArrayListUnmanaged(u8) = .{}; + defer text_builder.deinit(std.testing.allocator); var line: u32 = 0; while (line < 10) : (line += 1) { - try text_builder.appendSlice("πŸŒŸπŸŽ¨πŸš€πŸ•πŸ”πŸŸπŸŒˆπŸŽ­πŸŽͺ🎨🎬🎀🎧🎼🎹🎺🎸🎻\n"); + try text_builder.appendSlice(std.testing.allocator, "πŸŒŸπŸŽ¨πŸš€πŸ•πŸ”πŸŸπŸŒˆπŸŽ­πŸŽͺ🎨🎬🎀🎧🎼🎹🎺🎸🎻\n"); } try tb.setText(text_builder.items); @@ -515,8 +515,8 @@ test "OptimizedBuffer - many unique graphemes with small pool" { var failure_count: u32 = 0; while (render_count < 1000) : (render_count += 1) { - var text_builder = std.ArrayList(u8).init(std.testing.allocator); - defer text_builder.deinit(); + var text_builder: std.ArrayListUnmanaged(u8) = .{}; + defer text_builder.deinit(std.testing.allocator); const base_codepoint: u21 = 0x2600 + @as(u21, @intCast(render_count % 500)); const char_bytes = [_]u8{ @@ -524,9 +524,9 @@ test "OptimizedBuffer - many unique graphemes with small pool" { @intCast(0x80 | ((base_codepoint >> 6) & 0x3F)), @intCast(0x80 | (base_codepoint & 0x3F)), }; - try text_builder.appendSlice(&char_bytes); - try text_builder.appendSlice(" "); - try text_builder.appendSlice(&char_bytes); + try text_builder.appendSlice(std.testing.allocator, &char_bytes); + try text_builder.appendSlice(std.testing.allocator, " "); + try text_builder.appendSlice(std.testing.allocator, &char_bytes); tb.setText(text_builder.items) catch { failure_count += 1; diff --git a/packages/core/src/zig/tests/grapheme_test.zig b/packages/core/src/zig/tests/grapheme_test.zig index 9320ade20..d9c0757ec 100644 --- a/packages/core/src/zig/tests/grapheme_test.zig +++ b/packages/core/src/zig/tests/grapheme_test.zig @@ -285,16 +285,16 @@ test "GraphemePool - many allocations" { for (0..count) |i| { var buffer: [8]u8 = undefined; - const len = std.fmt.formatIntBuf(&buffer, i, 10, .lower, .{}); - ids[i] = try pool.alloc(buffer[0..len]); + const slice = std.fmt.bufPrint(&buffer, "{d}", .{i}) catch unreachable; + ids[i] = try pool.alloc(slice); try pool.incref(ids[i]); } for (ids, 0..count) |id, i| { const retrieved = try pool.get(id); var buffer: [8]u8 = undefined; - const len = std.fmt.formatIntBuf(&buffer, i, 10, .lower, .{}); - try std.testing.expectEqualSlices(u8, buffer[0..len], retrieved); + const slice = std.fmt.bufPrint(&buffer, "{d}", .{i}) catch unreachable; + try std.testing.expectEqualSlices(u8, slice, retrieved); } for (ids) |id| { @@ -306,8 +306,8 @@ test "GraphemePool - allocations with varying sizes" { var pool = GraphemePool.init(std.testing.allocator); defer pool.deinit(); - var ids = std.ArrayList(u32).init(std.testing.allocator); - defer ids.deinit(); + var ids: std.ArrayListUnmanaged(u32) = .{}; + defer ids.deinit(std.testing.allocator); for (0..50) |i| { const size = (i % 5) * 16 + 5; // Vary sizes: 5, 21, 37, 53, 69... @@ -315,7 +315,7 @@ test "GraphemePool - allocations with varying sizes" { @memset(buffer[0..size], @intCast(i % 256)); const id = try pool.alloc(buffer[0..size]); try pool.incref(id); - try ids.append(id); + try ids.append(std.testing.allocator, id); } for (ids.items, 0..50) |id, i| { @@ -338,12 +338,12 @@ test "GraphemePool - reuse many slots" { for (0..100) |i| { var buffer: [8]u8 = undefined; - const len = std.fmt.formatIntBuf(&buffer, i, 10, .lower, .{}); - const id = try pool.alloc(buffer[0..len]); + const slice = std.fmt.bufPrint(&buffer, "{d}", .{i}) catch unreachable; + const id = try pool.alloc(slice); try pool.incref(id); const retrieved = try pool.get(id); - try std.testing.expectEqualSlices(u8, buffer[0..len], retrieved); + try std.testing.expectEqualSlices(u8, slice, retrieved); try pool.decref(id); } @@ -427,8 +427,8 @@ test "GraphemePool - IDs remain unique across many allocations" { for (0..count) |i| { var buffer: [8]u8 = undefined; - const len = std.fmt.formatIntBuf(&buffer, i, 10, .lower, .{}); - ids[i] = try pool.alloc(buffer[0..len]); + const slice = std.fmt.bufPrint(&buffer, "{d}", .{i}) catch unreachable; + ids[i] = try pool.alloc(slice); try pool.incref(ids[i]); } @@ -827,8 +827,8 @@ test "GraphemeTracker - stress test many graphemes" { // Add many graphemes for (0..count) |i| { var buffer: [8]u8 = undefined; - const len = std.fmt.formatIntBuf(&buffer, i, 10, .lower, .{}); - ids[i] = try pool.alloc(buffer[0..len]); + const slice = std.fmt.bufPrint(&buffer, "{d}", .{i}) catch unreachable; + ids[i] = try pool.alloc(slice); tracker.add(ids[i]); } diff --git a/packages/core/src/zig/tests/memory_leak_regression_test.zig b/packages/core/src/zig/tests/memory_leak_regression_test.zig index 01b23a9b1..d610909e8 100644 --- a/packages/core/src/zig/tests/memory_leak_regression_test.zig +++ b/packages/core/src/zig/tests/memory_leak_regression_test.zig @@ -23,15 +23,15 @@ test "GraphemePool - defer cleanup on failure path" { var pool = GraphemePool.init(std.testing.allocator); defer pool.deinit(); - var allocated_ids = std.ArrayList(u32).init(std.testing.allocator); - defer allocated_ids.deinit(); + var allocated_ids: std.ArrayListUnmanaged(u32) = .{}; + defer allocated_ids.deinit(std.testing.allocator); for (0..5) |i| { var buffer: [8]u8 = undefined; - const len = std.fmt.formatIntBuf(&buffer, i, 10, .lower, .{}); - const gid = try pool.alloc(buffer[0..len]); + const slice = std.fmt.bufPrint(&buffer, "{d}", .{i}) catch unreachable; + const gid = try pool.alloc(slice); try pool.incref(gid); - try allocated_ids.append(gid); + try allocated_ids.append(std.testing.allocator, gid); } // Simulate failure cleanup @@ -53,8 +53,8 @@ test "GraphemePool - pending grapheme cleanup on failure" { var pool = GraphemePool.init(std.testing.allocator); defer pool.deinit(); - var result_graphemes = std.ArrayList(u32).init(std.testing.allocator); - defer result_graphemes.deinit(); + var result_graphemes: std.ArrayListUnmanaged(u32) = .{}; + defer result_graphemes.deinit(std.testing.allocator); var pending_gid: ?u32 = null; const success = false; // intentionally never true to test cleanup path @@ -73,7 +73,7 @@ test "GraphemePool - pending grapheme cleanup on failure" { const gid1 = try pool.alloc("grapheme1"); pending_gid = gid1; try pool.incref(gid1); - try result_graphemes.append(gid1); + try result_graphemes.append(std.testing.allocator, gid1); pending_gid = null; const gid2 = try pool.alloc("grapheme2"); diff --git a/packages/core/src/zig/tests/text-buffer-drawing_test.zig b/packages/core/src/zig/tests/text-buffer-drawing_test.zig index 1453086e7..85be4276b 100644 --- a/packages/core/src/zig/tests/text-buffer-drawing_test.zig +++ b/packages/core/src/zig/tests/text-buffer-drawing_test.zig @@ -455,9 +455,9 @@ test "drawTextBuffer - very long unwrapped line clipping" { var view = try TextBufferView.init(std.testing.allocator, tb); defer view.deinit(); - var long_text = std.ArrayList(u8).init(std.testing.allocator); - defer long_text.deinit(); - try long_text.appendNTimes('A', 200); + var long_text: std.ArrayListUnmanaged(u8) = .{}; + defer long_text.deinit(std.testing.allocator); + try long_text.appendNTimes(std.testing.allocator, 'A', 200); try tb.setText(long_text.items); view.setWrapMode(.word); @@ -1358,9 +1358,9 @@ test "drawTextBuffer - horizontal viewport width limits rendering (efficiency te var view = try TextBufferView.init(std.testing.allocator, tb); defer view.deinit(); - var long_line = std.ArrayList(u8).init(std.testing.allocator); - defer long_line.deinit(); - try long_line.appendNTimes('A', 1000); + var long_line: std.ArrayListUnmanaged(u8) = .{}; + defer long_line.deinit(std.testing.allocator); + try long_line.appendNTimes(std.testing.allocator, 'A', 1000); try tb.setText(long_line.items); diff --git a/packages/core/src/zig/tests/text-buffer-iterators_test.zig b/packages/core/src/zig/tests/text-buffer-iterators_test.zig index c6c741226..34153827c 100644 --- a/packages/core/src/zig/tests/text-buffer-iterators_test.zig +++ b/packages/core/src/zig/tests/text-buffer-iterators_test.zig @@ -55,16 +55,17 @@ test "walkLines - single text segment" { }); const Context = struct { - lines: std.ArrayList(LineInfo), + lines: std.ArrayListUnmanaged(LineInfo), + allocator: std.mem.Allocator, fn callback(ctx_ptr: *anyopaque, line_info: LineInfo) void { const ctx = @as(*@This(), @ptrCast(@alignCast(ctx_ptr))); - ctx.lines.append(line_info) catch {}; + ctx.lines.append(ctx.allocator, line_info) catch {}; } }; - var ctx = Context{ .lines = std.ArrayList(LineInfo).init(allocator) }; - defer ctx.lines.deinit(); + var ctx = Context{ .lines = .{}, .allocator = allocator }; + defer ctx.lines.deinit(allocator); iter_mod.walkLines(&rope, &ctx, Context.callback, true); @@ -101,16 +102,17 @@ test "walkLines - text + break + text" { }); const Context = struct { - lines: std.ArrayList(LineInfo), + lines: std.ArrayListUnmanaged(LineInfo), + allocator: std.mem.Allocator, fn callback(ctx_ptr: *anyopaque, line_info: LineInfo) void { const ctx = @as(*@This(), @ptrCast(@alignCast(ctx_ptr))); - ctx.lines.append(line_info) catch {}; + ctx.lines.append(ctx.allocator, line_info) catch {}; } }; - var ctx = Context{ .lines = std.ArrayList(LineInfo).init(allocator) }; - defer ctx.lines.deinit(); + var ctx = Context{ .lines = .{}, .allocator = allocator }; + defer ctx.lines.deinit(allocator); iter_mod.walkLines(&rope, &ctx, Context.callback, true); @@ -154,16 +156,17 @@ test "walkLines - exclude newlines in offset" { }); const Context = struct { - lines: std.ArrayList(LineInfo), + lines: std.ArrayListUnmanaged(LineInfo), + allocator: std.mem.Allocator, fn callback(ctx_ptr: *anyopaque, line_info: LineInfo) void { const ctx = @as(*@This(), @ptrCast(@alignCast(ctx_ptr))); - ctx.lines.append(line_info) catch {}; + ctx.lines.append(ctx.allocator, line_info) catch {}; } }; - var ctx = Context{ .lines = std.ArrayList(LineInfo).init(allocator) }; - defer ctx.lines.deinit(); + var ctx = Context{ .lines = .{}, .allocator = allocator }; + defer ctx.lines.deinit(allocator); iter_mod.walkLines(&rope, &ctx, Context.callback, false); diff --git a/packages/core/src/zig/tests/text-buffer-view_test.zig b/packages/core/src/zig/tests/text-buffer-view_test.zig index a8b155c42..69e7b4c26 100644 --- a/packages/core/src/zig/tests/text-buffer-view_test.zig +++ b/packages/core/src/zig/tests/text-buffer-view_test.zig @@ -699,13 +699,13 @@ test "TextBufferView word wrapping - fragmented rope with word boundary" { const chunk2 = tb.createChunk(mem_id, 14, 15); // "f" const chunk3 = tb.createChunk(mem_id, 15, 20); // "riend" - var segments = std.ArrayList(Segment).init(std.testing.allocator); - defer segments.deinit(); + var segments: std.ArrayListUnmanaged(Segment) = .{}; + defer segments.deinit(std.testing.allocator); - try segments.append(Segment{ .linestart = {} }); - try segments.append(Segment{ .text = chunk1 }); - try segments.append(Segment{ .text = chunk2 }); - try segments.append(Segment{ .text = chunk3 }); + try segments.append(std.testing.allocator, Segment{ .linestart = {} }); + try segments.append(std.testing.allocator, Segment{ .text = chunk1 }); + try segments.append(std.testing.allocator, Segment{ .text = chunk2 }); + try segments.append(std.testing.allocator, Segment{ .text = chunk3 }); try tb.rope.setSegments(segments.items); @@ -1436,11 +1436,11 @@ test "TextBufferView line info - lines with different widths" { var view = try TextBufferView.init(std.testing.allocator, tb); defer view.deinit(); - var text_builder = std.ArrayList(u8).init(std.testing.allocator); - defer text_builder.deinit(); - try text_builder.appendSlice("Short\n"); - try text_builder.appendNTimes('A', 50); - try text_builder.appendSlice("\nMedium"); + var text_builder: std.ArrayListUnmanaged(u8) = .{}; + defer text_builder.deinit(std.testing.allocator); + try text_builder.appendSlice(std.testing.allocator, "Short\n"); + try text_builder.appendNTimes(std.testing.allocator, 'A', 50); + try text_builder.appendSlice(std.testing.allocator, "\nMedium"); const text = text_builder.items; try tb.setText(text); @@ -1484,14 +1484,14 @@ test "TextBufferView line info - thousands of lines" { var view = try TextBufferView.init(std.testing.allocator, tb); defer view.deinit(); - var text_builder = std.ArrayList(u8).init(std.testing.allocator); - defer text_builder.deinit(); + var text_builder: std.ArrayListUnmanaged(u8) = .{}; + defer text_builder.deinit(std.testing.allocator); var i: u32 = 0; while (i < 999) : (i += 1) { - try std.fmt.format(text_builder.writer(), "Line {}\n", .{i}); + try text_builder.writer(std.testing.allocator).print("Line {}\n", .{i}); } - try std.fmt.format(text_builder.writer(), "Line {}", .{i}); + try text_builder.writer(std.testing.allocator).print("Line {}", .{i}); try tb.setText(text_builder.items); @@ -2488,14 +2488,14 @@ test "TextBufferView line info - line starts monotonically increasing" { var view = try TextBufferView.init(std.testing.allocator, tb); defer view.deinit(); - var text_builder = std.ArrayList(u8).init(std.testing.allocator); - defer text_builder.deinit(); + var text_builder: std.ArrayListUnmanaged(u8) = .{}; + defer text_builder.deinit(std.testing.allocator); var i: u32 = 0; while (i < 99) : (i += 1) { - try std.fmt.format(text_builder.writer(), "Line {}\n", .{i}); + try text_builder.writer(std.testing.allocator).print("Line {}\n", .{i}); } - try std.fmt.format(text_builder.writer(), "Line {}", .{i}); + try text_builder.writer(std.testing.allocator).print("Line {}", .{i}); try tb.setText(text_builder.items); @@ -2897,16 +2897,16 @@ test "TextBufferView word wrapping - chunk at exact wrap boundary" { const seg_mod = @import("../text-buffer-segment.zig"); const Segment = seg_mod.Segment; - var segments = std.ArrayList(Segment).init(std.testing.allocator); - defer segments.deinit(); + var segments: std.ArrayListUnmanaged(Segment) = .{}; + defer segments.deinit(std.testing.allocator); - try segments.append(Segment{ .linestart = {} }); + try segments.append(std.testing.allocator, Segment{ .linestart = {} }); const chunk1 = tb.createChunk(mem_id, 0, 17); - try segments.append(Segment{ .text = chunk1 }); + try segments.append(std.testing.allocator, Segment{ .text = chunk1 }); const chunk2 = tb.createChunk(mem_id, 17, 21); - try segments.append(Segment{ .text = chunk2 }); + try segments.append(std.testing.allocator, Segment{ .text = chunk2 }); try tb.rope.setSegments(segments.items); view.virtual_lines_dirty = true; diff --git a/packages/core/src/zig/tests/text-buffer_test.zig b/packages/core/src/zig/tests/text-buffer_test.zig index e5ab8b763..b774c10b1 100644 --- a/packages/core/src/zig/tests/text-buffer_test.zig +++ b/packages/core/src/zig/tests/text-buffer_test.zig @@ -238,11 +238,11 @@ test "TextBuffer line info - lines with different widths" { defer tb.deinit(); // Create text with different line lengths - var text_builder = std.ArrayList(u8).init(std.testing.allocator); - defer text_builder.deinit(); - try text_builder.appendSlice("Short\n"); - try text_builder.appendNTimes('A', 50); - try text_builder.appendSlice("\nMedium"); + var text_builder: std.ArrayListUnmanaged(u8) = .{}; + defer text_builder.deinit(std.testing.allocator); + try text_builder.appendSlice(std.testing.allocator, "Short\n"); + try text_builder.appendNTimes(std.testing.allocator, 'A', 50); + try text_builder.appendSlice(std.testing.allocator, "\nMedium"); const text = text_builder.items; try tb.setText(text); @@ -335,11 +335,11 @@ test "TextBuffer line info - buffer resize operations" { defer tb.deinit(); // Add text that will cause multiple resizes - var text_builder = std.ArrayList(u8).init(std.testing.allocator); - defer text_builder.deinit(); - try text_builder.appendNTimes('A', 100); - try text_builder.appendSlice("\n"); - try text_builder.appendNTimes('B', 100); + var text_builder: std.ArrayListUnmanaged(u8) = .{}; + defer text_builder.deinit(std.testing.allocator); + try text_builder.appendNTimes(std.testing.allocator, 'A', 100); + try text_builder.appendSlice(std.testing.allocator, "\n"); + try text_builder.appendNTimes(std.testing.allocator, 'B', 100); const longText = text_builder.items; try tb.setText(longText); @@ -355,15 +355,15 @@ test "TextBuffer line info - thousands of lines" { defer tb.deinit(); // Create text with 1000 lines - var text_builder = std.ArrayList(u8).init(std.testing.allocator); - defer text_builder.deinit(); + var text_builder: std.ArrayListUnmanaged(u8) = .{}; + defer text_builder.deinit(std.testing.allocator); var i: u32 = 0; while (i < 999) : (i += 1) { - try std.fmt.format(text_builder.writer(), "Line {}\n", .{i}); + try text_builder.writer(std.testing.allocator).print("Line {}\n", .{i}); } // Last line without newline - try std.fmt.format(text_builder.writer(), "Line {}", .{i}); + try text_builder.writer(std.testing.allocator).print("Line {}", .{i}); try tb.setText(text_builder.items); @@ -543,16 +543,17 @@ test "TextBuffer line iteration - walkLines callback" { try tb.setText(text); const Context = struct { - lines: std.ArrayList(iter_mod.LineInfo), + lines: std.ArrayListUnmanaged(iter_mod.LineInfo), + allocator: std.mem.Allocator, fn callback(ctx_ptr: *anyopaque, line_info: iter_mod.LineInfo) void { const ctx = @as(*@This(), @ptrCast(@alignCast(ctx_ptr))); - ctx.lines.append(line_info) catch {}; + ctx.lines.append(ctx.allocator, line_info) catch {}; } }; - var ctx = Context{ .lines = std.ArrayList(iter_mod.LineInfo).init(std.testing.allocator) }; - defer ctx.lines.deinit(); + var ctx = Context{ .lines = .{}, .allocator = std.testing.allocator }; + defer ctx.lines.deinit(std.testing.allocator); iter_mod.walkLines(&tb.rope, &ctx, Context.callback, true); @@ -1380,14 +1381,14 @@ test "TextBuffer setText - very long line with SIMD processing" { defer tb.deinit(); // Create a text longer than 16 bytes (SIMD vector size) to test SIMD path - var text_builder = std.ArrayList(u8).init(std.testing.allocator); - defer text_builder.deinit(); + var text_builder: std.ArrayListUnmanaged(u8) = .{}; + defer text_builder.deinit(std.testing.allocator); - try text_builder.appendNTimes('A', 100); - try text_builder.appendSlice("\r\n"); - try text_builder.appendNTimes('B', 100); - try text_builder.appendSlice("\n"); - try text_builder.appendNTimes('C', 100); + try text_builder.appendNTimes(std.testing.allocator, 'A', 100); + try text_builder.appendSlice(std.testing.allocator, "\r\n"); + try text_builder.appendNTimes(std.testing.allocator, 'B', 100); + try text_builder.appendSlice(std.testing.allocator, "\n"); + try text_builder.appendNTimes(std.testing.allocator, 'C', 100); try tb.setText(text_builder.items); @@ -1438,17 +1439,17 @@ test "TextBuffer setText - SIMD boundary conditions" { defer tb.deinit(); // Create text with newlines at SIMD vector boundaries (16 bytes) - var text_builder = std.ArrayList(u8).init(std.testing.allocator); - defer text_builder.deinit(); + var text_builder: std.ArrayListUnmanaged(u8) = .{}; + defer text_builder.deinit(std.testing.allocator); // 15 chars + \n = exactly 16 bytes - try text_builder.appendNTimes('X', 15); - try text_builder.appendSlice("\n"); + try text_builder.appendNTimes(std.testing.allocator, 'X', 15); + try text_builder.appendSlice(std.testing.allocator, "\n"); // 15 more chars + \n - try text_builder.appendNTimes('Y', 15); - try text_builder.appendSlice("\n"); + try text_builder.appendNTimes(std.testing.allocator, 'Y', 15); + try text_builder.appendSlice(std.testing.allocator, "\n"); // Final line - try text_builder.appendNTimes('Z', 10); + try text_builder.appendNTimes(std.testing.allocator, 'Z', 10); try tb.setText(text_builder.items); @@ -1467,13 +1468,13 @@ test "TextBuffer setText - CRLF at SIMD boundary" { defer tb.deinit(); // Create text where \r is at end of SIMD vector and \n is at start of next - var text_builder = std.ArrayList(u8).init(std.testing.allocator); - defer text_builder.deinit(); + var text_builder: std.ArrayListUnmanaged(u8) = .{}; + defer text_builder.deinit(std.testing.allocator); // 15 chars + \r = 16 bytes, then \n at position 16 - try text_builder.appendNTimes('A', 15); - try text_builder.appendSlice("\r\n"); - try text_builder.appendSlice("Next line"); + try text_builder.appendNTimes(std.testing.allocator, 'A', 15); + try text_builder.appendSlice(std.testing.allocator, "\r\n"); + try text_builder.appendSlice(std.testing.allocator, "Next line"); try tb.setText(text_builder.items); @@ -1874,13 +1875,13 @@ test "TextBuffer append - streaming/chunked append vs ground truth" { try tb.append(" end"); // Build expected ground truth - var expected = std.ArrayList(u8).init(std.testing.allocator); - defer expected.deinit(); - try expected.appendSlice("First"); - try expected.appendSlice("\nLine2"); - try expected.appendSlice("\n"); - try expected.appendSlice("Line3"); - try expected.appendSlice(" end"); + var expected: std.ArrayListUnmanaged(u8) = .{}; + defer expected.deinit(std.testing.allocator); + try expected.appendSlice(std.testing.allocator, "First"); + try expected.appendSlice(std.testing.allocator, "\nLine2"); + try expected.appendSlice(std.testing.allocator, "\n"); + try expected.appendSlice(std.testing.allocator, "Line3"); + try expected.appendSlice(std.testing.allocator, " end"); var out_buffer: [100]u8 = undefined; const written = tb.getPlainTextIntoBuffer(&out_buffer); diff --git a/packages/core/src/zig/tests/utf8_no_zwj_test.zig b/packages/core/src/zig/tests/utf8_no_zwj_test.zig index d7d2b3c8d..85d3552ee 100644 --- a/packages/core/src/zig/tests/utf8_no_zwj_test.zig +++ b/packages/core/src/zig/tests/utf8_no_zwj_test.zig @@ -91,15 +91,15 @@ test "no_zwj: mixed text with ZWJ emoji" { } test "no_zwj: findGraphemeInfo splits ZWJ sequences" { - var result_unicode = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result_unicode.deinit(); - var result_no_zwj = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result_no_zwj.deinit(); + var result_unicode: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result_unicode.deinit(testing.allocator); + var result_no_zwj: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result_no_zwj.deinit(testing.allocator); const text = "HiπŸ‘©β€πŸš€Bye"; - try utf8.findGraphemeInfo(text, 4, false, .unicode, &result_unicode); - try utf8.findGraphemeInfo(text, 4, false, .no_zwj, &result_no_zwj); + try utf8.findGraphemeInfo(text, 4, false, .unicode, testing.allocator, &result_unicode); + try utf8.findGraphemeInfo(text, 4, false, .no_zwj, testing.allocator, &result_no_zwj); // unicode: 1 grapheme (the whole ZWJ sequence) try testing.expectEqual(@as(usize, 1), result_unicode.items.len); diff --git a/packages/core/src/zig/tests/utf8_test.zig b/packages/core/src/zig/tests/utf8_test.zig index dc0e82aa7..ec5de8ab1 100644 --- a/packages/core/src/zig/tests/utf8_test.zig +++ b/packages/core/src/zig/tests/utf8_test.zig @@ -2077,26 +2077,26 @@ test "calculateTextWidth: U+269B atom symbol should be width 2" { // ============================================================================ test "findGraphemeInfo: empty string" { - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); - try utf8.findGraphemeInfo("", 4, true, .unicode, &result); + try utf8.findGraphemeInfo("", 4, true, .unicode, testing.allocator, &result); try testing.expectEqual(@as(usize, 0), result.items.len); } test "findGraphemeInfo: ASCII-only returns empty" { - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); - try utf8.findGraphemeInfo("hello world", 4, true, .unicode, &result); + try utf8.findGraphemeInfo("hello world", 4, true, .unicode, testing.allocator, &result); try testing.expectEqual(@as(usize, 0), result.items.len); } test "findGraphemeInfo: ASCII with tab" { - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); - try utf8.findGraphemeInfo("hello\tworld", 4, false, .unicode, &result); + try utf8.findGraphemeInfo("hello\tworld", 4, false, .unicode, testing.allocator, &result); // Should have one entry for the tab try testing.expectEqual(@as(usize, 1), result.items.len); @@ -2107,10 +2107,10 @@ test "findGraphemeInfo: ASCII with tab" { } test "findGraphemeInfo: multiple tabs" { - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); - try utf8.findGraphemeInfo("a\tb\tc", 4, false, .unicode, &result); + try utf8.findGraphemeInfo("a\tb\tc", 4, false, .unicode, testing.allocator, &result); // Should have two entries for the tabs try testing.expectEqual(@as(usize, 2), result.items.len); @@ -2129,11 +2129,11 @@ test "findGraphemeInfo: multiple tabs" { } test "findGraphemeInfo: CJK characters" { - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); const text = "helloδΈ–η•Œ"; - try utf8.findGraphemeInfo(text, 4, false, .unicode, &result); + try utf8.findGraphemeInfo(text, 4, false, .unicode, testing.allocator, &result); // Should have two entries for the CJK characters try testing.expectEqual(@as(usize, 2), result.items.len); @@ -2152,11 +2152,11 @@ test "findGraphemeInfo: CJK characters" { } test "findGraphemeInfo: emoji with skin tone" { - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); const text = "HiπŸ‘‹πŸΏBye"; // Hi + wave + dark skin tone + Bye - try utf8.findGraphemeInfo(text, 4, false, .unicode, &result); + try utf8.findGraphemeInfo(text, 4, false, .unicode, testing.allocator, &result); // Should have one entry for the emoji cluster try testing.expectEqual(@as(usize, 1), result.items.len); @@ -2168,11 +2168,11 @@ test "findGraphemeInfo: emoji with skin tone" { } test "findGraphemeInfo: emoji with ZWJ" { - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); const text = "aπŸ‘©β€πŸš€b"; // a + woman astronaut + b - try utf8.findGraphemeInfo(text, 4, false, .unicode, &result); + try utf8.findGraphemeInfo(text, 4, false, .unicode, testing.allocator, &result); // Should have one entry for the emoji cluster try testing.expectEqual(@as(usize, 1), result.items.len); @@ -2183,11 +2183,11 @@ test "findGraphemeInfo: emoji with ZWJ" { } test "findGraphemeInfo: combining mark" { - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); const text = "cafe\u{0301}"; // cafΓ© with combining acute - try utf8.findGraphemeInfo(text, 4, false, .unicode, &result); + try utf8.findGraphemeInfo(text, 4, false, .unicode, testing.allocator, &result); // Should have one entry for e + combining mark try testing.expectEqual(@as(usize, 1), result.items.len); @@ -2199,11 +2199,11 @@ test "findGraphemeInfo: combining mark" { } test "findGraphemeInfo: flag emoji" { - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); const text = "USπŸ‡ΊπŸ‡Έ"; // US + flag - try utf8.findGraphemeInfo(text, 4, false, .unicode, &result); + try utf8.findGraphemeInfo(text, 4, false, .unicode, testing.allocator, &result); // Should have one entry for the flag (two regional indicators) try testing.expectEqual(@as(usize, 1), result.items.len); @@ -2215,11 +2215,11 @@ test "findGraphemeInfo: flag emoji" { } test "findGraphemeInfo: mixed content" { - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); const text = "Hi\tδΈ–η•Œ!"; // Hi + tab + CJK + ! - try utf8.findGraphemeInfo(text, 4, false, .unicode, &result); + try utf8.findGraphemeInfo(text, 4, false, .unicode, testing.allocator, &result); // Should have three entries: tab, δΈ–, η•Œ try testing.expectEqual(@as(usize, 3), result.items.len); @@ -2244,21 +2244,21 @@ test "findGraphemeInfo: mixed content" { } test "findGraphemeInfo: only ASCII letters no cache" { - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); - try utf8.findGraphemeInfo("abcdefghij", 4, false, .unicode, &result); + try utf8.findGraphemeInfo("abcdefghij", 4, false, .unicode, testing.allocator, &result); // No special characters, should be empty try testing.expectEqual(@as(usize, 0), result.items.len); } test "findGraphemeInfo: emoji with VS16" { - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); const text = "I ❀️ U"; // I + space + heart + VS16 + space + U - try utf8.findGraphemeInfo(text, 4, false, .unicode, &result); + try utf8.findGraphemeInfo(text, 4, false, .unicode, testing.allocator, &result); // Should have one entry for the emoji cluster try testing.expectEqual(@as(usize, 1), result.items.len); @@ -2269,22 +2269,22 @@ test "findGraphemeInfo: emoji with VS16" { } test "findGraphemeInfo: realistic text" { - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); const text = "function test() {\n\tconst δΈ–η•Œ = 10;\n}"; - try utf8.findGraphemeInfo(text, 4, false, .unicode, &result); + try utf8.findGraphemeInfo(text, 4, false, .unicode, testing.allocator, &result); // Should have entries for: tab, δΈ–, η•Œ try testing.expectEqual(@as(usize, 3), result.items.len); } test "findGraphemeInfo: hiragana" { - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); const text = "こんにけは"; - try utf8.findGraphemeInfo(text, 4, false, .unicode, &result); + try utf8.findGraphemeInfo(text, 4, false, .unicode, testing.allocator, &result); // Should have 5 entries (each hiragana is 3 bytes, width 2) try testing.expectEqual(@as(usize, 5), result.items.len); @@ -2296,8 +2296,8 @@ test "findGraphemeInfo: hiragana" { } test "findGraphemeInfo: at SIMD boundary" { - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); // Create text with multibyte char near SIMD boundary (16 bytes) var buf: [32]u8 = undefined; @@ -2305,7 +2305,7 @@ test "findGraphemeInfo: at SIMD boundary" { const cjk = "δΈ–"; @memcpy(buf[14..17], cjk); // Place CJK char at boundary - try utf8.findGraphemeInfo(&buf, 4, false, .unicode, &result); + try utf8.findGraphemeInfo(&buf, 4, false, .unicode, testing.allocator, &result); // Should find the CJK character var found = false; @@ -2919,14 +2919,14 @@ test "calculateTextWidth: surrogate pair edge cases" { test "calculateTextWidth: long grapheme cluster chain" { // Create a base + many combining marks - var text = std.ArrayList(u8).init(testing.allocator); - defer text.deinit(); + var text: std.ArrayListUnmanaged(u8) = .{}; + defer text.deinit(testing.allocator); - try text.appendSlice("e"); + try text.appendSlice(testing.allocator, "e"); // Add 10 combining marks var i: usize = 0; while (i < 10) : (i += 1) { - try text.appendSlice("\u{0301}"); // Combining acute accent + try text.appendSlice(testing.allocator, "\u{0301}"); // Combining acute accent } const width = utf8.calculateTextWidth(text.items, 4, false, .unicode); @@ -3509,32 +3509,28 @@ test "calculateTextWidth: complex text with emojis and multiple scripts" { test "calculateTextWidth: validate against unicode-width-map.zon" { const zon_content = @embedFile("unicode-width-map.zon"); - const zon_with_null = try testing.allocator.dupeZ(u8, zon_content); - defer testing.allocator.free(zon_with_null); + + // Use arena allocator to avoid memory leaks from ZON parser string allocations + var arena = std.heap.ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + const zon_with_null = try allocator.dupeZ(u8, zon_content); const WidthEntry = struct { codepoint: []const u8, width: i32, }; - var status: std.zon.parse.Status = .{}; - defer status.deinit(testing.allocator); - const width_entries = std.zon.parse.fromSlice( []const WidthEntry, - testing.allocator, + allocator, zon_with_null, - &status, + null, .{}, ) catch |err| { return err; }; - defer { - for (width_entries) |entry| { - testing.allocator.free(entry.codepoint); - } - testing.allocator.free(width_entries); - } var successes: usize = 0; var failures: usize = 0; @@ -3592,10 +3588,10 @@ test "findGraphemeInfo: comprehensive multilingual text" { const expected_width = utf8.calculateTextWidth(text, 4, false, .unicode); - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); - try utf8.findGraphemeInfo(text, 4, false, .unicode, &result); + try utf8.findGraphemeInfo(text, 4, false, .unicode, testing.allocator, &result); try testing.expect(result.items.len > 0); var prev_end_byte: usize = 0; diff --git a/packages/core/src/zig/tests/utf8_wcwidth_test.zig b/packages/core/src/zig/tests/utf8_wcwidth_test.zig index a0131d5a3..aec6338f6 100644 --- a/packages/core/src/zig/tests/utf8_wcwidth_test.zig +++ b/packages/core/src/zig/tests/utf8_wcwidth_test.zig @@ -3,26 +3,26 @@ const testing = std.testing; const utf8 = @import("../utf8.zig"); test "findGraphemeInfo wcwidth: empty string" { - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); - try utf8.findGraphemeInfo("", 4, true, .wcwidth, &result); + try utf8.findGraphemeInfo("", 4, true, .wcwidth, testing.allocator, &result); try testing.expectEqual(@as(usize, 0), result.items.len); } test "findGraphemeInfo wcwidth: ASCII-only returns empty" { - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); - try utf8.findGraphemeInfo("hello world", 4, true, .wcwidth, &result); + try utf8.findGraphemeInfo("hello world", 4, true, .wcwidth, testing.allocator, &result); try testing.expectEqual(@as(usize, 0), result.items.len); } test "findGraphemeInfo wcwidth: ASCII with tab" { - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); - try utf8.findGraphemeInfo("hello\tworld", 4, false, .wcwidth, &result); + try utf8.findGraphemeInfo("hello\tworld", 4, false, .wcwidth, testing.allocator, &result); // Should have one entry for the tab try testing.expectEqual(@as(usize, 1), result.items.len); @@ -33,11 +33,11 @@ test "findGraphemeInfo wcwidth: ASCII with tab" { } test "findGraphemeInfo wcwidth: CJK characters" { - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); const text = "helloδΈ–η•Œ"; - try utf8.findGraphemeInfo(text, 4, false, .wcwidth, &result); + try utf8.findGraphemeInfo(text, 4, false, .wcwidth, testing.allocator, &result); // Should have two entries for the CJK characters (each codepoint separately) try testing.expectEqual(@as(usize, 2), result.items.len); @@ -56,11 +56,11 @@ test "findGraphemeInfo wcwidth: CJK characters" { } test "findGraphemeInfo wcwidth: emoji with skin tone - each codepoint separate" { - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); const text = "πŸ‘‹πŸΏ"; // Wave (4 bytes) + skin tone modifier (4 bytes) - try utf8.findGraphemeInfo(text, 4, false, .wcwidth, &result); + try utf8.findGraphemeInfo(text, 4, false, .wcwidth, testing.allocator, &result); // In wcwidth mode, these are TWO separate codepoints try testing.expectEqual(@as(usize, 2), result.items.len); @@ -77,11 +77,11 @@ test "findGraphemeInfo wcwidth: emoji with skin tone - each codepoint separate" } test "findGraphemeInfo wcwidth: emoji with ZWJ - each codepoint separate" { - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); const text = "πŸ‘©β€πŸš€"; // Woman + ZWJ + Rocket - try utf8.findGraphemeInfo(text, 4, false, .wcwidth, &result); + try utf8.findGraphemeInfo(text, 4, false, .wcwidth, testing.allocator, &result); // In wcwidth mode, we see woman (width 2) and rocket (width 2) // ZWJ has width 0 so it's not in the list @@ -89,11 +89,11 @@ test "findGraphemeInfo wcwidth: emoji with ZWJ - each codepoint separate" { } test "findGraphemeInfo wcwidth: combining mark - base and mark separate" { - var result = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result.deinit(); + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result.deinit(testing.allocator); const text = "e\u{0301}test"; // e + combining acute accent - try utf8.findGraphemeInfo(text, 4, false, .wcwidth, &result); + try utf8.findGraphemeInfo(text, 4, false, .wcwidth, testing.allocator, &result); // In wcwidth mode, combining mark is a separate codepoint with width 0 // So we don't see it in the results (only non-zero width codepoints) @@ -102,15 +102,15 @@ test "findGraphemeInfo wcwidth: combining mark - base and mark separate" { } test "findGraphemeInfo wcwidth vs unicode: emoji with skin tone" { - var result_wcwidth = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result_wcwidth.deinit(); - var result_unicode = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result_unicode.deinit(); + var result_wcwidth: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result_wcwidth.deinit(testing.allocator); + var result_unicode: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result_unicode.deinit(testing.allocator); const text = "HiπŸ‘‹πŸΏBye"; - try utf8.findGraphemeInfo(text, 4, false, .wcwidth, &result_wcwidth); - try utf8.findGraphemeInfo(text, 4, false, .unicode, &result_unicode); + try utf8.findGraphemeInfo(text, 4, false, .wcwidth, testing.allocator, &result_wcwidth); + try utf8.findGraphemeInfo(text, 4, false, .unicode, testing.allocator, &result_unicode); // wcwidth: 2 codepoints (wave + skin tone) try testing.expectEqual(@as(usize, 2), result_wcwidth.items.len); @@ -122,15 +122,15 @@ test "findGraphemeInfo wcwidth vs unicode: emoji with skin tone" { } test "findGraphemeInfo wcwidth vs unicode: flag emoji" { - var result_wcwidth = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result_wcwidth.deinit(); - var result_unicode = std.ArrayList(utf8.GraphemeInfo).init(testing.allocator); - defer result_unicode.deinit(); + var result_wcwidth: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result_wcwidth.deinit(testing.allocator); + var result_unicode: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + defer result_unicode.deinit(testing.allocator); const text = "πŸ‡ΊπŸ‡Έ"; // US flag (two regional indicators) - try utf8.findGraphemeInfo(text, 4, false, .wcwidth, &result_wcwidth); - try utf8.findGraphemeInfo(text, 4, false, .unicode, &result_unicode); + try utf8.findGraphemeInfo(text, 4, false, .wcwidth, testing.allocator, &result_wcwidth); + try utf8.findGraphemeInfo(text, 4, false, .unicode, testing.allocator, &result_unicode); // wcwidth: 2 codepoints (two regional indicators, each width 1) try testing.expectEqual(@as(usize, 2), result_wcwidth.items.len); diff --git a/packages/core/src/zig/text-buffer-segment.zig b/packages/core/src/zig/text-buffer-segment.zig index d59137744..dd6d3b961 100644 --- a/packages/core/src/zig/text-buffer-segment.zig +++ b/packages/core/src/zig/text-buffer-segment.zig @@ -94,13 +94,13 @@ pub const TextChunk = struct { const chunk_bytes = self.getBytes(mem_registry); - var grapheme_list = std.ArrayList(GraphemeInfo).init(allocator); - errdefer grapheme_list.deinit(); + var grapheme_list: std.ArrayListUnmanaged(GraphemeInfo) = .{}; + errdefer grapheme_list.deinit(allocator); - try utf8.findGraphemeInfo(chunk_bytes, tabwidth, self.isAsciiOnly(), width_method, &grapheme_list); + try utf8.findGraphemeInfo(chunk_bytes, tabwidth, self.isAsciiOnly(), width_method, allocator, &grapheme_list); // TODO: Calling this with an arena allocator will just double the memory usage? - const graphemes = try grapheme_list.toOwnedSlice(); + const graphemes = try grapheme_list.toOwnedSlice(allocator); mut_self.graphemes = graphemes; return graphemes; diff --git a/packages/core/src/zig/text-buffer.zig b/packages/core/src/zig/text-buffer.zig index 98b409dc4..c00b9ef6d 100644 --- a/packages/core/src/zig/text-buffer.zig +++ b/packages/core/src/zig/text-buffer.zig @@ -347,7 +347,7 @@ pub const UnifiedTextBuffer = struct { // The rope's boundary rewrite will handle normalization at join points var result = try self.textToSegments(self.global_allocator, text, mem_id, 0, false); - defer result.segments.deinit(); + defer result.segments.deinit(result.allocator); const insert_pos = self.rope.count(); try self.rope.insert_slice(insert_pos, result.segments.items); @@ -363,7 +363,7 @@ pub const UnifiedTextBuffer = struct { } var result = try self.textToSegments(self.global_allocator, text, mem_id, 0, true); - defer result.segments.deinit(); + defer result.segments.deinit(result.allocator); try self.rope.setSegments(result.segments.items); @@ -406,16 +406,16 @@ pub const UnifiedTextBuffer = struct { mem_id: u8, byte_offset: u32, prepend_linestart: bool, - ) TextBufferError!struct { segments: std.ArrayList(Segment), total_width: u32 } { + ) TextBufferError!struct { segments: std.ArrayListUnmanaged(Segment), total_width: u32, allocator: Allocator } { var break_result = utf8.LineBreakResult.init(allocator); defer break_result.deinit(); try utf8.findLineBreaks(text, &break_result); - var segments = std.ArrayList(Segment).init(allocator); - errdefer segments.deinit(); + var segments: std.ArrayListUnmanaged(Segment) = .{}; + errdefer segments.deinit(allocator); if (prepend_linestart) { - try segments.append(Segment{ .linestart = {} }); + try segments.append(allocator, Segment{ .linestart = {} }); } var local_start: u32 = 0; @@ -430,23 +430,23 @@ pub const UnifiedTextBuffer = struct { if (local_end > local_start) { const chunk = self.createChunk(mem_id, byte_offset + local_start, byte_offset + local_end); - try segments.append(Segment{ .text = chunk }); + try segments.append(allocator, Segment{ .text = chunk }); total_width += chunk.width; } - try segments.append(Segment{ .brk = {} }); - try segments.append(Segment{ .linestart = {} }); + try segments.append(allocator, Segment{ .brk = {} }); + try segments.append(allocator, Segment{ .linestart = {} }); local_start = break_pos + 1; } if (local_start < text.len) { const chunk = self.createChunk(mem_id, byte_offset + local_start, byte_offset + @as(u32, @intCast(text.len))); - try segments.append(Segment{ .text = chunk }); + try segments.append(allocator, Segment{ .text = chunk }); total_width += chunk.width; } - return .{ .segments = segments, .total_width = total_width }; + return .{ .segments = segments, .total_width = total_width, .allocator = allocator }; } pub fn getLineCount(self: *const Self) u32 { @@ -645,12 +645,12 @@ pub const UnifiedTextBuffer = struct { hl_idx: usize, }; - var events = std.ArrayList(Event).init(self.global_allocator); - defer events.deinit(); + var events: std.ArrayListUnmanaged(Event) = .{}; + defer events.deinit(self.global_allocator); for (highlights, 0..) |hl, idx| { - try events.append(.{ .col = hl.col_start, .is_start = true, .hl_idx = idx }); - try events.append(.{ .col = hl.col_end, .is_start = false, .hl_idx = idx }); + try events.append(self.global_allocator, .{ .col = hl.col_start, .is_start = true, .hl_idx = idx }); + try events.append(self.global_allocator, .{ .col = hl.col_end, .is_start = false, .hl_idx = idx }); } // Sort by column, ends before starts at same position diff --git a/packages/core/src/zig/utf8.zig b/packages/core/src/zig/utf8.zig index 605d1f842..390bd6000 100644 --- a/packages/core/src/zig/utf8.zig +++ b/packages/core/src/zig/utf8.zig @@ -60,16 +60,18 @@ pub const LineBreak = struct { }; pub const LineBreakResult = struct { - breaks: std.ArrayList(LineBreak), + breaks: std.ArrayListUnmanaged(LineBreak), + allocator: std.mem.Allocator, pub fn init(allocator: std.mem.Allocator) LineBreakResult { return .{ - .breaks = std.ArrayList(LineBreak).init(allocator), + .breaks = .{}, + .allocator = allocator, }; } pub fn deinit(self: *LineBreakResult) void { - self.breaks.deinit(); + self.breaks.deinit(self.allocator); } pub fn reset(self: *LineBreakResult) void { @@ -78,16 +80,18 @@ pub const LineBreakResult = struct { }; pub const TabStopResult = struct { - positions: std.ArrayList(usize), + positions: std.ArrayListUnmanaged(usize), + allocator: std.mem.Allocator, pub fn init(allocator: std.mem.Allocator) TabStopResult { return .{ - .positions = std.ArrayList(usize).init(allocator), + .positions = .{}, + .allocator = allocator, }; } pub fn deinit(self: *TabStopResult) void { - self.positions.deinit(); + self.positions.deinit(self.allocator); } pub fn reset(self: *TabStopResult) void { @@ -101,16 +105,18 @@ pub const WrapBreak = struct { }; pub const WrapBreakResult = struct { - breaks: std.ArrayList(WrapBreak), + breaks: std.ArrayListUnmanaged(WrapBreak), + allocator: std.mem.Allocator, pub fn init(allocator: std.mem.Allocator) WrapBreakResult { return .{ - .breaks = std.ArrayList(WrapBreak).init(allocator), + .breaks = .{}, + .allocator = allocator, }; } pub fn deinit(self: *WrapBreakResult) void { - self.breaks.deinit(); + self.breaks.deinit(self.allocator); } pub fn reset(self: *WrapBreakResult) void { @@ -233,7 +239,7 @@ pub fn findWrapBreaks(text: []const u8, result: *WrapBreakResult, width_method: // Use bit manipulation to extract positions while (bitmask != 0) { const bit_pos = @ctz(bitmask); - try result.breaks.append(.{ + try result.breaks.append(result.allocator, .{ .byte_offset = @intCast(pos + bit_pos), .char_offset = char_offset + @as(u16, @intCast(bit_pos)), }); @@ -261,7 +267,7 @@ pub fn findWrapBreaks(text: []const u8, result: *WrapBreakResult, width_method: } else true; if (isAsciiWrapBreak(b0)) { - try result.breaks.append(.{ + try result.breaks.append(result.allocator, .{ .byte_offset = @intCast(pos + i), .char_offset = char_offset, }); @@ -283,7 +289,7 @@ pub fn findWrapBreaks(text: []const u8, result: *WrapBreakResult, width_method: } else true; if (isUnicodeWrapBreak(dec.cp)) { - try result.breaks.append(.{ + try result.breaks.append(result.allocator, .{ .byte_offset = @intCast(pos + i), .char_offset = char_offset, }); @@ -310,7 +316,7 @@ pub fn findWrapBreaks(text: []const u8, result: *WrapBreakResult, width_method: } else true; if (isAsciiWrapBreak(b0)) { - try result.breaks.append(.{ + try result.breaks.append(result.allocator, .{ .byte_offset = @intCast(i), .char_offset = char_offset, }); @@ -330,7 +336,7 @@ pub fn findWrapBreaks(text: []const u8, result: *WrapBreakResult, width_method: } else true; if (isUnicodeWrapBreak(dec.cp)) { - try result.breaks.append(.{ + try result.breaks.append(result.allocator, .{ .byte_offset = @intCast(i), .char_offset = char_offset, }); @@ -361,7 +367,7 @@ pub fn findTabStops(text: []const u8, result: *TabStopResult) !void { var i: usize = 0; while (i < vector_len) : (i += 1) { if (text[pos + i] == '\t') { - try result.positions.append(pos + i); + try result.positions.append(result.allocator, pos + i); } } } @@ -370,7 +376,7 @@ pub fn findTabStops(text: []const u8, result: *TabStopResult) !void { while (pos < text.len) : (pos += 1) { if (text[pos] == '\t') { - try result.positions.append(pos); + try result.positions.append(result.allocator, pos); } } } @@ -408,14 +414,14 @@ pub fn findLineBreaks(text: []const u8, result: *LineBreakResult) !void { } // Check if this is part of CRLF const kind: LineBreakKind = if (absolute_index > 0 and text[absolute_index - 1] == '\r') .CRLF else .LF; - try result.breaks.append(.{ .pos = absolute_index, .kind = kind }); + try result.breaks.append(result.allocator, .{ .pos = absolute_index, .kind = kind }); } else if (b == '\r') { // Check for CRLF if (absolute_index + 1 < text.len and text[absolute_index + 1] == '\n') { - try result.breaks.append(.{ .pos = absolute_index + 1, .kind = .CRLF }); + try result.breaks.append(result.allocator, .{ .pos = absolute_index + 1, .kind = .CRLF }); i += 1; // Skip the \n in next iteration } else { - try result.breaks.append(.{ .pos = absolute_index, .kind = .CR }); + try result.breaks.append(result.allocator, .{ .pos = absolute_index, .kind = .CR }); } } } @@ -440,13 +446,13 @@ pub fn findLineBreaks(text: []const u8, result: *LineBreakResult) !void { } } const kind: LineBreakKind = if (pos > 0 and text[pos - 1] == '\r') .CRLF else .LF; - try result.breaks.append(.{ .pos = pos, .kind = kind }); + try result.breaks.append(result.allocator, .{ .pos = pos, .kind = kind }); } else if (b == '\r') { if (pos + 1 < text.len and text[pos + 1] == '\n') { - try result.breaks.append(.{ .pos = pos + 1, .kind = .CRLF }); + try result.breaks.append(result.allocator, .{ .pos = pos + 1, .kind = .CRLF }); pos += 1; } else { - try result.breaks.append(.{ .pos = pos, .kind = .CR }); + try result.breaks.append(result.allocator, .{ .pos = pos, .kind = .CR }); } } prev_was_cr = false; @@ -1711,11 +1717,12 @@ pub fn findGraphemeInfo( tab_width: u8, isASCIIOnly: bool, width_method: WidthMethod, - result: *std.ArrayList(GraphemeInfo), + allocator: std.mem.Allocator, + result: *std.ArrayListUnmanaged(GraphemeInfo), ) !void { switch (width_method) { - .unicode, .no_zwj => try findGraphemeInfoUnicode(text, tab_width, isASCIIOnly, width_method, result), - .wcwidth => try findGraphemeInfoWCWidth(text, tab_width, isASCIIOnly, result), + .unicode, .no_zwj => try findGraphemeInfoUnicode(text, tab_width, isASCIIOnly, width_method, allocator, result), + .wcwidth => try findGraphemeInfoWCWidth(text, tab_width, isASCIIOnly, allocator, result), } } @@ -1726,7 +1733,8 @@ fn findGraphemeInfoUnicode( tab_width: u8, isASCIIOnly: bool, width_method: WidthMethod, - result: *std.ArrayList(GraphemeInfo), + allocator: std.mem.Allocator, + result: *std.ArrayListUnmanaged(GraphemeInfo), ) !void { if (isASCIIOnly) { return; @@ -1768,7 +1776,7 @@ fn findGraphemeInfoUnicode( if (prev_cp != null and (cluster_is_multibyte or cluster_is_tab)) { if (cluster_width_state.width > 0 or width_method != .wcwidth) { const cluster_byte_len = (pos + i) - cluster_start; - try result.append(GraphemeInfo{ + try result.append(allocator, GraphemeInfo{ .byte_offset = @intCast(cluster_start), .byte_len = @intCast(cluster_byte_len), .width = @intCast(cluster_width_state.width), @@ -1819,7 +1827,7 @@ fn findGraphemeInfoUnicode( if (prev_cp != null and (cluster_is_multibyte or cluster_is_tab)) { if (cluster_width_state.width > 0 or width_method != .wcwidth) { const cluster_byte_len = (pos + i) - cluster_start; - try result.append(GraphemeInfo{ + try result.append(allocator, GraphemeInfo{ .byte_offset = @intCast(cluster_start), .byte_len = @intCast(cluster_byte_len), .width = @intCast(cluster_width_state.width), @@ -1870,7 +1878,7 @@ fn findGraphemeInfoUnicode( if (prev_cp != null and (cluster_is_multibyte or cluster_is_tab)) { if (cluster_width_state.width > 0 or width_method != .wcwidth) { const cluster_byte_len = pos - cluster_start; - try result.append(GraphemeInfo{ + try result.append(allocator, GraphemeInfo{ .byte_offset = @intCast(cluster_start), .byte_len = @intCast(cluster_byte_len), .width = @intCast(cluster_width_state.width), @@ -1908,7 +1916,7 @@ fn findGraphemeInfoUnicode( if (prev_cp != null and (cluster_is_multibyte or cluster_is_tab)) { if (cluster_width_state.width > 0 or width_method != .wcwidth) { const cluster_byte_len = text.len - cluster_start; - try result.append(GraphemeInfo{ + try result.append(allocator, GraphemeInfo{ .byte_offset = @intCast(cluster_start), .byte_len = @intCast(cluster_byte_len), .width = @intCast(cluster_width_state.width), @@ -1924,7 +1932,8 @@ fn findGraphemeInfoWCWidth( text: []const u8, tab_width: u8, isASCIIOnly: bool, - result: *std.ArrayList(GraphemeInfo), + allocator: std.mem.Allocator, + result: *std.ArrayListUnmanaged(GraphemeInfo), ) !void { if (isASCIIOnly) { return; @@ -1955,7 +1964,7 @@ fn findGraphemeInfoWCWidth( const is_multibyte = (cp_len != 1); if ((is_multibyte or is_tab) and cp_width > 0) { - try result.append(GraphemeInfo{ + try result.append(allocator, GraphemeInfo{ .byte_offset = @intCast(pos), .byte_len = @intCast(cp_len), .width = @intCast(cp_width),