From 1ee9b813f9e655ed1d84894078fa3075b9c5eaa8 Mon Sep 17 00:00:00 2001 From: Simon Klee Date: Fri, 24 Jul 2026 14:24:19 +0200 Subject: [PATCH 1/4] zig: migrate native core to 0.16 --- .github/workflows/bench-native-smoke.yml | 2 +- .github/workflows/build-core.yml | 2 +- .github/workflows/build-keymap.yml | 12 +- .github/workflows/build-native.yml | 2 +- .github/workflows/build-qrcode.yml | 10 +- .github/workflows/build-react.yml | 6 +- .github/workflows/build-solid.yml | 18 +- .github/workflows/build-ssh.yml | 6 +- .github/workflows/npm-release.yml | 6 +- .github/workflows/pkg-pr-new.yml | 6 +- .zig-version | 2 +- packages/core/package.json | 2 +- packages/core/src/renderer.ts | 6 +- .../core/src/tests/renderer.remote.test.ts | 19 +- packages/core/src/zig/audio.zig | 228 +++++++-------- packages/core/src/zig/bench-utils.zig | 38 ++- packages/core/src/zig/bench.zig | 25 +- .../zig/bench/buffer-color-blending_bench.zig | 21 +- .../src/zig/bench/buffer-draw-box_bench.zig | 50 ++-- .../bench/buffer-draw-text-buffer_bench.zig | 94 +++--- .../core/src/zig/bench/edit-buffer_bench.zig | 39 +-- .../core/src/zig/bench/editor-view_bench.zig | 26 +- .../core/src/zig/bench/rope-markers_bench.zig | 64 +++-- packages/core/src/zig/bench/rope_bench.zig | 49 ++-- .../core/src/zig/bench/styled-text_bench.zig | 31 +- .../zig/bench/text-buffer-coords_bench.zig | 36 +-- .../src/zig/bench/text-buffer-view_bench.zig | 24 +- .../zig/bench/text-chunk-graphemes_bench.zig | 10 +- packages/core/src/zig/bench/utf8_bench.zig | 89 +++--- packages/core/src/zig/buffer.zig | 6 +- packages/core/src/zig/build.zig | 151 ++++++---- packages/core/src/zig/build.zig.zon | 6 +- packages/core/src/zig/edit-buffer.zig | 2 +- packages/core/src/zig/event-emitter.zig | 2 +- packages/core/src/zig/file-logger.zig | 37 +-- packages/core/src/zig/grapheme.zig | 6 +- packages/core/src/zig/lib.zig | 13 +- packages/core/src/zig/link.zig | 6 +- packages/core/src/zig/mem-registry.zig | 4 +- .../src/zig/native-span-feed-bench-lib.zig | 2 + packages/core/src/zig/renderer-output.zig | 216 +++++++++----- packages/core/src/zig/renderer.zig | 160 +++++------ packages/core/src/zig/rope.zig | 60 ++-- packages/core/src/zig/syntax-style.zig | 11 +- packages/core/src/zig/terminal.zig | 184 ++++++------ packages/core/src/zig/tests/audio_test.zig | 23 +- packages/core/src/zig/tests/buffer_test.zig | 16 +- .../core/src/zig/tests/editor-view_test.zig | 17 +- packages/core/src/zig/tests/grapheme_test.zig | 2 +- .../zig/tests/memory_leak_regression_test.zig | 4 +- packages/core/src/zig/tests/renderer_test.zig | 205 +++++++------- packages/core/src/zig/tests/rope_test.zig | 36 +-- packages/core/src/zig/tests/terminal_test.zig | 267 +++++++++--------- packages/core/src/zig/tests/test-renderer.zig | 14 +- .../zig/tests/text-buffer-drawing_test.zig | 70 +++-- .../zig/tests/text-buffer-iterators_test.zig | 6 +- .../zig/tests/text-buffer-selection_test.zig | 30 +- .../text-buffer-selection_viewport_test.zig | 12 +- .../src/zig/tests/text-buffer-view_test.zig | 26 +- .../core/src/zig/tests/text-buffer_test.zig | 24 +- .../core/src/zig/tests/unicode-width-map.zon | 2 +- .../core/src/zig/tests/utf8_no_zwj_test.zig | 4 +- packages/core/src/zig/tests/utf8_test.zig | 42 +-- .../zig/tests/utf8_wcwidth_cursor_test.zig | 6 +- .../core/src/zig/tests/utf8_wcwidth_test.zig | 22 +- .../src/zig/tests/wrap-cache-perf_test.zig | 4 +- packages/core/src/zig/text-buffer-segment.zig | 2 +- packages/core/src/zig/text-buffer-view.zig | 46 +-- packages/core/src/zig/text-buffer.zig | 31 +- packages/core/src/zig/utf8.zig | 12 +- packages/core/src/zig/yoga.zig | 13 +- 71 files changed, 1456 insertions(+), 1269 deletions(-) diff --git a/.github/workflows/bench-native-smoke.yml b/.github/workflows/bench-native-smoke.yml index 36133c11a8..f6031d4c8a 100644 --- a/.github/workflows/bench-native-smoke.yml +++ b/.github/workflows/bench-native-smoke.yml @@ -14,7 +14,7 @@ concurrency: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true BUN_VERSION: 1.3.14 - ZIG_VERSION: 0.15.2 + ZIG_VERSION: 0.16.0 jobs: bench-native-smoke: diff --git a/.github/workflows/build-core.yml b/.github/workflows/build-core.yml index 53ad59bf37..c52e9b66f8 100644 --- a/.github/workflows/build-core.yml +++ b/.github/workflows/build-core.yml @@ -16,7 +16,7 @@ concurrency: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true NODE_VERSION: 26.4.0 - ZIG_VERSION: 0.15.2 + ZIG_VERSION: 0.16.0 jobs: build: diff --git a/.github/workflows/build-keymap.yml b/.github/workflows/build-keymap.yml index 50615a1559..d852807a56 100644 --- a/.github/workflows/build-keymap.yml +++ b/.github/workflows/build-keymap.yml @@ -39,7 +39,7 @@ jobs: - name: Setup Zig uses: mlugg/setup-zig@v2 with: - version: 0.15.2 + version: 0.16.0 use-cache: false - name: Restore Zig cache @@ -47,8 +47,8 @@ jobs: uses: actions/cache@v5 with: path: .zig-cache - key: zig-v1-${{ runner.os }}-0.15.2-${{ github.run_id }}-${{ github.job }} - restore-keys: zig-v1-${{ runner.os }}-0.15.2- + key: zig-v1-${{ runner.os }}-0.16.0-${{ github.run_id }}-${{ github.job }} + restore-keys: zig-v1-${{ runner.os }}-0.16.0- - name: Install dependencies run: bun install @@ -91,15 +91,15 @@ jobs: - name: Setup Zig uses: mlugg/setup-zig@v2 with: - version: 0.15.2 + version: 0.16.0 use-cache: false - name: Restore Zig cache uses: actions/cache@v5 with: path: .zig-cache - key: zig-v1-${{ runner.os }}-0.15.2-${{ github.run_id }}-${{ github.job }} - restore-keys: zig-v1-${{ runner.os }}-0.15.2- + key: zig-v1-${{ runner.os }}-0.16.0-${{ github.run_id }}-${{ github.job }} + restore-keys: zig-v1-${{ runner.os }}-0.16.0- - name: Install dependencies run: bun install diff --git a/.github/workflows/build-native.yml b/.github/workflows/build-native.yml index 85e91b7717..6210263e4e 100644 --- a/.github/workflows/build-native.yml +++ b/.github/workflows/build-native.yml @@ -13,7 +13,7 @@ permissions: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - ZIG_VERSION: 0.15.2 + ZIG_VERSION: 0.16.0 jobs: build-native: diff --git a/.github/workflows/build-qrcode.yml b/.github/workflows/build-qrcode.yml index 70782a3efd..cfbdf84caf 100644 --- a/.github/workflows/build-qrcode.yml +++ b/.github/workflows/build-qrcode.yml @@ -15,9 +15,6 @@ concurrency: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - # Workaround for bug in Zig 0.15.2 (fixed in next version) - # https://github.com/ziglang/zig/issues/25805 - ZIG_GLOBAL_CACHE_DIR: ${{ github.workspace }}/.zig-global-cache jobs: test: @@ -41,16 +38,15 @@ jobs: - name: Setup Zig uses: mlugg/setup-zig@v2 with: - version: 0.15.2 - use-cache: false + version: 0.16.0 - name: Restore Zig cache if: runner.os != 'Windows' uses: actions/cache@v5 with: path: .zig-cache - key: zig-v1-${{ runner.os }}-0.15.2-${{ github.run_id }}-${{ github.job }} - restore-keys: zig-v1-${{ runner.os }}-0.15.2- + key: zig-v1-${{ runner.os }}-0.16.0-${{ github.run_id }}-${{ github.job }} + restore-keys: zig-v1-${{ runner.os }}-0.16.0- - name: Install dependencies run: bun install diff --git a/.github/workflows/build-react.yml b/.github/workflows/build-react.yml index 0de883cfbf..d8d08bff21 100644 --- a/.github/workflows/build-react.yml +++ b/.github/workflows/build-react.yml @@ -38,7 +38,7 @@ jobs: - name: Setup Zig uses: mlugg/setup-zig@v2 with: - version: 0.15.2 + version: 0.16.0 use-cache: false - name: Restore Zig cache @@ -46,8 +46,8 @@ jobs: uses: actions/cache@v5 with: path: .zig-cache - key: zig-v1-${{ runner.os }}-0.15.2-${{ github.run_id }}-${{ github.job }} - restore-keys: zig-v1-${{ runner.os }}-0.15.2- + key: zig-v1-${{ runner.os }}-0.16.0-${{ github.run_id }}-${{ github.job }} + restore-keys: zig-v1-${{ runner.os }}-0.16.0- - name: Install dependencies run: bun install diff --git a/.github/workflows/build-solid.yml b/.github/workflows/build-solid.yml index 2a566d6e9c..b7550cd153 100644 --- a/.github/workflows/build-solid.yml +++ b/.github/workflows/build-solid.yml @@ -39,7 +39,7 @@ jobs: - name: Setup Zig uses: mlugg/setup-zig@v2 with: - version: 0.15.2 + version: 0.16.0 use-cache: false - name: Restore Zig cache @@ -47,8 +47,8 @@ jobs: uses: actions/cache@v5 with: path: .zig-cache - key: zig-v1-${{ runner.os }}-0.15.2-${{ github.run_id }}-${{ github.job }} - restore-keys: zig-v1-${{ runner.os }}-0.15.2- + key: zig-v1-${{ runner.os }}-0.16.0-${{ github.run_id }}-${{ github.job }} + restore-keys: zig-v1-${{ runner.os }}-0.16.0- - name: Install dependencies run: bun install @@ -88,15 +88,15 @@ jobs: - name: Setup Zig uses: mlugg/setup-zig@v2 with: - version: 0.15.2 + version: 0.16.0 use-cache: false - name: Restore Zig cache uses: actions/cache@v5 with: path: .zig-cache - key: zig-v1-${{ runner.os }}-0.15.2-${{ github.run_id }}-${{ github.job }} - restore-keys: zig-v1-${{ runner.os }}-0.15.2- + key: zig-v1-${{ runner.os }}-0.16.0-${{ github.run_id }}-${{ github.job }} + restore-keys: zig-v1-${{ runner.os }}-0.16.0- - name: Install dependencies run: bun install @@ -136,15 +136,15 @@ jobs: - name: Setup Zig uses: mlugg/setup-zig@v2 with: - version: 0.15.2 + version: 0.16.0 use-cache: false - name: Restore Zig cache uses: actions/cache@v5 with: path: .zig-cache - key: zig-v1-${{ runner.os }}-0.15.2-${{ github.run_id }}-${{ github.job }} - restore-keys: zig-v1-${{ runner.os }}-0.15.2- + key: zig-v1-${{ runner.os }}-0.16.0-${{ github.run_id }}-${{ github.job }} + restore-keys: zig-v1-${{ runner.os }}-0.16.0- - name: Install dependencies run: bun install diff --git a/.github/workflows/build-ssh.yml b/.github/workflows/build-ssh.yml index 618fbfdf3d..547c633449 100644 --- a/.github/workflows/build-ssh.yml +++ b/.github/workflows/build-ssh.yml @@ -39,9 +39,9 @@ jobs: - name: Setup Zig uses: mlugg/setup-zig@v2 with: - version: 0.15.2 + version: 0.16.0 # Keep Zig's cache path on the workspace drive, but do not restore/save - # stale .zig-cache archives on Windows; Zig 0.15.2 can fail with missing cache files. + # stale .zig-cache archives on Windows can contain missing cache files. use-cache: ${{ runner.os != 'Windows' }} cache-key: build-ssh @@ -98,7 +98,7 @@ jobs: - name: Setup Zig uses: mlugg/setup-zig@v2 with: - version: 0.15.2 + version: 0.16.0 cache-key: build-ssh-packed - name: Install dependencies diff --git a/.github/workflows/npm-release.yml b/.github/workflows/npm-release.yml index 04fb0f038c..77d21cee61 100644 --- a/.github/workflows/npm-release.yml +++ b/.github/workflows/npm-release.yml @@ -32,15 +32,15 @@ jobs: - name: Setup Zig uses: mlugg/setup-zig@v2 with: - version: 0.15.2 + version: 0.16.0 use-cache: false - name: Restore Zig cache uses: actions/cache@v5 with: path: .zig-cache - key: zig-v1-${{ runner.os }}-0.15.2-${{ github.run_id }}-${{ github.job }} - restore-keys: zig-v1-${{ runner.os }}-0.15.2- + key: zig-v1-${{ runner.os }}-0.16.0-${{ github.run_id }}-${{ github.job }} + restore-keys: zig-v1-${{ runner.os }}-0.16.0- - name: Generate snapshot version id: version_scheme diff --git a/.github/workflows/pkg-pr-new.yml b/.github/workflows/pkg-pr-new.yml index 63b0d59834..c491556883 100644 --- a/.github/workflows/pkg-pr-new.yml +++ b/.github/workflows/pkg-pr-new.yml @@ -33,15 +33,15 @@ jobs: - name: Setup Zig uses: mlugg/setup-zig@v2 with: - version: 0.15.2 + version: 0.16.0 use-cache: false - name: Restore Zig cache uses: actions/cache@v5 with: path: .zig-cache - key: zig-v1-${{ runner.os }}-0.15.2-${{ github.run_id }}-${{ github.job }} - restore-keys: zig-v1-${{ runner.os }}-0.15.2- + key: zig-v1-${{ runner.os }}-0.16.0-${{ github.run_id }}-${{ github.job }} + restore-keys: zig-v1-${{ runner.os }}-0.16.0- - name: Install dependencies run: bun install diff --git a/.zig-version b/.zig-version index 4312e0d0ca..04a373efe6 100644 --- a/.zig-version +++ b/.zig-version @@ -1 +1 @@ -0.15.2 +0.16.0 diff --git a/packages/core/package.json b/packages/core/package.json index 3dceed5fb4..f48ea5caeb 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -45,7 +45,7 @@ "publish": "bun scripts/publish.ts", "test:dist": "bun scripts/dist-test.ts", "test:standalone": "bun scripts/standalone-test.ts", - "test:js": "bun test", + "test:js": "bun test --path-ignore-patterns='src/zig/zig-pkg/**'", "test:js:node": "bun scripts/test-node.ts", "test": "bun run test:native && bun run test:js" }, diff --git a/packages/core/src/renderer.ts b/packages/core/src/renderer.ts index 761eb3a7d5..48ff7eeeee 100644 --- a/packages/core/src/renderer.ts +++ b/packages/core/src/renderer.ts @@ -527,6 +527,10 @@ class ScrollbackSnapshotRenderContext extends EventEmitter implements RenderCont } const DEFAULT_FORWARDED_ENV_KEYS = [ + "SSH_CONNECTION", + "SSH_CLIENT", + "SSH_TTY", + "MOSH_CONNECTION", "TMUX", "ZELLIJ", "ZELLIJ_SESSION_NAME", @@ -1138,7 +1142,7 @@ export class CliRenderer extends EventEmitter implements RenderContext { this.clearOnShutdown = config.clearOnShutdown ?? true this.lib.setClearOnShutdown(this.rendererPtr, this.clearOnShutdown) - const forwardEnvKeys = config.forwardEnvKeys ?? (config.remote === false ? [...DEFAULT_FORWARDED_ENV_KEYS] : []) + const forwardEnvKeys = config.forwardEnvKeys ?? (remoteMode === true ? [] : [...DEFAULT_FORWARDED_ENV_KEYS]) for (const key of forwardEnvKeys) { const value = process.env[key] if (value === undefined) continue diff --git a/packages/core/src/tests/renderer.remote.test.ts b/packages/core/src/tests/renderer.remote.test.ts index 25b699d6f1..ae8ababec3 100644 --- a/packages/core/src/tests/renderer.remote.test.ts +++ b/packages/core/src/tests/renderer.remote.test.ts @@ -3,7 +3,7 @@ import { describe, expect, test } from "bun:test" type RendererFactory = "test" | "direct-process-memory" | "cli-custom-memory" | "cli-custom-feed" async function getCapabilitiesFromChild( - options: { remote?: boolean }, + options: { remote?: boolean; forwardEnvKeys?: string[] }, env: Record, factory: RendererFactory = "test", ): Promise { @@ -160,6 +160,23 @@ describe("remote detection", () => { expect(caps.terminal.name).toBe("") }) + test("explicit remote mode applies explicitly forwarded terminal overrides", async () => { + const caps = await getCapabilitiesFromChild( + { remote: true, forwardEnvKeys: ["TERM", "OPENTUI_FORCE_WCWIDTH"] }, + { + PATH: process.env.PATH ?? "", + HOME: process.env.HOME ?? "", + TMPDIR: process.env.TMPDIR ?? "/tmp", + TERM: "xterm-256color", + OPENTUI_FORCE_WCWIDTH: "1", + }, + ) + + expect(caps.remote).toBe(true) + expect(caps.ansi256).toBe(true) + expect(caps.unicode).toBe("wcwidth") + }) + test("process stdout with memory output preserves auto remote detection", async () => { const caps = await getCapabilitiesFromChild( {}, diff --git a/packages/core/src/zig/audio.zig b/packages/core/src/zig/audio.zig index c878353f2e..9fd85f7a44 100644 --- a/packages/core/src/zig/audio.zig +++ b/packages/core/src/zig/audio.zig @@ -1,7 +1,7 @@ const std = @import("std"); -const c = @cImport({ - @cInclude("vendor/miniaudio/miniaudio.h"); -}); +const builtin = @import("builtin"); +const c = @import("miniaudio"); +const io = if (builtin.is_test) std.testing.io else @import("root").io; pub const Status = struct { pub const ok: i32 = 0; @@ -285,8 +285,8 @@ const Stream = struct { input_read: usize = 0, input_write: usize = 0, input_count: usize = 0, - input_lock: std.Thread.Mutex = .{}, - input_condition: std.Thread.Condition = .{}, + input_lock: std.Io.Mutex = .init, + input_condition: std.Io.Condition = .init, // Keep EOF publication atomic so future decoder reads need not preserve a lock-only invariant. input_ended: u32 = 0, cancel_requested: u32 = 0, @@ -329,7 +329,7 @@ const SoundGroup = struct { pub const Engine = struct { allocator: std.mem.Allocator, started: bool = false, - lock: std.Thread.Mutex = .{}, + lock: std.Io.Mutex = .init, context: c.ma_context = undefined, context_initialized: bool = false, core: c.ma_engine = undefined, @@ -410,7 +410,7 @@ pub const Engine = struct { pub fn deinit(self: *Engine) void { stopCaptureDevice(self); - self.lock.lock(); + self.lock.lockUncancelable(io); if (self.has_device) { _ = c.ma_device_stop(&self.device); @@ -432,7 +432,7 @@ pub const Engine = struct { transitionStreamToTerminal(stream, StreamState.cancelled); requestStreamCancellation(stream); } - self.lock.unlock(); + self.lock.unlock(io); if (self.capture) |capture| { capture.destroy(); @@ -580,8 +580,8 @@ fn decoderExitRequested(stream: *const Stream) bool { } fn failDecoderWorker(stream: *Stream) void { - stream.input_lock.lock(); - defer stream.input_lock.unlock(); + stream.input_lock.lockUncancelable(io); + defer stream.input_lock.unlock(io); if (decoderExitRequested(stream)) return; failStream(stream); } @@ -591,10 +591,10 @@ fn endStreamPlayback(stream: *Stream) void { } fn requestStreamCancellation(stream: *Stream) void { - stream.input_lock.lock(); + stream.input_lock.lockUncancelable(io); @atomicStore(u32, &stream.cancel_requested, 1, .release); - stream.input_condition.broadcast(); - stream.input_lock.unlock(); + stream.input_condition.broadcast(io); + stream.input_lock.unlock(io); } fn destroyStreamStorage(stream: *Stream, out_final_stats: ?*StreamStats) void { @@ -765,8 +765,8 @@ fn streamDecoderRead( if (bytes_to_read == 0) return c.MA_SUCCESS; const output_ptr = buffer_out orelse return c.MA_INVALID_ARGS; - stream.input_lock.lock(); - defer stream.input_lock.unlock(); + stream.input_lock.lockUncancelable(io); + defer stream.input_lock.unlock(io); const output = @as([*]u8, @ptrCast(output_ptr))[0..bytes_to_read]; const target_read = @min(bytes_to_read, stream_decoder_read_granularity); @@ -782,7 +782,7 @@ fn streamDecoderRead( stream.decoder_abort = true; break; } - stream.input_condition.wait(&stream.input_lock); + stream.input_condition.waitUncancelable(io, &stream.input_lock); } if (decoderExitRequested(stream) or stream.decoder_abort) break; @@ -850,10 +850,10 @@ fn streamDecoderWorker(stream: *Stream) void { } defer _ = c.ma_decoder_uninit(&decoder); - stream.input_lock.lock(); + stream.input_lock.lockUncancelable(io); stream.probe_active = false; if (decoderExitRequested(stream)) { - stream.input_lock.unlock(); + stream.input_lock.unlock(io); return; } const state = loadStreamState(stream); @@ -864,13 +864,13 @@ fn streamDecoderWorker(stream: *Stream) void { } const ready_generation = @atomicLoad(u32, &stream.ready_generation, .acquire); @atomicStore(u32, &stream.ready_generation, if (ready_generation == std.math.maxInt(u32)) 1 else ready_generation + 1, .release); - stream.input_lock.unlock(); + stream.input_lock.unlock(io); while (!decoderExitRequested(stream)) { const writable = c.ma_pcm_rb_available_write(&stream.pcm_ring); if (writable == 0) { // Only the full-ring producer polls; this avoids synchronizing the realtime consumer. - std.Thread.sleep(std.time.ns_per_ms); + io.sleep(.fromMilliseconds(1), .awake) catch {}; continue; } @@ -902,17 +902,17 @@ fn streamDecoderWorker(stream: *Stream) void { return; } if (frames_read == 0) { - stream.input_lock.lock(); + stream.input_lock.lockUncancelable(io); if (decoderExitRequested(stream)) { - stream.input_lock.unlock(); + stream.input_lock.unlock(io); return; } if (@atomicLoad(u32, &stream.input_ended, .acquire) != 0) { @atomicStore(u32, &stream.decoder_finished, 1, .release); - stream.input_lock.unlock(); + stream.input_lock.unlock(io); return; } - stream.input_lock.unlock(); + stream.input_lock.unlock(io); failDecoderWorker(stream); return; } @@ -1262,15 +1262,15 @@ pub fn destroy(engine: *Engine) void { pub fn refreshPlaybackDevices(engine: *Engine) i32 { const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); return refreshPlaybackDevicesLocked(e); } pub fn getPlaybackDeviceCount(engine: *Engine) u32 { const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); return @intCast(e.playback_devices.items.len); } @@ -1278,8 +1278,8 @@ pub fn getPlaybackDeviceName(engine: *Engine, index: u32, out_ptr: ?[*]u8, max_l if (out_ptr == null) return 0; const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); const idx: usize = @intCast(index); if (idx >= e.playback_devices.items.len) return 0; @@ -1288,8 +1288,8 @@ pub fn getPlaybackDeviceName(engine: *Engine, index: u32, out_ptr: ?[*]u8, max_l pub fn isPlaybackDeviceDefault(engine: *Engine, index: u32) bool { const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); const idx: usize = @intCast(index); if (idx >= e.playback_devices.items.len) return false; @@ -1298,8 +1298,8 @@ pub fn isPlaybackDeviceDefault(engine: *Engine, index: u32) bool { pub fn selectPlaybackDevice(engine: *Engine, index: u32) i32 { const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); if (e.started) return Status.err_invalid; if (e.playback_devices.items.len == 0) { @@ -1321,8 +1321,8 @@ pub fn selectPlaybackDevice(engine: *Engine, index: u32) i32 { pub fn clearPlaybackDeviceSelection(engine: *Engine) void { const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); e.selected_playback_index = null; if (!e.started and e.has_device) { @@ -1529,16 +1529,16 @@ pub fn getCaptureStats(engine: *Engine, out_stats: ?*CaptureStats) i32 { pub fn start(engine: *Engine, options_ptr: ?*const StartOptions) i32 { const e = engine; const options = if (options_ptr) |opts| opts.* else StartOptions{}; - e.lock.lock(); + e.lock.lockUncancelable(io); if (e.started and e.has_device) { - e.lock.unlock(); + e.lock.unlock(io); return Status.ok; } if (!e.has_device) { const context_status = ensureContextInitialized(e); if (context_status != Status.ok) { - e.lock.unlock(); + e.lock.unlock(io); return context_status; } @@ -1547,14 +1547,14 @@ pub fn start(engine: *Engine, options_ptr: ?*const StartOptions) i32 { if (e.playback_devices.items.len == 0) { const refresh_status = refreshPlaybackDevicesLocked(e); if (refresh_status != Status.ok) { - e.lock.unlock(); + e.lock.unlock(io); return refresh_status; } } const idx: usize = @intCast(selected_index); if (idx >= e.playback_devices.items.len) { - e.lock.unlock(); + e.lock.unlock(io); return Status.err_not_found; } selected_device_id = &e.playback_devices.items[idx].id; @@ -1585,7 +1585,7 @@ pub fn start(engine: *Engine, options_ptr: ?*const StartOptions) i32 { const init_result = c.ma_device_init(&e.context, &config, &e.device); if (init_result != c.MA_SUCCESS) { - e.lock.unlock(); + e.lock.unlock(io); return Status.err_device; } @@ -1600,15 +1600,15 @@ pub fn start(engine: *Engine, options_ptr: ?*const StartOptions) i32 { // Device startup can fail after mixer-only mode is active; preserve that mode. const was_started = e.started; e.started = true; - e.lock.unlock(); + e.lock.unlock(io); const start_result = c.ma_device_start(&e.device); if (start_result != c.MA_SUCCESS) { - e.lock.lock(); + e.lock.lockUncancelable(io); e.started = was_started; c.ma_device_uninit(&e.device); e.has_device = false; - e.lock.unlock(); + e.lock.unlock(io); return Status.err_device; } @@ -1616,16 +1616,16 @@ pub fn start(engine: *Engine, options_ptr: ?*const StartOptions) i32 { } pub fn startMixer(engine: *Engine) i32 { - engine.lock.lock(); + engine.lock.lockUncancelable(io); engine.started = true; - engine.lock.unlock(); + engine.lock.unlock(io); return Status.ok; } pub fn stop(engine: *Engine) i32 { const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); e.started = false; if (e.has_device) { @@ -1693,8 +1693,8 @@ pub fn createStream(engine: *Engine, options_ptr: ?*const StreamOptions, out_str const e = engine; const preflight = blk: { - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); reapFinishedVoices(e); const group_index: usize = @intCast(options.group_id); @@ -1753,11 +1753,11 @@ pub fn createStream(engine: *Engine, options_ptr: ?*const StreamOptions, out_str } stream.source_ready = true; - e.lock.lock(); + e.lock.lockUncancelable(io); const data_source: *c.ma_data_source = @ptrCast(&stream.source); const sound_flags: c.ma_uint32 = c.MA_SOUND_FLAG_NO_SPATIALIZATION | c.MA_SOUND_FLAG_NO_PITCH; if (c.ma_sound_init_from_data_source(&e.core, data_source, sound_flags, &e.groups.items[preflight.group_index].node, &stream.sound) != c.MA_SUCCESS) { - e.lock.unlock(); + e.lock.unlock(io); destroyStreamStorage(stream, null); return Status.err_device; } @@ -1767,7 +1767,7 @@ pub fn createStream(engine: *Engine, options_ptr: ?*const StreamOptions, out_str if (c.ma_sound_start(&stream.sound) != c.MA_SUCCESS) { c.ma_sound_uninit(&stream.sound); stream.sound_ready = false; - e.lock.unlock(); + e.lock.unlock(io); destroyStreamStorage(stream, null); return Status.err_device; } @@ -1776,10 +1776,10 @@ pub fn createStream(engine: *Engine, options_ptr: ?*const StreamOptions, out_str e.streams[slot_index] = stream; const stream_id = streamIdForSlot(e, slot_index); e.updateActiveVoiceCount(); - e.lock.unlock(); + e.lock.unlock(io); stream.worker = std.Thread.spawn(.{}, streamDecoderWorker, .{stream}) catch { - e.lock.lock(); + e.lock.lockUncancelable(io); if (stream.sound_ready) { _ = c.ma_sound_stop(&stream.sound); c.ma_sound_uninit(&stream.sound); @@ -1787,7 +1787,7 @@ pub fn createStream(engine: *Engine, options_ptr: ?*const StreamOptions, out_str } retireStreamSlotLocked(e, slot_index); e.updateActiveVoiceCount(); - e.lock.unlock(); + e.lock.unlock(io); destroyStreamStorage(stream, null); return Status.err_no_space; }; @@ -1806,12 +1806,12 @@ pub fn writeStream( const e = engine; // This pins the stream slot through the bounded copy; full input returns zero instead of waiting. - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); const stream = getStream(e, stream_id) orelse return Status.err_not_found; - stream.input_lock.lock(); - defer stream.input_lock.unlock(); + stream.input_lock.lockUncancelable(io); + defer stream.input_lock.unlock(io); if (@atomicLoad(u32, &stream.input_ended, .acquire) != 0 or @atomicLoad(u32, &stream.cancel_requested, .acquire) != 0 or stream.decoder_abort or isTerminalStreamState(loadStreamState(stream))) @@ -1835,24 +1835,24 @@ pub fn writeStream( stream.input_write = (stream.input_write + write_count) % stream.input_buffer.len; stream.input_count += write_count; _ = @atomicRmw(u64, &stream.bytes_received, .Add, write_count, .monotonic); - stream.input_condition.signal(); + stream.input_condition.signal(io); return @intCast(write_count); } pub fn endStream(engine: *Engine, stream_id: u32) i32 { if (stream_id == 0) return Status.err_invalid; const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); const stream = getStream(e, stream_id) orelse return Status.err_not_found; - stream.input_lock.lock(); - defer stream.input_lock.unlock(); + stream.input_lock.lockUncancelable(io); + defer stream.input_lock.unlock(io); if (loadStreamState(stream) == StreamState.failed or @atomicLoad(u32, &stream.cancel_requested, .acquire) != 0) { return Status.err_invalid; } @atomicStore(u32, &stream.input_ended, 1, .release); - stream.input_condition.broadcast(); + stream.input_condition.broadcast(io); return Status.ok; } @@ -1860,39 +1860,39 @@ pub fn restartStream(engine: *Engine, stream_id: u32) i32 { if (stream_id == 0) return Status.err_invalid; const e = engine; - e.lock.lock(); + e.lock.lockUncancelable(io); const stream = getStream(e, stream_id) orelse { - e.lock.unlock(); + e.lock.unlock(io); return Status.err_not_found; }; - stream.input_lock.lock(); + stream.input_lock.lockUncancelable(io); const state = loadStreamState(stream); if (state == StreamState.failed or state == StreamState.cancelled or @atomicLoad(u32, &stream.cancel_requested, .acquire) != 0 or stream.worker == null) { - stream.input_lock.unlock(); - e.lock.unlock(); + stream.input_lock.unlock(io); + e.lock.unlock(io); return Status.err_invalid; } setStreamState(stream, StreamState.reconnecting); @atomicStore(u32, &stream.decoder_stop_requested, 1, .release); - stream.input_condition.broadcast(); - stream.input_lock.unlock(); + stream.input_condition.broadcast(io); + stream.input_lock.unlock(io); const worker = stream.worker.?; stream.worker = null; - e.lock.unlock(); + e.lock.unlock(io); worker.join(); - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); - stream.input_lock.lock(); + stream.input_lock.lockUncancelable(io); stream.input_read = 0; stream.input_write = 0; stream.input_count = 0; @@ -1903,7 +1903,7 @@ pub fn restartStream(engine: *Engine, stream_id: u32) i32 { stream.probe_bytes = 0; @atomicStore(i32, &stream.error_code, 0, .release); @atomicStore(u32, &stream.decoder_stop_requested, 0, .release); - stream.input_lock.unlock(); + stream.input_lock.unlock(io); if (state == StreamState.ended) { // start() clears atEnd only after its is-playing check; stop first so its @@ -1928,8 +1928,8 @@ pub fn restartStream(engine: *Engine, stream_id: u32) i32 { pub fn setStreamVolume(engine: *Engine, stream_id: u32, volume: f32) i32 { if (stream_id == 0) return Status.err_invalid; const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); const stream = getStream(e, stream_id) orelse return Status.err_not_found; c.ma_sound_set_volume(&stream.sound, clamp(volume, 0, 4)); @@ -1939,8 +1939,8 @@ pub fn setStreamVolume(engine: *Engine, stream_id: u32, volume: f32) i32 { pub fn setStreamPan(engine: *Engine, stream_id: u32, pan: f32) i32 { if (stream_id == 0) return Status.err_invalid; const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); const stream = getStream(e, stream_id) orelse return Status.err_not_found; c.ma_sound_set_pan(&stream.sound, clamp(pan, -1, 1)); @@ -1950,8 +1950,8 @@ pub fn setStreamPan(engine: *Engine, stream_id: u32, pan: f32) i32 { pub fn setStreamGroup(engine: *Engine, stream_id: u32, group_id: u32) i32 { if (stream_id == 0) return Status.err_invalid; const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); const stream = getStream(e, stream_id) orelse return Status.err_not_found; const group_index: usize = @intCast(group_id); @@ -1991,13 +1991,13 @@ pub fn getStreamStats(engine: *Engine, stream_id: u32, out_stats: ?*StreamStats) pub fn closeStream(engine: *Engine, stream_id: u32, reason: u32, out_final_stats: ?*StreamStats) i32 { if (stream_id == 0 or out_final_stats == null or reason > StreamCloseReason.disposed) return Status.err_invalid; const e = engine; - e.lock.lock(); + e.lock.lockUncancelable(io); const slot_index = streamSlotIndex(e, stream_id) orelse { - e.lock.unlock(); + e.lock.unlock(io); return Status.err_not_found; }; const stream = e.streams[slot_index] orelse { - e.lock.unlock(); + e.lock.unlock(io); return Status.err_not_found; }; @@ -2013,7 +2013,7 @@ pub fn closeStream(engine: *Engine, stream_id: u32, reason: u32, out_final_stats } retireStreamSlotLocked(e, slot_index); e.updateActiveVoiceCount(); - e.lock.unlock(); + e.lock.unlock(io); requestStreamCancellation(stream); destroyStreamStorage(stream, out_final_stats); @@ -2026,8 +2026,8 @@ pub fn load(engine: *Engine, data_ptr: ?[*]const u8, data_len: usize, out_sound_ const encoded_audio = @as([*]const u8, @ptrCast(data_ptr.?))[0..data_len]; const sound = decodeSoundFromMemory(e.allocator, encoded_audio) catch return Status.err_decode; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); e.sounds.append(e.allocator, sound) catch { e.allocator.free(sound.samples); @@ -2043,8 +2043,8 @@ pub fn unload(engine: *Engine, sound_id: u32) i32 { if (sound_id == 0) return Status.err_invalid; const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); const sound_index: usize = @intCast(sound_id - 1); if (sound_index >= e.sounds.items.len) return Status.err_not_found; @@ -2075,8 +2075,8 @@ pub fn createGroup(engine: *Engine, name_ptr: ?[*]const u8, name_len: usize, out const e = engine; const name = @as([*]const u8, @ptrCast(name_ptr.?))[0..name_len]; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); for (e.groups.items, 0..) |group, idx| { if (std.mem.eql(u8, group.name, name)) { @@ -2117,8 +2117,8 @@ pub fn play(engine: *Engine, sound_id: u32, options_ptr: ?*const VoiceOptions, o if (out_voice_id == null) return Status.err_invalid; const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); reapFinishedVoices(e); if (sound_id == 0 or sound_id > @as(u32, @intCast(e.sounds.items.len))) return Status.err_not_found; @@ -2190,8 +2190,8 @@ pub fn stopVoice(engine: *Engine, voice_id: u32) i32 { if (voice_id == 0) return Status.err_invalid; const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); const idx: usize = @intCast(voice_id - 1); if (idx >= e.voices.len) return Status.err_not_found; @@ -2207,8 +2207,8 @@ pub fn setVoiceGroup(engine: *Engine, voice_id: u32, group_id: u32) i32 { if (voice_id == 0) return Status.err_invalid; const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); const voice_index: usize = @intCast(voice_id - 1); const group_index: usize = @intCast(group_id); @@ -2252,8 +2252,8 @@ pub fn setVoiceGroup(engine: *Engine, voice_id: u32, group_id: u32) i32 { pub fn setGroupVolume(engine: *Engine, group_id: u32, volume: f32) i32 { const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); const group_index: usize = @intCast(group_id); if (group_index >= e.groups.items.len) return Status.err_invalid; @@ -2267,8 +2267,8 @@ pub fn setGroupVolume(engine: *Engine, group_id: u32, volume: f32) i32 { pub fn setMasterVolume(engine: *Engine, volume: f32) i32 { const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); const clamped = clamp(volume, 0, 4); const result = c.ma_engine_set_volume(&e.core, clamped); @@ -2280,8 +2280,8 @@ pub fn setMasterVolume(engine: *Engine, volume: f32) i32 { pub fn enableTap(engine: *Engine, enabled: bool, capacity_frames: u32) i32 { const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); if (!enabled) { e.tap_enabled = false; @@ -2318,8 +2318,8 @@ pub fn readTap(engine: *Engine, out_ptr: ?[*]f32, frame_count: u32, channels: u8 if (channels == 0) return Status.err_invalid; const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); const out = @as([*]f32, @ptrCast(out_ptr.?))[0 .. @as(usize, frame_count) * @as(usize, channels)]; @memset(out, 0); @@ -2391,7 +2391,7 @@ fn audioCallback(device_ptr: ?*c.ma_device, output_ptr: ?*anyopaque, input_ptr: incrementLockMisses(engine); return; } - defer engine.lock.unlock(); + defer engine.lock.unlock(io); if (!engine.started) { @memset(out, 0); @@ -2449,8 +2449,8 @@ pub fn mixToBuffer(engine: *Engine, out_ptr: ?[*]f32, frame_count: u32, channels if (channels == 0) return Status.err_invalid; const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); const out = @as([*]f32, @ptrCast(out_ptr.?))[0 .. @as(usize, frame_count) * @as(usize, channels)]; @memset(out, 0); @@ -2510,8 +2510,8 @@ pub fn getStats(engine: *Engine, out_stats: ?*Stats) i32 { if (out_stats == null) return Status.err_invalid; const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); e.stats.lock_misses = loadLockMisses(e); reapFinishedVoices(e); diff --git a/packages/core/src/zig/bench-utils.zig b/packages/core/src/zig/bench-utils.zig index 5233702990..b7a4dbdfad 100644 --- a/packages/core/src/zig/bench-utils.zig +++ b/packages/core/src/zig/bench-utils.zig @@ -84,15 +84,35 @@ pub const BenchStats = struct { } }; +pub const BenchTimer = struct { + io: std.Io, + start_timestamp: std.Io.Timestamp, + + pub fn start(io: std.Io) BenchTimer { + return .{ + .io = io, + .start_timestamp = std.Io.Clock.awake.now(io), + }; + } + + pub fn read(self: BenchTimer) u64 { + const end_timestamp = std.Io.Clock.awake.now(self.io); + const elapsed: std.Io.Duration = self.start_timestamp.durationTo(end_timestamp); + return @intCast(elapsed.toNanoseconds()); + } +}; + /// Helper for running benchmark iterations with timing pub const BenchRunner = struct { + io: std.Io, allocator: std.mem.Allocator, - results: std.ArrayListUnmanaged(BenchResult), + results: std.ArrayList(BenchResult), - pub fn init(allocator: std.mem.Allocator) BenchRunner { + pub fn init(io: std.Io, allocator: std.mem.Allocator) BenchRunner { return .{ + .io = io, .allocator = allocator, - .results = .{}, + .results = .empty, }; } @@ -127,7 +147,7 @@ pub const BenchRunner = struct { var stats: BenchStats = .{}; var iter: usize = 0; while (iter < iterations) : (iter += 1) { - var timer = try std.time.Timer.start(); + const timer = BenchTimer.start(self.io); @call(.auto, benchFn, args); stats.record(timer.read()); } @@ -148,11 +168,11 @@ pub const BenchRunner = struct { /// Create a stdout writer with buffer for benchmark output pub const StdoutWriter = struct { buffer: [4096]u8 = undefined, - writer: std.fs.File.Writer = undefined, + writer: std.Io.File.Writer = undefined, - pub fn init() StdoutWriter { + pub fn init(io: std.Io) StdoutWriter { var self: StdoutWriter = .{}; - self.writer = std.fs.File.stdout().writer(&self.buffer); + self.writer = std.Io.File.stdout().writerStreaming(io, &self.buffer); return self; } @@ -220,7 +240,7 @@ pub fn printResults(writer: anytype, results: []const BenchResult) !void { const allocator = arena.allocator(); // Collect all unique memory stat names - var mem_stat_names: std.ArrayListUnmanaged([]const u8) = .{}; + var mem_stat_names: std.ArrayList([]const u8) = .empty; for (results) |result| { if (result.mem_stats) |stats| { for (stats) |stat| { @@ -247,7 +267,7 @@ pub fn printResults(writer: anytype, results: []const BenchResult) !void { var rme_col_width: usize = 6; // minimum for "RME95%" // Create a map to store column widths for each memory stat - var mem_col_widths: std.ArrayListUnmanaged(usize) = .{}; + var mem_col_widths: std.ArrayList(usize) = .empty; for (mem_stat_names.items) |name| { try mem_col_widths.append(allocator, name.len); // minimum is the name length } diff --git a/packages/core/src/zig/bench.zig b/packages/core/src/zig/bench.zig index f520396c25..b865b5e273 100644 --- a/packages/core/src/zig/bench.zig +++ b/packages/core/src/zig/bench.zig @@ -35,7 +35,7 @@ // Adding New Benchmarks: // 1. Create a new file in bench/ directory (e.g., bench/my_bench.zig) // 2. Export `pub const benchName = "My Benchmark";` -// 3. Export `pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8) ![]BenchResult` +// 3. Export `pub fn run(io: std.Io, allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8) ![]BenchResult` // 4. Import the module at the top of this file // 5. Add an entry to the `benchmarks` array in main() with your module @@ -66,7 +66,7 @@ const renderer_output_bench = @import("bench/renderer-output_bench.zig"); const BenchModule = struct { name: []const u8, - run: *const fn (std.mem.Allocator, bool, ?[]const u8) anyerror![]bench_utils.BenchResult, + run: *const fn (std.Io, std.mem.Allocator, bool, ?[]const u8) anyerror![]bench_utils.BenchResult, }; fn matchesFilter(bench_name: []const u8, filter: ?[]const u8) bool { @@ -91,8 +91,9 @@ fn matchesFilter(bench_name: []const u8, filter: ?[]const u8) bool { return false; } -pub fn main() !void { - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; +pub fn main(init: std.process.Init) !void { + const io = init.io; + var gpa: std.heap.DebugAllocator(.{}) = .init; defer _ = gpa.deinit(); const allocator = gpa.allocator(); @@ -123,8 +124,9 @@ pub fn main() !void { .{ .name = renderer_output_bench.benchName, .run = renderer_output_bench.run }, }; - const args = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, args); + var args_arena = std.heap.ArenaAllocator.init(allocator); + defer args_arena.deinit(); + const args = try init.minimal.args.toSlice(args_arena.allocator()); var show_mem = false; var json_output = false; @@ -149,7 +151,7 @@ pub fn main() !void { } } else if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) { var stdout_buffer: [4096]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &stdout_buffer); const stdout = &stdout_writer.interface; try stdout.print("Usage: bench [options]\n\n", .{}); try stdout.print("Options:\n", .{}); @@ -168,7 +170,7 @@ pub fn main() !void { } var stdout_buffer: [4096]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &stdout_buffer); const stdout = &stdout_writer.interface; if (!json_output and filter != null) { @@ -187,10 +189,9 @@ pub fn main() !void { var results_arena = std.heap.ArenaAllocator.init(allocator); defer results_arena.deinit(); - const start_time = std.time.nanoTimestamp(); - const results = try bench.run(results_arena.allocator(), show_mem, bench_filter); - const end_time = std.time.nanoTimestamp(); - const elapsed_ns = end_time - start_time; + const timer = bench_utils.BenchTimer.start(io); + const results = try bench.run(io, results_arena.allocator(), show_mem, bench_filter); + const elapsed_ns = timer.read(); if (results.len == 0) continue; diff --git a/packages/core/src/zig/bench/buffer-color-blending_bench.zig b/packages/core/src/zig/bench/buffer-color-blending_bench.zig index a772daa218..19376f059a 100644 --- a/packages/core/src/zig/bench/buffer-color-blending_bench.zig +++ b/packages/core/src/zig/bench/buffer-color-blending_bench.zig @@ -68,13 +68,14 @@ fn setupTextBuffer( } fn runTranslucentBoxes( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, show_mem: bool, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const name_translucent_bg = "1k translucent boxes (bg alpha 0.5)"; @@ -97,7 +98,7 @@ fn runTranslucentBoxes( for (0..iterations) |i| { buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); var box_i: usize = 0; while (box_i < BOX_COUNT) : (box_i += 1) { const x: i32 = @intCast(@as(i32, @intCast(box_i % BUFFER_WIDTH))); @@ -154,7 +155,7 @@ fn runTranslucentBoxes( try buf.pushOpacity(0.5); errdefer buf.popOpacity(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); var box_i: usize = 0; while (box_i < BOX_COUNT) : (box_i += 1) { const x: i32 = @intCast(@as(i32, @intCast(box_i % BUFFER_WIDTH))); @@ -204,13 +205,14 @@ fn runTranslucentBoxes( } fn runTranslucentTextBuffers( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, show_mem: bool, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const name_translucent_bg = "1k translucent text buffer (bg alpha 0.5)"; @@ -242,7 +244,7 @@ fn runTranslucentTextBuffers( try tbs[j].setText(TEXT_CONTENT); } - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); var buf_i: usize = 0; while (buf_i < TEXT_COUNT) : (buf_i += 1) { const x: i32 = @intCast(@as(i32, @intCast(buf_i % BUFFER_WIDTH))); @@ -294,7 +296,7 @@ fn runTranslucentTextBuffers( try buf.pushOpacity(0.5); errdefer buf.popOpacity(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); var buf_i: usize = 0; while (buf_i < TEXT_COUNT) : (buf_i += 1) { const x: i32 = @intCast(@as(i32, @intCast(buf_i % BUFFER_WIDTH))); @@ -370,21 +372,22 @@ fn runTranslucentFillOverImageMarkers( } pub fn run( + io: std.Io, allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { const pool = gp.initGlobalPool(allocator); - var all_results: std.ArrayListUnmanaged(BenchResult) = .{}; + var all_results: std.ArrayList(BenchResult) = .empty; errdefer all_results.deinit(allocator); const iterations: usize = 10; - const boxes_results = try runTranslucentBoxes(allocator, pool, show_mem, iterations, bench_filter); + const boxes_results = try runTranslucentBoxes(io, allocator, pool, show_mem, iterations, bench_filter); try all_results.appendSlice(allocator, boxes_results); - const text_buffers_results = try runTranslucentTextBuffers(allocator, pool, show_mem, iterations, bench_filter); + const text_buffers_results = try runTranslucentTextBuffers(io, allocator, pool, show_mem, iterations, bench_filter); try all_results.appendSlice(allocator, text_buffers_results); const image_marker_results = try runTranslucentFillOverImageMarkers(allocator, pool, iterations, bench_filter); diff --git a/packages/core/src/zig/bench/buffer-draw-box_bench.zig b/packages/core/src/zig/bench/buffer-draw-box_bench.zig index bcc59adc03..93936c2cb7 100644 --- a/packages/core/src/zig/bench/buffer-draw-box_bench.zig +++ b/packages/core/src/zig/bench/buffer-draw-box_bench.zig @@ -42,13 +42,14 @@ fn rgba(r: f32, g: f32, b: f32, a: f32) buffer.RGBA { } fn runTransparentBoxes( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, show_mem: bool, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const name_opacity = "1k transparent boxes (opacity 0)"; @@ -76,7 +77,7 @@ fn runTransparentBoxes( try buf.pushOpacity(0.0); errdefer buf.popOpacity(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); var box_i: usize = 0; while (box_i < BOX_COUNT) : (box_i += 1) { const x: i32 = @intCast(@as(i32, @intCast(box_i % BUFFER_WIDTH))); @@ -131,7 +132,7 @@ fn runTransparentBoxes( for (0..iterations) |i| { buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); var box_i: usize = 0; while (box_i < BOX_COUNT) : (box_i += 1) { const x: i32 = @intCast(@as(i32, @intCast(box_i % BUFFER_WIDTH))); @@ -185,7 +186,7 @@ fn runTransparentBoxes( for (0..iterations) |i| { buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); var box_i: usize = 0; while (box_i < BOX_COUNT) : (box_i += 1) { const x: i32 = @intCast(@as(i32, @intCast(box_i % BUFFER_WIDTH))); @@ -235,13 +236,14 @@ fn runTransparentBoxes( } fn runFilledBoxes( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, show_mem: bool, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const name_opaque = "1k filled boxes (fully opaque)"; @@ -266,7 +268,7 @@ fn runFilledBoxes( for (0..iterations) |i| { buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); var box_i: usize = 0; while (box_i < BOX_COUNT) : (box_i += 1) { const x: i32 = @intCast(@as(i32, @intCast(box_i % BUFFER_WIDTH))); @@ -320,7 +322,7 @@ fn runFilledBoxes( for (0..iterations) |i| { buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); var box_i: usize = 0; while (box_i < BOX_COUNT) : (box_i += 1) { const x: i32 = @intCast(@as(i32, @intCast(box_i % BUFFER_WIDTH))); @@ -377,7 +379,7 @@ fn runFilledBoxes( try buf.pushOpacity(0.5); errdefer buf.popOpacity(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); var box_i: usize = 0; while (box_i < BOX_COUNT) : (box_i += 1) { const x: i32 = @intCast(@as(i32, @intCast(box_i % BUFFER_WIDTH))); @@ -427,13 +429,14 @@ fn runFilledBoxes( } fn runFilledBoxesTitle( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, show_mem: bool, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const name_title = "1k filled boxes (with title)"; @@ -453,7 +456,7 @@ fn runFilledBoxesTitle( for (0..iterations) |i| { buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); var box_i: usize = 0; while (box_i < BOX_COUNT) : (box_i += 1) { const x: i32 = @intCast(@as(i32, @intCast(box_i % BUFFER_WIDTH))); @@ -502,13 +505,14 @@ fn runFilledBoxesTitle( } fn runFilledBoxesBorders( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, show_mem: bool, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const name_noborders = "1k filled boxes (without borders)"; @@ -528,7 +532,7 @@ fn runFilledBoxesBorders( for (0..iterations) |i| { buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); var box_i: usize = 0; while (box_i < BOX_COUNT) : (box_i += 1) { const x: i32 = @intCast(@as(i32, @intCast(box_i % BUFFER_WIDTH))); @@ -577,13 +581,14 @@ fn runFilledBoxesBorders( } fn runFilledBoxesClipped( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, show_mem: bool, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const name_fully_clipped = "1k filled boxes (fully clipped)"; @@ -608,7 +613,7 @@ fn runFilledBoxesClipped( for (0..iterations) |i| { buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); var box_i: usize = 0; while (box_i < BOX_COUNT) : (box_i += 1) { try buf.drawBox( @@ -657,7 +662,7 @@ fn runFilledBoxesClipped( for (0..iterations) |i| { buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); var box_i: usize = 0; while (box_i < BOX_COUNT) : (box_i += 1) { const x: i32 = @intCast(@as(i32, @intCast(box_i % BUFFER_WIDTH))); @@ -708,7 +713,7 @@ fn runFilledBoxesClipped( for (0..iterations) |i| { buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); var box_i: usize = 0; while (box_i < BOX_COUNT) : (box_i += 1) { try buf.drawBox( @@ -756,30 +761,31 @@ fn runFilledBoxesClipped( } pub fn run( + io: std.Io, allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { const pool = gp.initGlobalPool(allocator); - var all_results: std.ArrayListUnmanaged(BenchResult) = .{}; + var all_results: std.ArrayList(BenchResult) = .empty; errdefer all_results.deinit(allocator); const iterations: usize = 10; - const transparent_results = try runTransparentBoxes(allocator, pool, show_mem, iterations, bench_filter); + const transparent_results = try runTransparentBoxes(io, allocator, pool, show_mem, iterations, bench_filter); try all_results.appendSlice(allocator, transparent_results); - const filled_results = try runFilledBoxes(allocator, pool, show_mem, iterations, bench_filter); + const filled_results = try runFilledBoxes(io, allocator, pool, show_mem, iterations, bench_filter); try all_results.appendSlice(allocator, filled_results); - const title_results = try runFilledBoxesTitle(allocator, pool, show_mem, iterations, bench_filter); + const title_results = try runFilledBoxesTitle(io, allocator, pool, show_mem, iterations, bench_filter); try all_results.appendSlice(allocator, title_results); - const partial_results = try runFilledBoxesBorders(allocator, pool, show_mem, iterations, bench_filter); + const partial_results = try runFilledBoxesBorders(io, allocator, pool, show_mem, iterations, bench_filter); try all_results.appendSlice(allocator, partial_results); - const clipped_results = try runFilledBoxesClipped(allocator, pool, show_mem, iterations, bench_filter); + const clipped_results = try runFilledBoxesClipped(io, allocator, pool, show_mem, iterations, bench_filter); try all_results.appendSlice(allocator, clipped_results); return all_results.toOwnedSlice(allocator); diff --git a/packages/core/src/zig/bench/buffer-draw-text-buffer_bench.zig b/packages/core/src/zig/bench/buffer-draw-text-buffer_bench.zig index 0091563d67..711393b634 100644 --- a/packages/core/src/zig/bench/buffer-draw-text-buffer_bench.zig +++ b/packages/core/src/zig/bench/buffer-draw-text-buffer_bench.zig @@ -24,7 +24,7 @@ fn rgba(r: f32, g: f32, b: f32, a: f32) buffer.RGBA { } fn generateText(allocator: std.mem.Allocator, lines: u32, avg_line_len: u32) ![]u8 { - var buf: std.ArrayListUnmanaged(u8) = .{}; + var buf: std.ArrayList(u8) = .empty; errdefer buf.deinit(allocator); const patterns = [_][]const u8{ @@ -49,7 +49,7 @@ fn generateText(allocator: std.mem.Allocator, lines: u32, avg_line_len: u32) ![] } fn generateManySmallChunks(allocator: std.mem.Allocator, chunks: u32) ![]u8 { - var buf: std.ArrayListUnmanaged(u8) = .{}; + var buf: std.ArrayList(u8) = .empty; errdefer buf.deinit(allocator); for (0..chunks) |i| { @@ -86,13 +86,14 @@ fn setupTextBuffer( } fn benchRenderColdCache( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, iterations: usize, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const name = "COLD: 120x40 render (500 lines, wrap=120, includes setup)"; @@ -114,7 +115,7 @@ fn benchRenderColdCache( buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); buf.drawTextBuffer(view, 0, 0); stats.record(timer.read()); @@ -143,13 +144,14 @@ fn benchRenderColdCache( } fn benchWrapAndRender( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, iterations: usize, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const name = "WRAP+RENDER: 120x40 render (500 lines, wrap=120)"; @@ -173,7 +175,7 @@ fn benchWrapAndRender( buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); buf.drawTextBuffer(view, 0, 0); stats.record(timer.read()); @@ -206,13 +208,14 @@ fn benchWrapAndRender( } fn benchRenderWarmCache( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, iterations: usize, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const warm_name = "WARM: 120x40 render (500 lines, pre-wrapped, pure render)"; @@ -238,7 +241,7 @@ fn benchRenderWarmCache( buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); buf.drawTextBuffer(view, 0, 0); stats.record(timer.read()); @@ -277,7 +280,7 @@ fn benchRenderWarmCache( for (0..iterations) |_| { buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); buf.drawTextBuffer(view, 0, 0); stats.record(timer.read()); } @@ -297,13 +300,14 @@ fn benchRenderWarmCache( } fn benchRenderSmallResolution( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, iterations: usize, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const no_wrap_name = "80x24 render (100 lines, no wrap)"; @@ -329,7 +333,7 @@ fn benchRenderSmallResolution( for (0..iterations) |i| { buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); buf.drawTextBuffer(view, 0, 0); stats.record(timer.read()); @@ -368,7 +372,7 @@ fn benchRenderSmallResolution( for (0..iterations) |_| { buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); buf.drawTextBuffer(view, 0, 0); stats.record(timer.read()); } @@ -388,13 +392,14 @@ fn benchRenderSmallResolution( } fn benchRenderMediumResolution( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, iterations: usize, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const name = "200x60 render (1000 lines, wrap=200)"; @@ -416,7 +421,7 @@ fn benchRenderMediumResolution( for (0..iterations) |i| { buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); buf.drawTextBuffer(view, 0, 0); stats.record(timer.read()); @@ -445,13 +450,14 @@ fn benchRenderMediumResolution( } fn benchRenderMassiveResolution( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, iterations: usize, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const name = "400x200 render (10k lines, wrap=400)"; @@ -473,7 +479,7 @@ fn benchRenderMassiveResolution( for (0..iterations) |i| { buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); buf.drawTextBuffer(view, 0, 0); stats.record(timer.read()); @@ -502,13 +508,14 @@ fn benchRenderMassiveResolution( } fn benchRenderMassiveLines( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, iterations: usize, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const name = "120x40 render (50k lines, viewport first 40)"; @@ -530,7 +537,7 @@ fn benchRenderMassiveLines( for (0..iterations) |i| { buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); buf.drawTextBuffer(view, 0, 0); stats.record(timer.read()); @@ -559,19 +566,20 @@ fn benchRenderMassiveLines( } fn benchRenderOneMassiveLine( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, iterations: usize, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const name = "80x30 render (1 massive line 500KB, wrap=80)"; if (!bench_utils.matchesBenchFilter(name, bench_filter)) return results.toOwnedSlice(allocator); - var buf_builder: std.ArrayListUnmanaged(u8) = .{}; + var buf_builder: std.ArrayList(u8) = .empty; defer buf_builder.deinit(allocator); for (0..100000) |_| { @@ -593,7 +601,7 @@ fn benchRenderOneMassiveLine( for (0..iterations) |i| { buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); buf.drawTextBuffer(view, 0, 0); stats.record(timer.read()); @@ -622,13 +630,14 @@ fn benchRenderOneMassiveLine( } fn benchRenderManySmallChunks( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, iterations: usize, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const name = "80x30 render (10k tiny chunks)"; @@ -650,7 +659,7 @@ fn benchRenderManySmallChunks( for (0..iterations) |i| { buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); buf.drawTextBuffer(view, 0, 0); stats.record(timer.read()); @@ -679,13 +688,14 @@ fn benchRenderManySmallChunks( } fn benchRenderWithViewport( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, iterations: usize, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); _ = show_mem; @@ -713,7 +723,7 @@ fn benchRenderWithViewport( for (0..iterations) |_| { buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); buf.drawTextBuffer(view, 0, 0); stats.record(timer.read()); } @@ -742,7 +752,7 @@ fn benchRenderWithViewport( for (0..iterations) |_| { buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); buf.drawTextBuffer(view, 0, 0); stats.record(timer.read()); } @@ -762,13 +772,14 @@ fn benchRenderWithViewport( } fn benchRenderWithSelection( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, iterations: usize, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); _ = show_mem; @@ -796,7 +807,7 @@ fn benchRenderWithSelection( for (0..iterations) |_| { buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); buf.drawTextBuffer(view, 0, 0); stats.record(timer.read()); } @@ -825,7 +836,7 @@ fn benchRenderWithSelection( for (0..iterations) |_| { buf.clear(CLEAR_BG, null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); buf.drawTextBuffer(view, 0, 0); stats.record(timer.read()); } @@ -845,6 +856,7 @@ fn benchRenderWithSelection( } pub fn run( + io: std.Io, allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8, @@ -852,42 +864,42 @@ pub fn run( // Global pool and unicode data are initialized once in bench.zig const pool = gp.initGlobalPool(allocator); - var all_results: std.ArrayListUnmanaged(BenchResult) = .{}; + var all_results: std.ArrayList(BenchResult) = .empty; errdefer all_results.deinit(allocator); const iterations: usize = 10; - const cold_cache_results = try benchRenderColdCache(allocator, pool, iterations, show_mem, bench_filter); + const cold_cache_results = try benchRenderColdCache(io, allocator, pool, iterations, show_mem, bench_filter); try all_results.appendSlice(allocator, cold_cache_results); - const warm_cache_results = try benchRenderWarmCache(allocator, pool, iterations, show_mem, bench_filter); + const warm_cache_results = try benchRenderWarmCache(io, allocator, pool, iterations, show_mem, bench_filter); try all_results.appendSlice(allocator, warm_cache_results); - const wrap_render_results = try benchWrapAndRender(allocator, pool, iterations, show_mem, bench_filter); + const wrap_render_results = try benchWrapAndRender(io, allocator, pool, iterations, show_mem, bench_filter); try all_results.appendSlice(allocator, wrap_render_results); - const small_res_results = try benchRenderSmallResolution(allocator, pool, iterations, show_mem, bench_filter); + const small_res_results = try benchRenderSmallResolution(io, allocator, pool, iterations, show_mem, bench_filter); try all_results.appendSlice(allocator, small_res_results); - const medium_res_results = try benchRenderMediumResolution(allocator, pool, iterations, show_mem, bench_filter); + const medium_res_results = try benchRenderMediumResolution(io, allocator, pool, iterations, show_mem, bench_filter); try all_results.appendSlice(allocator, medium_res_results); - const massive_res_results = try benchRenderMassiveResolution(allocator, pool, iterations, show_mem, bench_filter); + const massive_res_results = try benchRenderMassiveResolution(io, allocator, pool, iterations, show_mem, bench_filter); try all_results.appendSlice(allocator, massive_res_results); - const massive_lines_results = try benchRenderMassiveLines(allocator, pool, iterations, show_mem, bench_filter); + const massive_lines_results = try benchRenderMassiveLines(io, allocator, pool, iterations, show_mem, bench_filter); try all_results.appendSlice(allocator, massive_lines_results); - const one_massive_line_results = try benchRenderOneMassiveLine(allocator, pool, iterations, show_mem, bench_filter); + const one_massive_line_results = try benchRenderOneMassiveLine(io, allocator, pool, iterations, show_mem, bench_filter); try all_results.appendSlice(allocator, one_massive_line_results); - const many_chunks_results = try benchRenderManySmallChunks(allocator, pool, iterations, show_mem, bench_filter); + const many_chunks_results = try benchRenderManySmallChunks(io, allocator, pool, iterations, show_mem, bench_filter); try all_results.appendSlice(allocator, many_chunks_results); - const viewport_results = try benchRenderWithViewport(allocator, pool, iterations, show_mem, bench_filter); + const viewport_results = try benchRenderWithViewport(io, allocator, pool, iterations, show_mem, bench_filter); try all_results.appendSlice(allocator, viewport_results); - const selection_results = try benchRenderWithSelection(allocator, pool, iterations, show_mem, bench_filter); + const selection_results = try benchRenderWithSelection(io, allocator, pool, iterations, show_mem, bench_filter); try all_results.appendSlice(allocator, selection_results); return all_results.toOwnedSlice(allocator); diff --git a/packages/core/src/zig/bench/edit-buffer_bench.zig b/packages/core/src/zig/bench/edit-buffer_bench.zig index f6ff551afe..31c277ccf3 100644 --- a/packages/core/src/zig/bench/edit-buffer_bench.zig +++ b/packages/core/src/zig/bench/edit-buffer_bench.zig @@ -12,13 +12,14 @@ const MemStat = bench_utils.MemStat; pub const benchName = "EditBuffer Operations"; fn benchInsertOperations( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, iterations: usize, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const link_pool = link.initGlobalLinkPool(allocator); @@ -34,7 +35,7 @@ fn benchInsertOperations( defer eb.deinit(); const text = "Hello, world! "; - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..1000) |_| { try eb.insertText(text); try eb.setCursor(0, 0); @@ -76,7 +77,7 @@ fn benchInsertOperations( defer eb.deinit(); const text = "Line 1\nLine 2\nLine 3\n"; - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..500) |_| { try eb.insertText(text); } @@ -109,13 +110,14 @@ fn benchInsertOperations( } fn benchDeleteOperations( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, iterations: usize, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const link_pool = link.initGlobalLinkPool(allocator); @@ -136,7 +138,7 @@ fn benchDeleteOperations( try eb.insertText(text); } - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..500) |_| { try eb.backspace(); } @@ -182,7 +184,7 @@ fn benchDeleteOperations( try eb.insertText(text); } - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); // Delete across 50 lines try eb.deleteRange(.{ .row = 10, .col = 0 }, .{ .row = 60, .col = 0 }); stats.record(timer.read()); @@ -214,13 +216,14 @@ fn benchDeleteOperations( } fn benchMixedOperations( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, iterations: usize, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const link_pool = link.initGlobalLinkPool(allocator); @@ -235,7 +238,7 @@ fn benchMixedOperations( var eb = try EditBuffer.init(allocator, pool, link_pool, .unicode, null); defer eb.deinit(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); // Type some text for (0..100) |_| { @@ -280,13 +283,14 @@ fn benchMixedOperations( } fn benchWordBoundaryOperations( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, iterations: usize, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const link_pool = link.initGlobalLinkPool(allocator); @@ -309,7 +313,7 @@ fn benchWordBoundaryOperations( try eb.setCursor(0, 0); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); // Navigate through 1000 word boundaries for (0..1000) |_| { const cursor = eb.getNextWordBoundary(); @@ -362,7 +366,7 @@ fn benchWordBoundaryOperations( const last_line = if (line_count > 0) line_count - 1 else 0; try eb.setCursor(last_line, 4500); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); // Navigate backward through 1000 word boundaries for (0..1000) |_| { const cursor = eb.getPrevWordBoundary(); @@ -412,7 +416,7 @@ fn benchWordBoundaryOperations( try eb.setCursor(0, 0); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); // Navigate through 500 word boundaries across lines for (0..500) |_| { const cursor = eb.getNextWordBoundary(); @@ -447,6 +451,7 @@ fn benchWordBoundaryOperations( } pub fn run( + io: std.Io, allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8, @@ -454,22 +459,22 @@ pub fn run( // Global pool and unicode data are initialized once in bench.zig const pool = gp.initGlobalPool(allocator); - var all_results: std.ArrayListUnmanaged(BenchResult) = .{}; + var all_results: std.ArrayList(BenchResult) = .empty; errdefer all_results.deinit(allocator); const iterations: usize = 5; // Run all benchmark categories and filter results - const insert_results = try benchInsertOperations(allocator, pool, iterations, show_mem, bench_filter); + const insert_results = try benchInsertOperations(io, allocator, pool, iterations, show_mem, bench_filter); try all_results.appendSlice(allocator, insert_results); - const delete_results = try benchDeleteOperations(allocator, pool, iterations, show_mem, bench_filter); + const delete_results = try benchDeleteOperations(io, allocator, pool, iterations, show_mem, bench_filter); try all_results.appendSlice(allocator, delete_results); - const mixed_results = try benchMixedOperations(allocator, pool, iterations, show_mem, bench_filter); + const mixed_results = try benchMixedOperations(io, allocator, pool, iterations, show_mem, bench_filter); try all_results.appendSlice(allocator, mixed_results); - const word_boundary_results = try benchWordBoundaryOperations(allocator, pool, iterations, show_mem, bench_filter); + const word_boundary_results = try benchWordBoundaryOperations(io, allocator, pool, iterations, show_mem, bench_filter); try all_results.appendSlice(allocator, word_boundary_results); return all_results.toOwnedSlice(allocator); diff --git a/packages/core/src/zig/bench/editor-view_bench.zig b/packages/core/src/zig/bench/editor-view_bench.zig index 41c2e3946d..fb37e291e2 100644 --- a/packages/core/src/zig/bench/editor-view_bench.zig +++ b/packages/core/src/zig/bench/editor-view_bench.zig @@ -14,7 +14,7 @@ pub const benchName = "EditorView Visual Navigation"; const calls_per_iteration = 10_000; -fn appendResult(allocator: std.mem.Allocator, results: *std.ArrayListUnmanaged(BenchResult), name: []const u8, stats: BenchStats) !void { +fn appendResult(allocator: std.mem.Allocator, results: *std.ArrayList(BenchResult), name: []const u8, stats: BenchStats) !void { try results.append(allocator, .{ .name = name, .min_ns = stats.min_ns, @@ -40,12 +40,13 @@ fn keepAlive(checksum: u64) !void { } fn benchMoveDownBoundary( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, link_pool: *link.LinkPool, iterations: usize, bench_filter: ?[]const u8, - results: *std.ArrayListUnmanaged(BenchResult), + results: *std.ArrayList(BenchResult), ) !void { const name = "moveDownVisual wrapped boundary: 10k calls"; if (!bench_utils.matchesBenchFilter(name, bench_filter)) return; @@ -62,7 +63,7 @@ fn benchMoveDownBoundary( var stats: BenchStats = .{}; var checksum: u64 = 0; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..calls_per_iteration) |_| { resetPrimaryCursor(eb, 0, 10, 10); ev.desired_visual_col = null; @@ -77,12 +78,13 @@ fn benchMoveDownBoundary( } fn benchMoveUpBoundary( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, link_pool: *link.LinkPool, iterations: usize, bench_filter: ?[]const u8, - results: *std.ArrayListUnmanaged(BenchResult), + results: *std.ArrayList(BenchResult), ) !void { const name = "moveUpVisual wrapped boundary: 10k calls"; if (!bench_utils.matchesBenchFilter(name, bench_filter)) return; @@ -100,7 +102,7 @@ fn benchMoveUpBoundary( var stats: BenchStats = .{}; var checksum: u64 = 0; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..calls_per_iteration) |_| { resetPrimaryCursor(eb, 0, text.len, @intCast(text.len)); ev.desired_visual_col = null; @@ -115,12 +117,13 @@ fn benchMoveUpBoundary( } fn benchVisualEOLBoundary( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, link_pool: *link.LinkPool, iterations: usize, bench_filter: ?[]const u8, - results: *std.ArrayListUnmanaged(BenchResult), + results: *std.ArrayList(BenchResult), ) !void { const name = "getVisualEOL wrapped boundary: 10k calls"; if (!bench_utils.matchesBenchFilter(name, bench_filter)) return; @@ -138,7 +141,7 @@ fn benchVisualEOLBoundary( var stats: BenchStats = .{}; var checksum: u64 = 0; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..calls_per_iteration) |_| { const cursor = ev.getVisualEOL(); checksum +%= cursor.visual_row + cursor.visual_col + cursor.logical_col; @@ -150,6 +153,7 @@ fn benchVisualEOLBoundary( } pub fn run( + io: std.Io, allocator: std.mem.Allocator, _: bool, bench_filter: ?[]const u8, @@ -157,13 +161,13 @@ pub fn run( const pool = gp.initGlobalPool(allocator); const link_pool = link.initGlobalLinkPool(allocator); - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const iterations: usize = 20; - try benchMoveDownBoundary(allocator, pool, link_pool, iterations, bench_filter, &results); - try benchMoveUpBoundary(allocator, pool, link_pool, iterations, bench_filter, &results); - try benchVisualEOLBoundary(allocator, pool, link_pool, iterations, bench_filter, &results); + try benchMoveDownBoundary(io, allocator, pool, link_pool, iterations, bench_filter, &results); + try benchMoveUpBoundary(io, allocator, pool, link_pool, iterations, bench_filter, &results); + try benchVisualEOLBoundary(io, allocator, pool, link_pool, iterations, bench_filter, &results); return results.toOwnedSlice(allocator); } diff --git a/packages/core/src/zig/bench/rope-markers_bench.zig b/packages/core/src/zig/bench/rope-markers_bench.zig index 5fe3f269fc..d55d0c8a94 100644 --- a/packages/core/src/zig/bench/rope-markers_bench.zig +++ b/packages/core/src/zig/bench/rope-markers_bench.zig @@ -51,7 +51,7 @@ const RopeType = rope_mod.Rope(Token); /// Create a rope with specific marker density /// marker_every: insert a marker every N text tokens fn createRope(allocator: std.mem.Allocator, text_count: u32, marker_every: u32) !RopeType { - var tokens: std.ArrayListUnmanaged(Token) = .{}; + var tokens: std.ArrayList(Token) = .empty; defer tokens.deinit(allocator); for (0..text_count) |i| { @@ -65,11 +65,12 @@ fn createRope(allocator: std.mem.Allocator, text_count: u32, marker_every: u32) } fn benchRebuildMarkerIndex( + io: std.Io, allocator: std.mem.Allocator, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); // Small rope, high marker density (every 10 tokens) @@ -81,7 +82,7 @@ fn benchRebuildMarkerIndex( var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); const rope = try createRope(arena.allocator(), 1000, 10); _ = rope; // Markers are automatically indexed during rope creation stats.record(timer.read()); @@ -108,7 +109,7 @@ fn benchRebuildMarkerIndex( var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); const rope = try createRope(arena.allocator(), 1000, 100); _ = rope; // Markers are automatically indexed during rope creation stats.record(timer.read()); @@ -135,7 +136,7 @@ fn benchRebuildMarkerIndex( var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); const rope = try createRope(arena.allocator(), 10000, 10); _ = rope; // Markers are automatically indexed during rope creation stats.record(timer.read()); @@ -162,7 +163,7 @@ fn benchRebuildMarkerIndex( var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); const rope = try createRope(arena.allocator(), 10000, 100); _ = rope; // Markers are automatically indexed during rope creation stats.record(timer.read()); @@ -189,7 +190,7 @@ fn benchRebuildMarkerIndex( var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); const rope = try createRope(arena.allocator(), 50000, 50); _ = rope; // Markers are automatically indexed during rope creation stats.record(timer.read()); @@ -216,7 +217,7 @@ fn benchRebuildMarkerIndex( var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); const rope = try createRope(arena.allocator(), 100000, 200); _ = rope; // Markers are automatically indexed during rope creation stats.record(timer.read()); @@ -238,11 +239,12 @@ fn benchRebuildMarkerIndex( } fn benchMarkerLookup( + io: std.Io, allocator: std.mem.Allocator, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); // O(1) lookup in small rope @@ -257,7 +259,7 @@ fn benchMarkerLookup( var rope = try createRope(arena.allocator(), 1000, 10); // Markers are automatically indexed in the tree structure - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..100) |i| { _ = rope.getMarker(.marker, @intCast(i % rope.markerCount(.marker))); } @@ -288,7 +290,7 @@ fn benchMarkerLookup( var rope = try createRope(arena.allocator(), 10000, 50); // Markers are automatically indexed in the tree structure - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..1000) |i| { _ = rope.getMarker(.marker, @intCast(i % rope.markerCount(.marker))); } @@ -323,7 +325,7 @@ fn benchMarkerLookup( var prng = std.Random.DefaultPrng.init(42); const random = prng.random(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..10000) |_| { const line = random.intRangeAtMost(u32, 0, marker_count - 1); _ = rope.getMarker(.marker, line); @@ -356,7 +358,7 @@ fn benchMarkerLookup( // Markers are automatically indexed in the tree structure const marker_count = rope.markerCount(.marker); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..marker_count) |i| { _ = rope.getMarker(.marker, @intCast(i)); } @@ -379,11 +381,12 @@ fn benchMarkerLookup( } fn benchMarkerCount( + io: std.Io, allocator: std.mem.Allocator, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); // Count markers - should be O(1) hash lookup @@ -398,7 +401,7 @@ fn benchMarkerCount( var rope = try createRope(arena.allocator(), 10000, 50); // Markers are automatically indexed in the tree structure - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..100000) |_| { _ = rope.markerCount(.marker); } @@ -421,11 +424,12 @@ fn benchMarkerCount( } fn benchDepthVsPerformance( + io: std.Io, allocator: std.mem.Allocator, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); // Shallow tree (from_slice creates balanced tree) @@ -437,7 +441,7 @@ fn benchDepthVsPerformance( var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); const rope = try createRope(arena.allocator(), 10000, 50); _ = rope; // Markers are automatically indexed during rope creation stats.record(timer.read()); @@ -473,7 +477,7 @@ fn benchDepthVsPerformance( } } - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); // Markers are automatically indexed in the tree structure stats.record(timer.read()); } @@ -494,11 +498,12 @@ fn benchDepthVsPerformance( } fn benchEditThenRebuild( + io: std.Io, allocator: std.mem.Allocator, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); // Typical edit workflow: build, edit, rebuild @@ -513,7 +518,7 @@ fn benchEditThenRebuild( var rope = try createRope(arena.allocator(), 10000, 50); // Markers are automatically indexed in the tree structure - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); // Simulate typing at line 50 const line50_marker = rope.getMarker(.marker, 50).?; const insert_pos = line50_marker.leaf_index + 1; @@ -552,7 +557,7 @@ fn benchEditThenRebuild( var rope = try createRope(arena.allocator(), 10000, 50); // Markers are automatically indexed in the tree structure - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); // Insert new line (marker) at position 100 try rope.insert(100, .{ .marker = {} }); // Markers are automatically indexed in the tree structure @@ -583,7 +588,7 @@ fn benchEditThenRebuild( var rope = try createRope(arena.allocator(), 10000, 50); // Markers are automatically indexed in the tree structure - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); // Delete marker at position const marker_pos = rope.getMarker(.marker, 50).?.leaf_index; try rope.delete(marker_pos); @@ -611,7 +616,7 @@ fn benchMemoryUsage( iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); // Memory comparison: with vs without marker index @@ -674,35 +679,36 @@ fn benchMemoryUsage( } pub fn run( + io: std.Io, allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { _ = show_mem; - var all_results: std.ArrayListUnmanaged(BenchResult) = .{}; + var all_results: std.ArrayList(BenchResult) = .empty; errdefer all_results.deinit(allocator); const iterations: usize = 10; // Rebuild index benchmarks - const rebuild_results = try benchRebuildMarkerIndex(allocator, iterations, bench_filter); + const rebuild_results = try benchRebuildMarkerIndex(io, allocator, iterations, bench_filter); try all_results.appendSlice(allocator, rebuild_results); // Marker lookup benchmarks - const lookup_results = try benchMarkerLookup(allocator, iterations, bench_filter); + const lookup_results = try benchMarkerLookup(io, allocator, iterations, bench_filter); try all_results.appendSlice(allocator, lookup_results); // Marker count benchmarks - const count_results = try benchMarkerCount(allocator, iterations, bench_filter); + const count_results = try benchMarkerCount(io, allocator, iterations, bench_filter); try all_results.appendSlice(allocator, count_results); // Tree depth impact - const depth_results = try benchDepthVsPerformance(allocator, iterations, bench_filter); + const depth_results = try benchDepthVsPerformance(io, allocator, iterations, bench_filter); try all_results.appendSlice(allocator, depth_results); // Edit workflows - const edit_results = try benchEditThenRebuild(allocator, iterations, bench_filter); + const edit_results = try benchEditThenRebuild(io, allocator, iterations, bench_filter); try all_results.appendSlice(allocator, edit_results); // Memory usage comparison diff --git a/packages/core/src/zig/bench/rope_bench.zig b/packages/core/src/zig/bench/rope_bench.zig index 93600bce65..b47dbcaefd 100644 --- a/packages/core/src/zig/bench/rope_bench.zig +++ b/packages/core/src/zig/bench/rope_bench.zig @@ -23,11 +23,12 @@ const TestItem = struct { const RopeType = rope_mod.Rope(TestItem); fn benchInsertOperations( + io: std.Io, allocator: std.mem.Allocator, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); // Sequential appends @@ -40,7 +41,7 @@ fn benchInsertOperations( defer arena.deinit(); var rope = try RopeType.init(arena.allocator()); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..10000) |i| { try rope.append(.{ .value = @intCast(i) }); } @@ -69,7 +70,7 @@ fn benchInsertOperations( defer arena.deinit(); var rope = try RopeType.init(arena.allocator()); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..10000) |i| { try rope.prepend(.{ .value = @intCast(i) }); } @@ -100,7 +101,7 @@ fn benchInsertOperations( var rope = try RopeType.init(arena.allocator()); var prng = std.Random.DefaultPrng.init(42); const random = prng.random(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..5000) |i| { const pos = if (rope.count() > 0) random.intRangeAtMost(u32, 0, rope.count()) @@ -127,11 +128,12 @@ fn benchInsertOperations( } fn benchDeleteOperations( + io: std.Io, allocator: std.mem.Allocator, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); var items: [10000]TestItem = undefined; @@ -149,7 +151,7 @@ fn benchDeleteOperations( defer arena.deinit(); var rope = try RopeType.from_slice(arena.allocator(), &items); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..5000) |_| { try rope.delete(rope.count() - 1); } @@ -178,7 +180,7 @@ fn benchDeleteOperations( defer arena.deinit(); var rope = try RopeType.from_slice(arena.allocator(), &items); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..5000) |_| { try rope.delete(0); } @@ -209,7 +211,7 @@ fn benchDeleteOperations( var rope = try RopeType.from_slice(arena.allocator(), &items); var prng = std.Random.DefaultPrng.init(42); const random = prng.random(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..5000) |_| { const pos = random.intRangeAtMost(u32, 0, rope.count() - 1); try rope.delete(pos); @@ -233,11 +235,12 @@ fn benchDeleteOperations( } fn benchBulkOperations( + io: std.Io, allocator: std.mem.Allocator, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); var items: [10000]TestItem = undefined; @@ -259,7 +262,7 @@ fn benchBulkOperations( for (&chunk, 0..) |*item, i| { item.* = .{ .value = @intCast(i) }; } - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..10) |_| { try rope.insert_slice(rope.count(), &chunk); } @@ -288,7 +291,7 @@ fn benchBulkOperations( defer arena.deinit(); var rope = try RopeType.from_slice(arena.allocator(), &items); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..10) |_| { const start = if (rope.count() > 500) rope.count() - 500 else 0; const end = rope.count(); @@ -319,7 +322,7 @@ fn benchBulkOperations( defer arena.deinit(); var rope = try RopeType.from_slice(arena.allocator(), &items); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..100) |_| { const mid = rope.count() / 2; var right = try rope.split(mid); @@ -351,7 +354,7 @@ fn benchBulkOperations( var rope1 = try RopeType.from_slice(arena.allocator(), items[0..5000]); const rope2 = try RopeType.from_slice(arena.allocator(), items[5000..]); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); try rope1.concat(&rope2); stats.record(timer.read()); } @@ -372,11 +375,12 @@ fn benchBulkOperations( } fn benchAccessPatterns( + io: std.Io, allocator: std.mem.Allocator, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); var items: [10000]TestItem = undefined; @@ -394,7 +398,7 @@ fn benchAccessPatterns( defer arena.deinit(); const rope = try RopeType.from_slice(arena.allocator(), &items); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..10000) |i| { _ = rope.get(@intCast(i)); } @@ -425,7 +429,7 @@ fn benchAccessPatterns( const rope = try RopeType.from_slice(arena.allocator(), &items); var prng = std.Random.DefaultPrng.init(42); const random = prng.random(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..10000) |_| { const pos = random.intRangeAtMost(u32, 0, 9999); _ = rope.get(pos); @@ -465,7 +469,7 @@ fn benchAccessPatterns( } }; var ctx: Ctx = .{}; - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); try rope.walk(&ctx, Ctx.walker); stats.record(timer.read()); } @@ -486,28 +490,29 @@ fn benchAccessPatterns( } pub fn run( + io: std.Io, allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { _ = show_mem; // Rope benchmarks don't currently track memory - var all_results: std.ArrayListUnmanaged(BenchResult) = .{}; + var all_results: std.ArrayList(BenchResult) = .empty; errdefer all_results.deinit(allocator); const iterations: usize = 10; // Run all benchmark categories and filter results - const insert_results = try benchInsertOperations(allocator, iterations, bench_filter); + const insert_results = try benchInsertOperations(io, allocator, iterations, bench_filter); try all_results.appendSlice(allocator, insert_results); - const delete_results = try benchDeleteOperations(allocator, iterations, bench_filter); + const delete_results = try benchDeleteOperations(io, allocator, iterations, bench_filter); try all_results.appendSlice(allocator, delete_results); - const bulk_results = try benchBulkOperations(allocator, iterations, bench_filter); + const bulk_results = try benchBulkOperations(io, allocator, iterations, bench_filter); try all_results.appendSlice(allocator, bulk_results); - const access_results = try benchAccessPatterns(allocator, iterations, bench_filter); + const access_results = try benchAccessPatterns(io, allocator, iterations, bench_filter); try all_results.appendSlice(allocator, access_results); return all_results.toOwnedSlice(allocator); diff --git a/packages/core/src/zig/bench/styled-text_bench.zig b/packages/core/src/zig/bench/styled-text_bench.zig index 955c906aec..0958dcd536 100644 --- a/packages/core/src/zig/bench/styled-text_bench.zig +++ b/packages/core/src/zig/bench/styled-text_bench.zig @@ -26,11 +26,12 @@ fn rgba(r: f32, g: f32, b: f32, a: f32) RGBA { } fn benchSetStyledTextOperations( + io: std.Io, allocator: std.mem.Allocator, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); // Setup global resources @@ -66,7 +67,7 @@ fn benchSetStyledTextOperations( .attributes = 0, }}; - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); try tb.setStyledText(&chunks); stats.record(timer.read()); } @@ -120,7 +121,7 @@ fn benchSetStyledTextOperations( .{ .text_ptr = text5.ptr, .text_len = text5.len, .fg_ptr = rgbaToPtr(&magenta), .bg_ptr = null, .attributes = 0 }, }; - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); try tb.setStyledText(&chunks); stats.record(timer.read()); } @@ -177,7 +178,7 @@ fn benchSetStyledTextOperations( .{ .text_ptr = t7.ptr, .text_len = t7.len, .fg_ptr = rgbaToPtr(&operator_color), .bg_ptr = null, .attributes = 0 }, }; - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); try tb.setStyledText(&chunks); stats.record(timer.read()); } @@ -225,7 +226,7 @@ fn benchSetStyledTextOperations( .{ .text_ptr = text.ptr, .text_len = text.len, .fg_ptr = rgbaToPtr(&color), .bg_ptr = null, .attributes = 0 }, }; - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); try tb.setStyledText(&chunks); stats.record(timer.read()); } @@ -270,7 +271,7 @@ fn benchSetStyledTextOperations( .{ .text_ptr = t4.ptr, .text_len = t4.len, .fg_ptr = null, .bg_ptr = null, .attributes = 3 }, }; - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); try tb.setStyledText(&chunks); stats.record(timer.read()); } @@ -291,11 +292,12 @@ fn benchSetStyledTextOperations( } fn benchHighlightOperations( + io: std.Io, allocator: std.mem.Allocator, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); // Setup global resources @@ -324,7 +326,7 @@ fn benchHighlightOperations( const text = "Line 1 with some text\nLine 2 with more text\nLine 3 here\nLine 4 content\nLine 5 final"; try tb.setText(text); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); // Add 1000 highlights sequentially for (0..1000) |i| { @@ -367,7 +369,7 @@ fn benchHighlightOperations( const text = "Line 1 with some text\nLine 2 with more text\nLine 3 here\nLine 4 content\nLine 5 final"; try tb.setText(text); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); // Batch all highlights in a transaction tb.startHighlightsTransaction(); @@ -403,7 +405,7 @@ fn benchHighlightOperations( var stats: BenchStats = .{}; // Build a realistic multi-line code snippet with 100 chunks - var chunk_list: std.ArrayListUnmanaged(StyledChunk) = .{}; + var chunk_list: std.ArrayList(StyledChunk) = .empty; defer chunk_list.deinit(allocator); const keyword_color = rgba(0.8, 0.4, 1.0, 1.0); @@ -434,7 +436,7 @@ fn benchHighlightOperations( defer style.deinit(); tb.setSyntaxStyle(style); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); try tb.setStyledText(chunk_list.items); stats.record(timer.read()); } @@ -455,21 +457,22 @@ fn benchHighlightOperations( } pub fn run( + io: std.Io, allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { _ = show_mem; - var all_results: std.ArrayListUnmanaged(BenchResult) = .{}; + var all_results: std.ArrayList(BenchResult) = .empty; errdefer all_results.deinit(allocator); const iterations: usize = 100; - const styled_text_results = try benchSetStyledTextOperations(allocator, iterations, bench_filter); + const styled_text_results = try benchSetStyledTextOperations(io, allocator, iterations, bench_filter); try all_results.appendSlice(allocator, styled_text_results); - const highlight_results = try benchHighlightOperations(allocator, iterations, bench_filter); + const highlight_results = try benchHighlightOperations(io, allocator, iterations, bench_filter); try all_results.appendSlice(allocator, highlight_results); return all_results.toOwnedSlice(allocator); diff --git a/packages/core/src/zig/bench/text-buffer-coords_bench.zig b/packages/core/src/zig/bench/text-buffer-coords_bench.zig index 3fb5cd9760..3ea651cac8 100644 --- a/packages/core/src/zig/bench/text-buffer-coords_bench.zig +++ b/packages/core/src/zig/bench/text-buffer-coords_bench.zig @@ -13,7 +13,7 @@ pub const benchName = "TextBuffer Coordinate Conversion"; /// Create a text buffer with N lines for testing fn createTestBuffer(allocator: std.mem.Allocator, line_count: u32, chars_per_line: u32) !UnifiedRope { - var segments: std.ArrayListUnmanaged(Segment) = .{}; + var segments: std.ArrayList(Segment) = .empty; defer segments.deinit(allocator); for (0..line_count) |i| { @@ -37,11 +37,12 @@ fn createTestBuffer(allocator: std.mem.Allocator, line_count: u32, chars_per_lin } fn benchCoordsToOffsetCurrent( + io: std.Io, allocator: std.mem.Allocator, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); // Small buffer - 100 lines @@ -56,7 +57,7 @@ fn benchCoordsToOffsetCurrent( var rope = try createTestBuffer(arena.allocator(), 100, 50); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); // Access lines throughout the buffer for (0..100) |i| { const line: u32 = @intCast(i % 100); @@ -89,7 +90,7 @@ fn benchCoordsToOffsetCurrent( var rope = try createTestBuffer(arena.allocator(), 1000, 50); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..100) |i| { const line: u32 = @intCast((i * 10) % 1000); _ = iter_mod.coordsToOffset(&rope, line, 25); @@ -121,7 +122,7 @@ fn benchCoordsToOffsetCurrent( var rope = try createTestBuffer(arena.allocator(), 10000, 50); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..100) |i| { const line: u32 = @intCast((i * 100) % 10000); _ = iter_mod.coordsToOffset(&rope, line, 25); @@ -153,7 +154,7 @@ fn benchCoordsToOffsetCurrent( var rope = try createTestBuffer(arena.allocator(), 1000, 50); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..100) |_| { _ = iter_mod.coordsToOffset(&rope, 999, 25); // Last line } @@ -176,11 +177,12 @@ fn benchCoordsToOffsetCurrent( } fn benchOffsetToCoordsCurrent( + io: std.Io, allocator: std.mem.Allocator, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); // Small buffer @@ -199,7 +201,7 @@ fn benchOffsetToCoordsCurrent( var prng = std.Random.DefaultPrng.init(42); const random = prng.random(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..100) |_| { const offset = random.intRangeAtMost(u32, 0, total_width); _ = iter_mod.offsetToCoords(&rope, offset); @@ -235,7 +237,7 @@ fn benchOffsetToCoordsCurrent( var prng = std.Random.DefaultPrng.init(42); const random = prng.random(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..100) |_| { const offset = random.intRangeAtMost(u32, 0, total_width); _ = iter_mod.offsetToCoords(&rope, offset); @@ -271,7 +273,7 @@ fn benchOffsetToCoordsCurrent( var prng = std.Random.DefaultPrng.init(42); const random = prng.random(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..100) |_| { const offset = random.intRangeAtMost(u32, 0, total_width); _ = iter_mod.offsetToCoords(&rope, offset); @@ -295,11 +297,12 @@ fn benchOffsetToCoordsCurrent( } fn benchGetLineCount( + io: std.Io, allocator: std.mem.Allocator, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); // getLineCount is already optimized with metrics @@ -314,7 +317,7 @@ fn benchGetLineCount( var rope = try createTestBuffer(arena.allocator(), 10000, 50); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..100000) |_| { _ = iter_mod.getLineCount(&rope); } @@ -337,25 +340,26 @@ fn benchGetLineCount( } pub fn run( + io: std.Io, allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { _ = show_mem; - var all_results: std.ArrayListUnmanaged(BenchResult) = .{}; + var all_results: std.ArrayList(BenchResult) = .empty; errdefer all_results.deinit(allocator); const iterations: usize = 10; // Current implementation benchmarks - const coords_results = try benchCoordsToOffsetCurrent(allocator, iterations, bench_filter); + const coords_results = try benchCoordsToOffsetCurrent(io, allocator, iterations, bench_filter); try all_results.appendSlice(allocator, coords_results); - const offset_results = try benchOffsetToCoordsCurrent(allocator, iterations, bench_filter); + const offset_results = try benchOffsetToCoordsCurrent(io, allocator, iterations, bench_filter); try all_results.appendSlice(allocator, offset_results); - const count_results = try benchGetLineCount(allocator, iterations, bench_filter); + const count_results = try benchGetLineCount(io, allocator, iterations, bench_filter); try all_results.appendSlice(allocator, count_results); return all_results.toOwnedSlice(allocator); diff --git a/packages/core/src/zig/bench/text-buffer-view_bench.zig b/packages/core/src/zig/bench/text-buffer-view_bench.zig index 26d34640f6..fb7dadb44c 100644 --- a/packages/core/src/zig/bench/text-buffer-view_bench.zig +++ b/packages/core/src/zig/bench/text-buffer-view_bench.zig @@ -26,7 +26,7 @@ const large_text_patterns = [_][]const u8{ }; pub fn generateLargeText(allocator: std.mem.Allocator, lines: u32, target_bytes: usize) ![]u8 { - var buffer: std.ArrayListUnmanaged(u8) = .{}; + var buffer: std.ArrayList(u8) = .empty; errdefer buffer.deinit(allocator); var current_bytes: usize = 0; @@ -49,7 +49,7 @@ pub fn generateLargeText(allocator: std.mem.Allocator, lines: u32, target_bytes: } pub fn generateLargeTextSingleLine(allocator: std.mem.Allocator, target_bytes: usize) ![]u8 { - var buffer: std.ArrayListUnmanaged(u8) = .{}; + var buffer: std.ArrayList(u8) = .empty; errdefer buffer.deinit(allocator); var current_bytes: usize = 0; @@ -92,13 +92,14 @@ fn computeSingleLineTextSize(target_bytes: usize) usize { } fn benchSetText( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, iterations: usize, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const link_pool = link.initGlobalLinkPool(allocator); @@ -114,7 +115,7 @@ fn benchSetText( var tb = try UnifiedTextBuffer.init(allocator, pool, link_pool, .unicode); defer tb.deinit(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); try tb.setText(text); stats.record(timer.read()); @@ -164,7 +165,7 @@ fn benchSetText( var tb = try UnifiedTextBuffer.init(allocator, pool, link_pool, .unicode); defer tb.deinit(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); try tb.setText(text); stats.record(timer.read()); @@ -195,6 +196,7 @@ fn benchSetText( } fn benchWrap( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, text: []const u8, @@ -219,7 +221,7 @@ fn benchWrap( view.setWrapMode(wrap_mode); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); view.setWrapWidth(wrap_width); const count = view.getVirtualLineCount(); stats.record(timer.read()); @@ -250,6 +252,7 @@ fn benchWrap( } fn benchMeasureForDimensionsLayout( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, text: []const u8, @@ -288,7 +291,7 @@ fn benchMeasureForDimensionsLayout( newline_mem_id = try tb.registerMemBuffer(newline, false); } - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..steps) |step| { if (streaming) { try tb.appendFromMemId(token_mem_id); @@ -329,6 +332,7 @@ fn benchMeasureForDimensionsLayout( } pub fn run( + io: std.Io, allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8, @@ -336,13 +340,13 @@ pub fn run( // Global pool and unicode data are initialized once in bench.zig const pool = gp.initGlobalPool(allocator); - var all_results: std.ArrayListUnmanaged(BenchResult) = .{}; + var all_results: std.ArrayList(BenchResult) = .empty; errdefer all_results.deinit(allocator); const iterations: usize = 10; // Run setText benchmarks - const setText_results = try benchSetText(allocator, pool, iterations, show_mem, bench_filter); + const setText_results = try benchSetText(io, allocator, pool, iterations, show_mem, bench_filter); try all_results.appendSlice(allocator, setText_results); var text_multiline: ?[]u8 = null; @@ -382,6 +386,7 @@ pub fn run( } var bench_result = try benchMeasureForDimensionsLayout( + io, allocator, pool, text_multiline.?, @@ -442,6 +447,7 @@ pub fn run( } var bench_result = try benchWrap( + io, allocator, pool, text, diff --git a/packages/core/src/zig/bench/text-chunk-graphemes_bench.zig b/packages/core/src/zig/bench/text-chunk-graphemes_bench.zig index 6bd39f8079..3b6ab417a0 100644 --- a/packages/core/src/zig/bench/text-chunk-graphemes_bench.zig +++ b/packages/core/src/zig/bench/text-chunk-graphemes_bench.zig @@ -15,7 +15,7 @@ pub const benchName = "TextChunk getGraphemes"; const TextType = enum { ascii, mixed, heavy_unicode }; fn generateTestText(allocator: std.mem.Allocator, size: usize, text_type: TextType) ![]u8 { - var buffer: std.ArrayListUnmanaged(u8) = .{}; + var buffer: std.ArrayList(u8) = .empty; errdefer buffer.deinit(allocator); switch (text_type) { @@ -77,6 +77,7 @@ fn generateTestText(allocator: std.mem.Allocator, size: usize, text_type: TextTy } fn benchGetGraphemes( + io: std.Io, allocator: std.mem.Allocator, size: usize, text_type: TextType, @@ -123,7 +124,7 @@ fn benchGetGraphemes( // Clear cached graphemes chunk.graphemes = null; - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); const graphemes = try chunk.getGraphemes( arena_alloc, ®istry, @@ -216,6 +217,7 @@ fn computeBenchName(allocator: std.mem.Allocator, size: usize, text_type: TextTy } pub fn run( + io: std.Io, allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8, @@ -223,7 +225,7 @@ pub fn run( // Global pool and unicode data are initialized once in bench.zig _ = gp.initGlobalPool(allocator); - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(allocator); const iterations: usize = 100; @@ -236,6 +238,7 @@ pub fn run( for (text_types) |text_type| { for (sizes) |size| { const result = try benchGetGraphemes( + io, allocator, size, text_type, @@ -255,6 +258,7 @@ pub fn run( } var result = try benchGetGraphemes( + io, allocator, size, text_type, diff --git a/packages/core/src/zig/bench/utf8_bench.zig b/packages/core/src/zig/bench/utf8_bench.zig index c4886a7187..2dc265eb62 100644 --- a/packages/core/src/zig/bench/utf8_bench.zig +++ b/packages/core/src/zig/bench/utf8_bench.zig @@ -18,7 +18,7 @@ fn generateAsciiText(allocator: std.mem.Allocator, length: usize) ![]const u8 { } fn generateMixedText(allocator: std.mem.Allocator, length: usize) ![]const u8 { - var text: std.ArrayListUnmanaged(u8) = .{}; + var text: std.ArrayList(u8) = .empty; errdefer text.deinit(allocator); var i: usize = 0; while (text.items.len < length) : (i += 1) { @@ -34,7 +34,7 @@ fn generateMixedText(allocator: std.mem.Allocator, length: usize) ![]const u8 { } fn generateUnicodeHeavyText(allocator: std.mem.Allocator, length: usize) ![]const u8 { - var text: std.ArrayListUnmanaged(u8) = .{}; + var text: std.ArrayList(u8) = .empty; errdefer text.deinit(allocator); var i: usize = 0; while (text.items.len < length) : (i += 1) { @@ -51,11 +51,12 @@ fn generateUnicodeHeavyText(allocator: std.mem.Allocator, length: usize) ![]cons // Benchmark isAsciiOnly fn benchIsAsciiOnly( + io: std.Io, results_alloc: std.mem.Allocator, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(results_alloc); // Small ASCII text (1KB) @@ -68,7 +69,7 @@ fn benchIsAsciiOnly( var stats: BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); _ = utf8.isAsciiOnly(text); stats.record(timer.read()); } @@ -95,7 +96,7 @@ fn benchIsAsciiOnly( var stats: BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); _ = utf8.isAsciiOnly(text); stats.record(timer.read()); } @@ -122,7 +123,7 @@ fn benchIsAsciiOnly( var stats: BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); _ = utf8.isAsciiOnly(text); stats.record(timer.read()); } @@ -149,7 +150,7 @@ fn benchIsAsciiOnly( var stats: BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); _ = utf8.isAsciiOnly(text); stats.record(timer.read()); } @@ -171,11 +172,12 @@ fn benchIsAsciiOnly( // Benchmark findLineBreaks fn benchFindLineBreaks( + io: std.Io, results_alloc: std.mem.Allocator, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(results_alloc); // Text with LF breaks @@ -186,7 +188,7 @@ fn benchFindLineBreaks( defer temp.deinit(); const alloc = temp.allocator(); - var text: std.ArrayListUnmanaged(u8) = .{}; + var text: std.ArrayList(u8) = .empty; for (0..100) |_| { try text.appendSlice(alloc, "This is a line of text that ends with a newline character.\n"); } @@ -197,7 +199,7 @@ fn benchFindLineBreaks( var stats: BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); try utf8.findLineBreaks(test_text, &line_result); stats.record(timer.read()); } @@ -222,7 +224,7 @@ fn benchFindLineBreaks( defer temp.deinit(); const alloc = temp.allocator(); - var text: std.ArrayListUnmanaged(u8) = .{}; + var text: std.ArrayList(u8) = .empty; for (0..100) |_| { try text.appendSlice(alloc, "This is a line of text that ends with CRLF.\r\n"); } @@ -233,7 +235,7 @@ fn benchFindLineBreaks( var stats: BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); try utf8.findLineBreaks(test_text, &line_result); stats.record(timer.read()); } @@ -258,7 +260,7 @@ fn benchFindLineBreaks( defer temp.deinit(); const alloc = temp.allocator(); - var text: std.ArrayListUnmanaged(u8) = .{}; + var text: std.ArrayList(u8) = .empty; for (0..1000) |_| { try text.appendSlice(alloc, "Short line\n"); } @@ -269,7 +271,7 @@ fn benchFindLineBreaks( var stats: BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); try utf8.findLineBreaks(test_text, &line_result); stats.record(timer.read()); } @@ -291,11 +293,12 @@ fn benchFindLineBreaks( // Benchmark findWrapBreaks fn benchFindWrapBreaks( + io: std.Io, results_alloc: std.mem.Allocator, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(results_alloc); // ASCII text @@ -312,7 +315,7 @@ fn benchFindWrapBreaks( var stats: BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); try utf8.findWrapBreaks(text, &wrap_result, .unicode); stats.record(timer.read()); } @@ -343,7 +346,7 @@ fn benchFindWrapBreaks( var stats: BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); try utf8.findWrapBreaks(text, &wrap_result, .unicode); stats.record(timer.read()); } @@ -365,11 +368,12 @@ fn benchFindWrapBreaks( // Benchmark findWrapPosByWidth fn benchFindWrapPosByWidth( + io: std.Io, results_alloc: std.mem.Allocator, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(results_alloc); // ASCII text, narrow width @@ -382,7 +386,7 @@ fn benchFindWrapPosByWidth( var stats: BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); _ = utf8.findWrapPosByWidth(text, 40, 4, true, .unicode); stats.record(timer.read()); } @@ -409,7 +413,7 @@ fn benchFindWrapPosByWidth( var stats: BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); _ = utf8.findWrapPosByWidth(text, 120, 4, true, .unicode); stats.record(timer.read()); } @@ -436,7 +440,7 @@ fn benchFindWrapPosByWidth( var stats: BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); _ = utf8.findWrapPosByWidth(text, 80, 4, false, .unicode); stats.record(timer.read()); } @@ -463,7 +467,7 @@ fn benchFindWrapPosByWidth( var stats: BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); _ = utf8.findWrapPosByWidth(text, 80, 4, false, .unicode); stats.record(timer.read()); } @@ -485,11 +489,12 @@ fn benchFindWrapPosByWidth( // Benchmark findPosByWidth fn benchFindPosByWidth( + io: std.Io, results_alloc: std.mem.Allocator, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(results_alloc); // ASCII text, find middle @@ -502,7 +507,7 @@ fn benchFindPosByWidth( var stats: BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); _ = utf8.findPosByWidth(text, 500, 4, true, true, .unicode); stats.record(timer.read()); } @@ -529,7 +534,7 @@ fn benchFindPosByWidth( var stats: BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); _ = utf8.findPosByWidth(text, 90000, 4, true, true, .unicode); stats.record(timer.read()); } @@ -556,7 +561,7 @@ fn benchFindPosByWidth( var stats: BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); _ = utf8.findPosByWidth(text, 5000, 4, false, true, .unicode); stats.record(timer.read()); } @@ -578,11 +583,12 @@ fn benchFindPosByWidth( // Benchmark calculateTextWidth fn benchCalculateTextWidth( + io: std.Io, results_alloc: std.mem.Allocator, iterations: usize, bench_filter: ?[]const u8, ) ![]BenchResult { - var results: std.ArrayListUnmanaged(BenchResult) = .{}; + var results: std.ArrayList(BenchResult) = .empty; errdefer results.deinit(results_alloc); // Small ASCII text (1KB) @@ -595,7 +601,7 @@ fn benchCalculateTextWidth( var stats: BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); _ = utf8.calculateTextWidth(text, 4, true, .unicode); stats.record(timer.read()); } @@ -622,7 +628,7 @@ fn benchCalculateTextWidth( var stats: BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); _ = utf8.calculateTextWidth(text, 4, true, .unicode); stats.record(timer.read()); } @@ -649,7 +655,7 @@ fn benchCalculateTextWidth( var stats: BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); _ = utf8.calculateTextWidth(text, 4, true, .unicode); stats.record(timer.read()); } @@ -674,7 +680,7 @@ fn benchCalculateTextWidth( defer temp.deinit(); const alloc = temp.allocator(); - var text: std.ArrayListUnmanaged(u8) = .{}; + var text: std.ArrayList(u8) = .empty; for (0..10 * 1024) |i| { if (i % 20 == 0) { try text.append(alloc, '\t'); @@ -685,7 +691,7 @@ fn benchCalculateTextWidth( var stats: BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); _ = utf8.calculateTextWidth(text.items, 4, false, .unicode); stats.record(timer.read()); } @@ -712,7 +718,7 @@ fn benchCalculateTextWidth( var stats: BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); _ = utf8.calculateTextWidth(text, 4, false, .unicode); stats.record(timer.read()); } @@ -739,7 +745,7 @@ fn benchCalculateTextWidth( var stats: BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); _ = utf8.calculateTextWidth(text, 4, false, .unicode); stats.record(timer.read()); } @@ -760,39 +766,40 @@ fn benchCalculateTextWidth( } pub fn run( + io: std.Io, allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8, ) ![]BenchResult { _ = show_mem; - var all_results: std.ArrayListUnmanaged(BenchResult) = .{}; + var all_results: std.ArrayList(BenchResult) = .empty; errdefer all_results.deinit(allocator); const iterations: usize = 1000; // isAsciiOnly benchmarks - const ascii_only_results = try benchIsAsciiOnly(allocator, iterations, bench_filter); + const ascii_only_results = try benchIsAsciiOnly(io, allocator, iterations, bench_filter); try all_results.appendSlice(allocator, ascii_only_results); // findLineBreaks benchmarks - const line_breaks_results = try benchFindLineBreaks(allocator, iterations, bench_filter); + const line_breaks_results = try benchFindLineBreaks(io, allocator, iterations, bench_filter); try all_results.appendSlice(allocator, line_breaks_results); // findWrapBreaks benchmarks - const wrap_breaks_results = try benchFindWrapBreaks(allocator, iterations, bench_filter); + const wrap_breaks_results = try benchFindWrapBreaks(io, allocator, iterations, bench_filter); try all_results.appendSlice(allocator, wrap_breaks_results); // findWrapPosByWidth benchmarks - const wrap_pos_results = try benchFindWrapPosByWidth(allocator, iterations, bench_filter); + const wrap_pos_results = try benchFindWrapPosByWidth(io, allocator, iterations, bench_filter); try all_results.appendSlice(allocator, wrap_pos_results); // findPosByWidth benchmarks - const pos_width_results = try benchFindPosByWidth(allocator, iterations, bench_filter); + const pos_width_results = try benchFindPosByWidth(io, allocator, iterations, bench_filter); try all_results.appendSlice(allocator, pos_width_results); // calculateTextWidth benchmarks - const text_width_results = try benchCalculateTextWidth(allocator, iterations, bench_filter); + const text_width_results = try benchCalculateTextWidth(io, allocator, iterations, bench_filter); try all_results.appendSlice(allocator, text_width_results); return all_results.toOwnedSlice(allocator); diff --git a/packages/core/src/zig/buffer.zig b/packages/core/src/zig/buffer.zig index ec2f1b0087..582c4927a1 100644 --- a/packages/core/src/zig/buffer.zig +++ b/packages/core/src/zig/buffer.zig @@ -228,10 +228,10 @@ 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.ArrayListUnmanaged(ClipRect) = .{}; + var scissor_stack: std.ArrayListUnmanaged(ClipRect) = .empty; errdefer scissor_stack.deinit(allocator); - var opacity_stack: std.ArrayListUnmanaged(f32) = .{}; + var opacity_stack: std.ArrayListUnmanaged(f32) = .empty; errdefer opacity_stack.deinit(allocator); const lp = options.link_pool orelse link.initGlobalLinkPool(allocator); @@ -1266,7 +1266,7 @@ pub const OptimizedBuffer = struct { } } - var grapheme_list: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var grapheme_list: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer grapheme_list.deinit(self.allocator); const tab_width: u8 = 2; diff --git a/packages/core/src/zig/build.zig b/packages/core/src/zig/build.zig index 5cb8dfc0e5..28c0fecdf7 100644 --- a/packages/core/src/zig/build.zig +++ b/packages/core/src/zig/build.zig @@ -8,7 +8,7 @@ const SupportedZigVersion = struct { }; const SUPPORTED_ZIG_VERSIONS = [_]SupportedZigVersion{ - .{ .major = 0, .minor = 15, .patch = 2 }, + .{ .major = 0, .minor = 16, .patch = 0 }, }; const SupportedTarget = struct { @@ -97,23 +97,23 @@ fn nativeExecutableTarget(b: *std.Build) std.Build.ResolvedTarget { return b.resolveTargetQuery(.{}); } - // Zig 0.15.2's ELF linker currently fails on newer glibc startup objects - // that ship .sframe relocations. Keep shipped libraries on linux-gnu, but - // use musl for local native executables so test/debug/bench still work. + // Zig 0.16's ELF linker still fails on newer glibc startup objects that + // ship .sframe relocations. Keep shipped libraries on linux-gnu, but use + // musl for local native executables so test/debug/bench still work. var query = b.graph.host.query; query.abi = .musl; query.glibc_version = null; return b.resolveTargetQuery(query); } -fn pathExists(path: []const u8) bool { +fn pathExists(b: *std.Build, path: []const u8) bool { if (path.len == 0) return false; - std.fs.cwd().access(path, .{}) catch return false; + std.Io.Dir.cwd().access(b.graph.io, path, .{}) catch return false; return true; } fn isMacOSSDKPath(path: []const u8) bool { - const trimmed_path = std.mem.trimRight(u8, path, "/"); + const trimmed_path = std.mem.trimEnd(u8, path, "/"); if (trimmed_path.len == 0) return false; const base_name = std.fs.path.basename(trimmed_path); @@ -121,12 +121,12 @@ fn isMacOSSDKPath(path: []const u8) bool { } fn macOSSDKHasFramework(b: *std.Build, sdk_path: []const u8, framework: []const u8) bool { - return pathExists(b.pathJoin(&.{ sdk_path, "System", "Library", "Frameworks", b.fmt("{s}.framework", .{framework}) })); + return pathExists(b, b.pathJoin(&.{ sdk_path, "System", "Library", "Frameworks", b.fmt("{s}.framework", .{framework}) })); } fn isMacOSSDKAvailable(b: *std.Build, sdk_path: []const u8) bool { return isMacOSSDKPath(sdk_path) and - pathExists(b.pathJoin(&.{ sdk_path, "usr", "lib" })) and + pathExists(b, b.pathJoin(&.{ sdk_path, "usr", "lib" })) and macOSSDKHasFramework(b, sdk_path, "CoreFoundation") and macOSSDKHasFramework(b, sdk_path, "CoreAudio") and macOSSDKHasFramework(b, sdk_path, "AudioToolbox"); @@ -141,14 +141,14 @@ fn resolveMacOSSDKPath(b: *std.Build) ?[]const u8 { const env_vars = [_][]const u8{ "SDKROOT", "MACOS_SDK_PATH", "MACOSX_SDK_PATH" }; for (env_vars) |env_var| { - if (b.graph.env_map.get(env_var)) |sdk_path| { + if (b.graph.environ_map.get(env_var)) |sdk_path| { if (isMacOSSDKAvailable(b, sdk_path)) return sdk_path; } } - if (builtin.os.tag == .macos and std.zig.system.darwin.isSdkInstalled(b.allocator)) { + if (builtin.os.tag == .macos and std.zig.system.darwin.isSdkInstalled(b.allocator, b.graph.io)) { const sdk_target = b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .macos }); - if (std.zig.system.darwin.getSdk(b.allocator, &sdk_target.result)) |sdk_path| { + if (std.zig.system.darwin.getSdk(b.allocator, b.graph.io, &sdk_target.result)) |sdk_path| { if (isMacOSSDKAvailable(b, sdk_path)) return sdk_path; } } @@ -166,7 +166,7 @@ fn printMissingMacOSSDK(target_description: []const u8) void { fn addMiniaudioShim( b: *std.Build, - artifact: *std.Build.Step.Compile, + module: *std.Build.Module, target: std.Build.ResolvedTarget, macos_sdk_path: ?[]const u8, ) void { @@ -182,9 +182,9 @@ fn addMiniaudioShim( else => &.{"-std=c99"}, }; - artifact.addIncludePath(b.path(".")); - artifact.linkLibC(); - artifact.addCSourceFile(.{ + module.addIncludePath(b.path(".")); + module.link_libc = true; + module.addCSourceFile(.{ .file = b.path("miniaudio_shim.c"), .flags = c_flags, }); @@ -197,19 +197,19 @@ fn appendCFlags(b: *std.Build, base: []const []const u8, extra: []const []const return flags; } -fn addImageShim(b: *std.Build, artifact: *std.Build.Step.Compile, target: std.Build.ResolvedTarget, macos_sdk_path: ?[]const u8) void { +fn addImageShim(b: *std.Build, module: *std.Build.Module, target: std.Build.ResolvedTarget, macos_sdk_path: ?[]const u8) void { const flags: []const []const u8 = switch (target.result.os.tag) { .macos => &.{ "-std=c99", "-ffp-contract=off", "-fvisibility=hidden", "-isysroot", macos_sdk_path.? }, else => &.{ "-std=c99", "-ffp-contract=off", "-fvisibility=hidden" }, }; - artifact.addCSourceFile(.{ + module.addCSourceFile(.{ .file = b.path("image-shim.c"), .flags = flags, }); - artifact.addIncludePath(b.path("vendor/lcms2/include")); - artifact.addIncludePath(b.path("vendor/lcms2/src")); - artifact.addCSourceFiles(.{ + module.addIncludePath(b.path("vendor/lcms2/include")); + module.addIncludePath(b.path("vendor/lcms2/src")); + module.addCSourceFiles(.{ .root = b.path("vendor/lcms2"), .files = &LCMS2_SOURCES, // image.zig serializes every LittleCMS operation with icc_cache_mutex. @@ -226,7 +226,7 @@ fn addImageShim(b: *std.Build, artifact: *std.Build.Step.Compile, target: std.Bu .macos => &.{ "-std=c99", "-ffp-contract=off", "-fvisibility=hidden", "-fno-sanitize=bounds", "-isysroot", macos_sdk_path.? }, else => &.{ "-std=c99", "-ffp-contract=off", "-fvisibility=hidden", "-fno-sanitize=bounds" }, }; - artifact.addCSourceFile(.{ + module.addCSourceFile(.{ .file = b.path("image-resize-shim.c"), .flags = resize_flags, }); @@ -239,12 +239,12 @@ fn addImageShim(b: *std.Build, artifact: *std.Build.Step.Compile, target: std.Bu appendCFlags(b, webp_flags, &.{ "-DWEBP_HAVE_SSE2", "-DWEBP_HAVE_SSE41", "-DWEBP_HAVE_AVX2" }) else webp_flags; - artifact.addIncludePath(b.path("vendor/libwebp")); - artifact.addCSourceFile(.{ + module.addIncludePath(b.path("vendor/libwebp")); + module.addCSourceFile(.{ .file = b.path("image-webp-config.c"), .flags = webp_dispatch_flags, }); - artifact.addCSourceFiles(.{ + module.addCSourceFiles(.{ .root = b.path("vendor/libwebp"), .files = &.{ "src/dec/alpha_dec.c", @@ -282,7 +282,7 @@ fn addImageShim(b: *std.Build, artifact: *std.Build.Step.Compile, target: std.Bu switch (target.result.cpu.arch) { .x86_64 => { - artifact.addCSourceFiles(.{ + module.addCSourceFiles(.{ .root = b.path("vendor/libwebp"), .files = &.{ "src/dsp/alpha_processing_sse2.c", @@ -295,16 +295,16 @@ fn addImageShim(b: *std.Build, artifact: *std.Build.Step.Compile, target: std.Bu }, .flags = webp_flags, }); - artifact.addCSourceFile(.{ + module.addCSourceFile(.{ .file = b.path("image-webp-sse41.c"), .flags = webp_flags, }); - artifact.addCSourceFile(.{ + module.addCSourceFile(.{ .file = b.path("image-webp-avx2.c"), .flags = webp_flags, }); }, - .aarch64 => artifact.addCSourceFiles(.{ + .aarch64 => module.addCSourceFiles(.{ .root = b.path("vendor/libwebp"), .files = &.{ "src/dsp/alpha_processing_neon.c", @@ -321,51 +321,51 @@ fn addImageShim(b: *std.Build, artifact: *std.Build.Step.Compile, target: std.Bu } } -fn addMacOSSDKSearchPaths(b: *std.Build, artifact: *std.Build.Step.Compile, sdk_path: []const u8) void { +fn addMacOSSDKSearchPaths(b: *std.Build, module: *std.Build.Module, sdk_path: []const u8) void { const include_path = b.pathJoin(&.{ sdk_path, "usr", "include" }); const framework_path = b.pathJoin(&.{ sdk_path, "System", "Library", "Frameworks" }); const lib_path = b.pathJoin(&.{ sdk_path, "usr", "lib" }); - artifact.addSystemIncludePath(.{ .cwd_relative = include_path }); - artifact.addSystemFrameworkPath(.{ .cwd_relative = framework_path }); - artifact.addFrameworkPath(.{ .cwd_relative = framework_path }); - artifact.addLibraryPath(.{ .cwd_relative = lib_path }); + module.addSystemIncludePath(.{ .cwd_relative = include_path }); + module.addSystemFrameworkPath(.{ .cwd_relative = framework_path }); + module.addFrameworkPath(.{ .cwd_relative = framework_path }); + module.addLibraryPath(.{ .cwd_relative = lib_path }); } -fn addMacOSSystemLibraries(b: *std.Build, artifact: *std.Build.Step.Compile, sdk_path: []const u8) void { - artifact.linkFramework("CoreFoundation"); - artifact.linkFramework("CoreAudio"); - artifact.linkFramework("AudioToolbox"); - artifact.linkSystemLibrary("pthread"); - addMacOSSDKSearchPaths(b, artifact, sdk_path); +fn addMacOSSystemLibraries(b: *std.Build, module: *std.Build.Module, sdk_path: []const u8) void { + module.linkFramework("CoreFoundation", .{}); + module.linkFramework("CoreAudio", .{}); + module.linkFramework("AudioToolbox", .{}); + module.linkSystemLibrary("pthread", .{}); + addMacOSSDKSearchPaths(b, module, sdk_path); } fn addNativeAudioDependencies( b: *std.Build, - artifact: *std.Build.Step.Compile, + module: *std.Build.Module, target: std.Build.ResolvedTarget, macos_sdk_path: ?[]const u8, ) void { - addMiniaudioShim(b, artifact, target, macos_sdk_path); - addImageShim(b, artifact, target, macos_sdk_path); + addMiniaudioShim(b, module, target, macos_sdk_path); + addImageShim(b, module, target, macos_sdk_path); switch (target.result.os.tag) { - .macos => addMacOSSystemLibraries(b, artifact, macos_sdk_path.?), + .macos => addMacOSSystemLibraries(b, module, macos_sdk_path.?), .linux => { - artifact.linkSystemLibrary("dl"); - artifact.linkSystemLibrary("pthread"); - artifact.linkSystemLibrary("m"); + module.linkSystemLibrary("dl", .{}); + module.linkSystemLibrary("pthread", .{}); + module.linkSystemLibrary("m", .{}); }, else => {}, } } -fn addYogaDependencies(b: *std.Build, artifact: *std.Build.Step.Compile) void { +fn addYogaDependencies(b: *std.Build, module: *std.Build.Module) void { const yoga_dep = b.dependency("yoga", .{}); - artifact.linkLibCpp(); - artifact.addIncludePath(yoga_dep.path("")); - artifact.addCSourceFiles(.{ + module.link_libcpp = true; + module.addIncludePath(yoga_dep.path("")); + module.addCSourceFiles(.{ .root = yoga_dep.path(""), .files = &YOGA_CXX_SOURCES, .flags = &YOGA_CXX_FLAGS, @@ -385,6 +385,30 @@ fn ghosttyVtAvailable(target: std.Build.ResolvedTarget) bool { }; } +fn addTranslatedCImports( + b: *std.Build, + module: *std.Build.Module, + optimize: std.builtin.OptimizeMode, + target: std.Build.ResolvedTarget, +) void { + const miniaudio_translate = b.addTranslateC(.{ + .root_source_file = b.path("vendor/miniaudio/miniaudio.h"), + .target = target, + .optimize = optimize, + }); + miniaudio_translate.addIncludePath(b.path(".")); + module.addImport("miniaudio", miniaudio_translate.createModule()); + + const yoga_dep = b.dependency("yoga", .{}); + const yoga_translate = b.addTranslateC(.{ + .root_source_file = yoga_dep.path("yoga/Yoga.h"), + .target = target, + .optimize = optimize, + }); + yoga_translate.addIncludePath(yoga_dep.path("")); + module.addImport("yoga", yoga_translate.createModule()); +} + /// Apply dependencies to a module fn applyDependencies( b: *std.Build, @@ -394,6 +418,8 @@ fn applyDependencies( build_options: *std.Build.Step.Options, ) void { module.addOptions("build_options", build_options); + addTranslatedCImports(b, module, optimize, target); + const ghostty_vt_available = ghosttyVtAvailable(target); const ghostty_vt_options = b.addOptions(); ghostty_vt_options.addOption(bool, "available", ghostty_vt_available); @@ -515,8 +541,8 @@ pub fn build(b: *std.Build) void { printMissingMacOSSDK("native macOS tests"); std.process.exit(1); } - addNativeAudioDependencies(b, test_artifact, native_target, macos_sdk_path); - addYogaDependencies(b, test_artifact); + addNativeAudioDependencies(b, test_mod, native_target, macos_sdk_path); + addYogaDependencies(b, test_mod); const run_test = b.addRunArtifact(test_artifact); test_step.dependOn(&run_test.step); @@ -543,23 +569,24 @@ pub fn build(b: *std.Build) void { bench_step.dependOn(&run_bench.step); const bench_ffi_step = b.step("bench-ffi", "Build NativeSpanFeed benchmark library"); + const bench_ffi_target = b.resolveTargetQuery(.{}); const bench_ffi_mod = b.createModule(.{ .root_source_file = b.path("native-span-feed-bench-lib.zig"), - .target = native_target, + .target = bench_ffi_target, .optimize = bench_optimize, }); - applyDependencies(b, bench_ffi_mod, bench_optimize, native_target, build_options); + applyDependencies(b, bench_ffi_mod, bench_optimize, bench_ffi_target, build_options); const bench_ffi_lib = b.addLibrary(.{ .name = "native_span_feed_bench", .root_module = bench_ffi_mod, .linkage = .dynamic, }); - if (native_target.result.os.tag == .macos and macos_sdk_path == null) { + if (bench_ffi_target.result.os.tag == .macos and macos_sdk_path == null) { printMissingMacOSSDK("native macOS benchmark FFI library"); std.process.exit(1); } - addNativeAudioDependencies(b, bench_ffi_lib, native_target, macos_sdk_path); - addYogaDependencies(b, bench_ffi_lib); + addNativeAudioDependencies(b, bench_ffi_mod, bench_ffi_target, macos_sdk_path); + addYogaDependencies(b, bench_ffi_mod); const install_bench_ffi = b.addInstallArtifact(bench_ffi_lib, .{}); bench_ffi_step.dependOn(&install_bench_ffi.step); bench_step.dependOn(bench_ffi_step); @@ -612,8 +639,8 @@ fn buildNativeTarget( 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) + if (std.mem.find(u8, supported_target.zig_target, native_arch) != null and + std.mem.find(u8, supported_target.zig_target, native_os) != null) { try buildTarget( b, @@ -690,8 +717,8 @@ fn buildTarget( .linkage = .dynamic, }); - addNativeAudioDependencies(b, lib, target, macos_sdk_path); - addYogaDependencies(b, lib); + addNativeAudioDependencies(b, module, target, macos_sdk_path); + addYogaDependencies(b, module); const install_dir = b.addInstallArtifact(lib, .{ .dest_dir = .{ diff --git a/packages/core/src/zig/build.zig.zon b/packages/core/src/zig/build.zig.zon index 0038c07992..25f0da18b9 100644 --- a/packages/core/src/zig/build.zig.zon +++ b/packages/core/src/zig/build.zig.zon @@ -2,11 +2,11 @@ .name = .opentui, .version = "0.1.11", .fingerprint = 0x5445027d063f5083, - .minimum_zig_version = "0.15.2", + .minimum_zig_version = "0.16.0", .dependencies = .{ .uucode = .{ - .url = "https://github.com/jacobsandlund/uucode/archive/84ceda8561a17ba4a9b96ac5c583f779660bbd4e.tar.gz", - .hash = "uucode-0.1.0-ZZjBPtA_TQCWp5PIKmfm5tu1WOkKWFmBGFEMxircPfkA", + .url = "https://github.com/jacobsandlund/uucode/archive/8ad04b756f85a5ba1ac8d2b8cb48d0946f06b630.tar.gz", + .hash = "uucode-0.2.0-ZZjBPuS4VABmt0lQ-jDiKB4afmuZVvZpbX09FP5JrR8N", }, .ghostty = .{ .url = "https://github.com/ghostty-org/ghostty/archive/74d0c72fd9318ad3ab95bfb56f6c2d995e267e2e.tar.gz", diff --git a/packages/core/src/zig/edit-buffer.zig b/packages/core/src/zig/edit-buffer.zig index aa1d059774..db229c1da7 100644 --- a/packages/core/src/zig/edit-buffer.zig +++ b/packages/core/src/zig/edit-buffer.zig @@ -149,7 +149,7 @@ pub const EditBuffer = struct { const add_buffer = try AddBuffer.init(allocator, text_buffer, 65536); errdefer {} - var cursors: std.ArrayListUnmanaged(Cursor) = .{}; + var cursors: std.ArrayListUnmanaged(Cursor) = .empty; errdefer cursors.deinit(allocator); try cursors.append(allocator, .{ .row = 0, .col = 0 }); diff --git a/packages/core/src/zig/event-emitter.zig b/packages/core/src/zig/event-emitter.zig index 5c875d5e01..0ec84d2807 100644 --- a/packages/core/src/zig/event-emitter.zig +++ b/packages/core/src/zig/event-emitter.zig @@ -33,7 +33,7 @@ pub fn EventEmitter(comptime EventType: type) type { pub fn on(self: *Self, event: EventType, listener: Listener) !void { const list_ptr = self.listeners.getPtr(event) orelse { - self.listeners.put(event, .{}); + self.listeners.put(event, .empty); return self.on(event, listener); }; diff --git a/packages/core/src/zig/file-logger.zig b/packages/core/src/zig/file-logger.zig index 4bf79efd2d..149f6cf592 100644 --- a/packages/core/src/zig/file-logger.zig +++ b/packages/core/src/zig/file-logger.zig @@ -1,4 +1,6 @@ const std = @import("std"); +const builtin = @import("builtin"); +const io = if (builtin.is_test) std.testing.io else @import("root").io; pub const LogLevel = enum(u8) { err = 0, @@ -7,35 +9,36 @@ pub const LogLevel = enum(u8) { debug = 3, }; -var log_file: ?std.fs.File = null; -var log_mutex: std.Thread.Mutex = .{}; +var log_file: ?std.Io.File = null; +var log_mutex: std.Io.Mutex = .init; var initialized: bool = false; /// Initialize the file logger with a timestamped filename (called automatically on first use) fn ensureInit() void { if (initialized) return; - const timestamp = std.time.timestamp(); + const timestamp = std.Io.Clock.now(.real, io).toSeconds(); var filename_buf: [128]u8 = undefined; const filename = std.fmt.bufPrint(&filename_buf, "opentui_debug_{d}.log", .{timestamp}) catch return; - log_file = std.fs.cwd().createFile(filename, .{ .truncate = true }) catch return; + log_file = std.Io.Dir.cwd().createFile(io, filename, .{ .truncate = true }) catch return; // Log initialization const init_msg = std.fmt.bufPrint(&filename_buf, "=== Log initialized at timestamp {d} ===\n", .{timestamp}) catch return; - _ = log_file.?.write(init_msg) catch return; - log_file.?.sync() catch return; + var writer = log_file.?.writerStreaming(io, &.{}); + writer.interface.writeAll(init_msg) catch return; + log_file.?.sync(io) catch return; initialized = true; } /// Close the log file pub fn deinit() void { - log_mutex.lock(); - defer log_mutex.unlock(); + log_mutex.lockUncancelable(io); + defer log_mutex.unlock(io); if (log_file) |file| { - file.close(); + file.close(io); log_file = null; initialized = false; } @@ -43,8 +46,8 @@ pub fn deinit() void { /// Log a message with level, file, line info and immediate flush pub fn logMessage(level: LogLevel, comptime format: []const u8, args: anytype) void { - log_mutex.lock(); - defer log_mutex.unlock(); + log_mutex.lockUncancelable(io); + defer log_mutex.unlock(io); // Auto-initialize on first use if (!initialized) { @@ -62,17 +65,19 @@ pub fn logMessage(level: LogLevel, comptime format: []const u8, args: anytype) v .debug => "DEBUG", }; - const timestamp = std.time.microTimestamp(); + const timestamp = std.Io.Clock.now(.real, io).toMicroseconds(); + + var writer = log_file.?.writerStreaming(io, &.{}); const msg = std.fmt.bufPrint(&buf, "[{d}] {s}: ", .{ timestamp, level_str }) catch return; - _ = log_file.?.write(msg) catch return; + writer.interface.writeAll(msg) catch return; const user_msg = std.fmt.bufPrint(&buf, format, args) catch return; - _ = log_file.?.write(user_msg) catch return; - _ = log_file.?.write("\n") catch return; + writer.interface.writeAll(user_msg) catch return; + writer.interface.writeAll("\n") catch return; // CRITICAL: Flush immediately so logs are on disk even if we crash - log_file.?.sync() catch return; + log_file.?.sync(io) catch return; } pub fn err(comptime format: []const u8, args: anytype) void { diff --git a/packages/core/src/zig/grapheme.zig b/packages/core/src/zig/grapheme.zig index e7aabee7c9..4cf9ca9e57 100644 --- a/packages/core/src/zig/grapheme.zig +++ b/packages/core/src/zig/grapheme.zig @@ -114,7 +114,7 @@ pub const GraphemePool = struct { assert(slots_per_page[i] > 0); classes[i] = ClassPool.init(allocator, CLASS_SIZES[i], slots_per_page[i]); } - return .{ .allocator = allocator, .classes = classes, .interned_live_ids = .{} }; + return .{ .allocator = allocator, .classes = classes, .interned_live_ids = .empty }; } pub fn deinit(self: *GraphemePool) void { @@ -364,8 +364,8 @@ pub const GraphemePool = struct { .slot_capacity = slot_capacity, .slots_per_page = slots_per_page, .slot_size_bytes = slot_size_bytes, - .slots = .{}, - .free_list = .{}, + .slots = .empty, + .free_list = .empty, .num_slots = 0, }; } diff --git a/packages/core/src/zig/lib.zig b/packages/core/src/zig/lib.zig index 3da9b431e2..fd3cdc2c52 100644 --- a/packages/core/src/zig/lib.zig +++ b/packages/core/src/zig/lib.zig @@ -2,6 +2,9 @@ const std = @import("std"); const build_options = @import("build_options"); const Allocator = std.mem.Allocator; +var io_threaded: std.Io.Threaded = .init_single_threaded; +pub const io = io_threaded.io(); + const ansi = @import("ansi.zig"); const buffer = @import("buffer.zig"); const renderer = @import("renderer.zig"); @@ -220,10 +223,10 @@ export fn destroyEventSink(sink_handle: NativeHandle) void { handles.finishDestroy(token.handle); } -var gpa: std.heap.GeneralPurposeAllocator(.{ +var gpa: std.heap.DebugAllocator(.{ .enable_memory_limit = build_options.gpa_safe_stats, .safety = build_options.gpa_safe_stats, -}) = .{}; +}) = .init; const globalAllocator = gpa.allocator(); var arena = std.heap.ArenaAllocator.init(globalAllocator); const globalArena = arena.allocator(); @@ -1143,14 +1146,14 @@ export fn setTerminalTitle(renderer_handle: NativeHandle, titlePtr: ?[*]const u8 export fn copyToClipboardOSC52(renderer_handle: NativeHandle, target: u8, text_ptr: ?[*]const u8, text_len: u32) bool { const object_ptr = acquireRenderer(renderer_handle) orelse return false; - const targetEnum = std.meta.intToEnum(terminal.ClipboardTarget, target) catch .clipboard; + const targetEnum = std.enums.fromInt(terminal.ClipboardTarget, target) orelse .clipboard; const text_utf8 = sliceFromPtrLen(text_ptr, text_len); return object_ptr.copyToClipboardOSC52(targetEnum, text_utf8); } export fn clearClipboardOSC52(renderer_handle: NativeHandle, target: u8) bool { const object_ptr = acquireRenderer(renderer_handle) orelse return false; - const targetEnum = std.meta.intToEnum(terminal.ClipboardTarget, target) catch .clipboard; + const targetEnum = std.enums.fromInt(terminal.ClipboardTarget, target) orelse .clipboard; return object_ptr.clearClipboardOSC52(targetEnum); } @@ -3072,7 +3075,7 @@ export fn encodeUnicode( const is_ascii_only = utf8.isAsciiOnly(text); // Find grapheme info - var grapheme_list: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var grapheme_list: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer grapheme_list.deinit(globalAllocator); const tab_width: u8 = 2; diff --git a/packages/core/src/zig/link.zig b/packages/core/src/zig/link.zig index ce4a3a2d99..dab24eff64 100644 --- a/packages/core/src/zig/link.zig +++ b/packages/core/src/zig/link.zig @@ -44,11 +44,11 @@ pub const LinkPool = struct { .slot_capacity = slot_capacity, .slots_per_page = slots_per_page, .slot_size_bytes = slot_size_bytes, - .slots = .{}, - .free_list = .{}, + .slots = .empty, + .free_list = .empty, .num_slots = 0, .retired_slot_count = 0, - .interned_live_ids = .{}, + .interned_live_ids = .empty, }; } diff --git a/packages/core/src/zig/mem-registry.zig b/packages/core/src/zig/mem-registry.zig index 5f0f9e8916..a308f92359 100644 --- a/packages/core/src/zig/mem-registry.zig +++ b/packages/core/src/zig/mem-registry.zig @@ -21,8 +21,8 @@ pub const MemRegistry = struct { pub fn init(allocator: Allocator) MemRegistry { return .{ - .buffers = .{}, - .free_slots = .{}, + .buffers = .empty, + .free_slots = .empty, .allocator = allocator, }; } diff --git a/packages/core/src/zig/native-span-feed-bench-lib.zig b/packages/core/src/zig/native-span-feed-bench-lib.zig index 61f762bb62..acb9d21034 100644 --- a/packages/core/src/zig/native-span-feed-bench-lib.zig +++ b/packages/core/src/zig/native-span-feed-bench-lib.zig @@ -1,6 +1,8 @@ const lib = @import("lib.zig"); const bench = @import("bench/native-span-feed_bench.zig"); +pub const io = lib.io; + comptime { _ = lib; _ = bench; diff --git a/packages/core/src/zig/renderer-output.zig b/packages/core/src/zig/renderer-output.zig index 7f3f77f3d3..12159faf0b 100644 --- a/packages/core/src/zig/renderer-output.zig +++ b/packages/core/src/zig/renderer-output.zig @@ -17,12 +17,29 @@ const std = @import("std"); const builtin = @import("builtin"); +const io = if (builtin.is_test) std.testing.io else @import("root").io; const Allocator = std.mem.Allocator; const NativeSpanFeed = @import("native-span-feed.zig"); pub const OUTPUT_BUFFER_SIZE = 1024 * 1024 * 2; // 2 MiB, double-buffered per BufferedBackend for thread handoff const UTF16_BUFFER_SIZE = 4096; +const WindowsConsole = struct { + extern "kernel32" fn GetConsoleMode( + handle: std.os.windows.HANDLE, + mode: *std.os.windows.DWORD, + ) callconv(.winapi) std.os.windows.BOOL; + extern "kernel32" fn GetConsoleOutputCP() callconv(.winapi) std.os.windows.UINT; + extern "kernel32" fn SetConsoleOutputCP(code_page: std.os.windows.UINT) callconv(.winapi) std.os.windows.BOOL; + extern "kernel32" fn WriteConsoleW( + handle: std.os.windows.HANDLE, + buffer: [*]const u16, + chars_to_write: std.os.windows.DWORD, + chars_written: ?*std.os.windows.DWORD, + reserved: ?*anyopaque, + ) callconv(.winapi) std.os.windows.BOOL; +}; + pub const WriteStatus = enum(u8) { ok = 0, skipped = 1, @@ -131,24 +148,24 @@ test "UTF-8 output rejects invalid and incomplete input" { try std.testing.expectError(error.InvalidUtf8, utf8ToUtf16Chunk(&output, "\xF0\x9F")); } -fn isWindowsConsole(file: std.fs.File) bool { +fn isWindowsConsole(file: std.Io.File) bool { if (builtin.os.tag != .windows) return false; var console_mode: std.os.windows.DWORD = 0; - return std.os.windows.kernel32.GetConsoleMode(file.handle, &console_mode) != 0; + return WindowsConsole.GetConsoleMode(file.handle, &console_mode).toBool(); } pub const StdoutOutput = struct { - stdout: std.fs.File, + stdout: std.Io.File, stdoutBuffer: [4096]u8 = undefined, utf16Buffer: [UTF16_BUFFER_SIZE]u16 = undefined, windowsConsole: bool, pub fn init() StdoutOutput { - return initForFile(std.fs.File.stdout()); + return initForFile(std.Io.File.stdout()); } - fn initForFile(stdout: std.fs.File) StdoutOutput { + fn initForFile(stdout: std.Io.File) StdoutOutput { return .{ .stdout = stdout, .windowsConsole = isWindowsConsole(stdout), @@ -178,7 +195,7 @@ pub const StdoutOutput = struct { } fn writeBytes(self: *StdoutOutput, data: []const u8) void { - var stdoutWriter = self.stdout.writerStreaming(&self.stdoutBuffer); + var stdoutWriter = self.stdout.writerStreaming(io, &self.stdoutBuffer); const w = &stdoutWriter.interface; w.writeAll(data) catch {}; w.flush() catch {}; @@ -202,13 +219,13 @@ pub const StdoutOutput = struct { var remaining = data; while (remaining.len > 0) { var written: std.os.windows.DWORD = 0; - if (std.os.windows.kernel32.WriteConsoleW( + if (!WindowsConsole.WriteConsoleW( self.stdout.handle, remaining.ptr, @intCast(remaining.len), &written, null, - ) == 0 or written == 0) return false; + ).toBool() or written == 0) return false; remaining = remaining[@intCast(written)..]; } return true; @@ -217,24 +234,24 @@ pub const StdoutOutput = struct { test "StdoutOutput leaves the Windows console output code page unchanged" { if (builtin.os.tag != .windows) return error.SkipZigTest; - if (!isWindowsConsole(std.fs.File.stdout())) return error.SkipZigTest; + if (!isWindowsConsole(std.Io.File.stdout())) return error.SkipZigTest; - const original_code_page = std.os.windows.kernel32.GetConsoleOutputCP(); + const original_code_page = WindowsConsole.GetConsoleOutputCP(); if (original_code_page == 0) return error.SkipZigTest; - defer _ = std.os.windows.kernel32.SetConsoleOutputCP(original_code_page); + defer _ = WindowsConsole.SetConsoleOutputCP(original_code_page); - if (std.os.windows.kernel32.SetConsoleOutputCP(437) == 0) return error.SkipZigTest; + if (!WindowsConsole.SetConsoleOutputCP(437).toBool()) return error.SkipZigTest; const stdout_output = StdoutOutput.init(); try std.testing.expect(stdout_output.windowsConsole); - try std.testing.expectEqual(@as(u32, 437), std.os.windows.kernel32.GetConsoleOutputCP()); + try std.testing.expectEqual(@as(u32, 437), WindowsConsole.GetConsoleOutputCP()); } test "StdoutOutput preserves bytes for redirected output" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); - const file = try tmp.dir.createFile("stdout", .{ .read = true }); - defer file.close(); + const file = try tmp.dir.createFile(std.testing.io, "stdout", .{ .read = true }); + defer file.close(std.testing.io); try std.testing.expect(!isWindowsConsole(file)); var stdout_output = StdoutOutput.initForFile(file); @@ -244,14 +261,14 @@ test "StdoutOutput preserves bytes for redirected output" { stdout_output.bufferedOutput().write(expected[split..]); var actual: [expected.len]u8 = undefined; - const actual_len = try file.preadAll(&actual, 0); + const actual_len = try file.readPositionalAll(std.testing.io, &actual, 0); try std.testing.expectEqual(expected.len, actual_len); try std.testing.expectEqualStrings(expected, &actual); } pub const MemoryOutput = struct { allocator: Allocator, - bytes: std.ArrayListUnmanaged(u8) = .{}, + bytes: std.ArrayListUnmanaged(u8) = .empty, pub fn init(allocator: Allocator) MemoryOutput { return .{ .allocator = allocator }; @@ -271,6 +288,65 @@ pub const MemoryOutput = struct { } }; +fn BackendWriter( + comptime Backend: type, + comptime append: fn (*Backend, []const u8) error{BufferFull}!void, +) type { + return struct { + const Writer = @This(); + + backend: *Backend, + interface: std.Io.Writer, + + fn init(backend: *Backend) Writer { + return .{ + .backend = backend, + .interface = .{ + .vtable = &.{ .drain = drain }, + .buffer = &.{}, + }, + }; + } + + pub fn write(self: Writer, data: []const u8) error{BufferFull}!usize { + var copy = self; + return copy.interface.write(data) catch return error.BufferFull; + } + + pub fn writeAll(self: Writer, data: []const u8) error{BufferFull}!void { + var copy = self; + return copy.interface.writeAll(data) catch return error.BufferFull; + } + + pub fn writeByte(self: Writer, byte: u8) error{BufferFull}!void { + var copy = self; + return copy.interface.writeByte(byte) catch return error.BufferFull; + } + + pub fn print(self: Writer, comptime format: []const u8, args: anytype) error{BufferFull}!void { + var copy = self; + return copy.interface.print(format, args) catch return error.BufferFull; + } + + fn drain(w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize { + const self: *Writer = @alignCast(@fieldParentPtr("interface", w)); + var written: usize = 0; + + for (data[0 .. data.len - 1]) |slice| { + append(self.backend, slice) catch return error.WriteFailed; + written += slice.len; + } + + const pattern = data[data.len - 1]; + for (0..splat) |_| { + append(self.backend, pattern) catch return error.WriteFailed; + written += pattern.len; + } + return written; + } + }; +} + /// Tagged union dispatching to BufferedBackend or FeedBackend. pub const OutputBackend = union(enum) { buffered: BufferedBackend, @@ -382,8 +458,8 @@ pub const BufferedBackend = struct { useThread: bool = false, renderThread: ?std.Thread = null, - renderMutex: std.Thread.Mutex = .{}, - renderCondition: std.Thread.Condition = .{}, + renderMutex: std.Io.Mutex = .init, + renderCondition: std.Io.Condition = .init, renderRequested: bool = false, shouldTerminate: bool = false, renderInProgress: bool = false, @@ -433,9 +509,9 @@ pub const BufferedBackend = struct { /// allocator would be undefined behavior. pub fn deinit(self: *BufferedBackend) void { if (self.renderThread) |thread| { - self.renderMutex.lock(); + self.renderMutex.lockUncancelable(io); while (self.renderInProgress) { - self.renderCondition.wait(&self.renderMutex); + self.renderCondition.waitUncancelable(io, &self.renderMutex); } self.shouldTerminate = true; // Do NOT set renderRequested — the thread should wake, see @@ -443,8 +519,8 @@ pub const BufferedBackend = struct { // the stale last-frame buffer. Previously setting renderRequested // here caused a stale frame to be emitted AFTER the shutdown // ANSI sequence had already restored the terminal. - self.renderCondition.signal(); - self.renderMutex.unlock(); + self.renderCondition.signal(io); + self.renderMutex.unlock(io); thread.join(); self.renderThread = null; } @@ -469,7 +545,7 @@ pub const BufferedBackend = struct { pub fn prepareFrame(self: *BufferedBackend) WriteStatus { if (!self.useThread) return .ok; if (!self.renderMutex.tryLock()) return .skipped; - defer self.renderMutex.unlock(); + defer self.renderMutex.unlock(io); if (self.renderInProgress) return .skipped; return .ok; } @@ -496,15 +572,15 @@ pub const BufferedBackend = struct { } } else { if (self.renderThread) |thread| { - self.renderMutex.lock(); + self.renderMutex.lockUncancelable(io); while (self.renderInProgress) { - self.renderCondition.wait(&self.renderMutex); + self.renderCondition.waitUncancelable(io, &self.renderMutex); } self.shouldTerminate = true; // Wake the thread with a terminate-only signal; do not set // renderRequested (that would replay the stale buffer). - self.renderCondition.signal(); - self.renderMutex.unlock(); + self.renderCondition.signal(io); + self.renderMutex.unlock(io); thread.join(); self.renderThread = null; @@ -519,41 +595,35 @@ pub const BufferedBackend = struct { self.useThread = use_thread; } - /// Frame-time writer context. A pointer to the backend so writes know - /// which active buffer to append to. - pub const WriterCtx = struct { - backend: *BufferedBackend, - }; - - pub const Writer = std.io.GenericWriter(WriterCtx, error{BufferFull}, bufferWrite); + pub const Writer = BackendWriter(BufferedBackend, bufferWrite); - fn bufferWrite(ctx: WriterCtx, data: []const u8) error{BufferFull}!usize { - const self = ctx.backend; - const bufferLen = if (self.activeBuffer == .A) + fn bufferWrite(self: *BufferedBackend, data: []const u8) error{BufferFull}!void { + const buffer_len = if (self.activeBuffer == .A) &self.outputLenA else &self.outputLenB; - const required = bufferLen.* + data.len; + const required = std.math.add(usize, buffer_len.*, data.len) catch { + self.frameWriteFailed = true; + return error.BufferFull; + }; const buffer = if (self.activeBuffer == .A) &self.outputA else &self.outputB; if (required > buffer.*.len) { - const capacity = @max(required, buffer.*.len * 2); + const doubled_capacity = std.math.mul(usize, buffer.*.len, 2) catch required; + const capacity = @max(required, doubled_capacity); buffer.* = self.allocator.realloc(buffer.*, capacity) catch { self.frameWriteFailed = true; return error.BufferFull; }; } - @memcpy(buffer.*[bufferLen.*..][0..data.len], data); - bufferLen.* += data.len; - return data.len; + @memcpy(buffer.*[buffer_len.*..][0..data.len], data); + buffer_len.* = required; } - // TODO: std.io.GenericWriter is deprecated but the replacement is much more involved. - // Migrate when the ecosystem stabilizes. pub fn writer(self: *BufferedBackend) Writer { - return .{ .context = .{ .backend = self } }; + return .init(self); } pub fn beginFrame(self: *BufferedBackend) void { @@ -609,13 +679,13 @@ pub const BufferedBackend = struct { self.hasCommittedFrame = true; return .ok; } - const writeStart = std.time.microTimestamp(); + const writeStart = std.Io.Clock.awake.now(io); const committed_buffer = self.activeBuffer; if (self.useThread) { - self.renderMutex.lock(); + self.renderMutex.lockUncancelable(io); while (self.renderInProgress) { - self.renderCondition.wait(&self.renderMutex); + self.renderCondition.waitUncancelable(io, &self.renderMutex); } // Hand off the just-written buffer to the render thread and flip @@ -632,15 +702,15 @@ pub const BufferedBackend = struct { self.renderRequested = true; self.renderInProgress = true; - self.renderCondition.signal(); - self.renderMutex.unlock(); + self.renderCondition.signal(io); + self.renderMutex.unlock(io); } else { const to_write = if (self.activeBuffer == .A) self.outputA[0..self.outputLenA] else self.outputB[0..self.outputLenB]; self.output.write(to_write); - self.lastWriteTimeUs = @as(f64, @floatFromInt(std.time.microTimestamp() - writeStart)); + self.lastWriteTimeUs = @as(f64, @floatFromInt(writeStart.untilNow(io, .awake).toMicroseconds())); } self.lastCommittedBuffer = committed_buffer; @@ -650,16 +720,16 @@ pub const BufferedBackend = struct { fn renderThreadFn(self: *BufferedBackend) void { while (true) { - self.renderMutex.lock(); + self.renderMutex.lockUncancelable(io); while (!self.renderRequested and !self.shouldTerminate) { - self.renderCondition.wait(&self.renderMutex); + self.renderCondition.waitUncancelable(io, &self.renderMutex); } // Terminate wins: when shouldTerminate is set, exit without // writing even if a render was also requested. This keeps // shutdown-ANSI the last thing on the wire. if (self.shouldTerminate) { - self.renderMutex.unlock(); + self.renderMutex.unlock(io); break; } @@ -668,14 +738,14 @@ pub const BufferedBackend = struct { const outputData = self.currentOutputBuffer; const outputLen = self.currentOutputLen; - const writeStart = std.time.microTimestamp(); + const writeStart = std.Io.Clock.awake.now(io); self.output.write(outputData[0..outputLen]); - self.lastWriteTimeUs = @as(f64, @floatFromInt(std.time.microTimestamp() - writeStart)); + self.lastWriteTimeUs = @as(f64, @floatFromInt(writeStart.untilNow(io, .awake).toMicroseconds())); self.renderInProgress = false; - self.renderCondition.signal(); - self.renderMutex.unlock(); + self.renderCondition.signal(io); + self.renderMutex.unlock(io); } } @@ -683,11 +753,11 @@ pub const BufferedBackend = struct { if (data.len == 0) return; if (self.useThread) { - self.renderMutex.lock(); + self.renderMutex.lockUncancelable(io); while (self.renderInProgress) { - self.renderCondition.wait(&self.renderMutex); + self.renderCondition.waitUncancelable(io, &self.renderMutex); } - self.renderMutex.unlock(); + self.renderMutex.unlock(io); } self.output.write(data); @@ -695,11 +765,11 @@ pub const BufferedBackend = struct { pub fn writeOutMultiple(self: *BufferedBackend, data_slices: []const []const u8) void { if (self.useThread) { - self.renderMutex.lock(); + self.renderMutex.lockUncancelable(io); while (self.renderInProgress) { - self.renderCondition.wait(&self.renderMutex); + self.renderCondition.waitUncancelable(io, &self.renderMutex); } - self.renderMutex.unlock(); + self.renderMutex.unlock(io); } var totalLen: usize = 0; @@ -799,23 +869,17 @@ pub const FeedBackend = struct { return false; } - pub const WriterCtx = struct { - backend: *FeedBackend, - }; - - pub const Writer = std.io.GenericWriter(WriterCtx, error{BufferFull}, frameWrite); + pub const Writer = BackendWriter(FeedBackend, frameWrite); - fn frameWrite(ctx: WriterCtx, data: []const u8) error{BufferFull}!usize { - const self = ctx.backend; + fn frameWrite(self: *FeedBackend, data: []const u8) error{BufferFull}!void { self.frameBytes.appendSlice(self.feed.allocator, data) catch { self.frameWriteFailed = true; return error.BufferFull; }; - return data.len; } pub fn writer(self: *FeedBackend) Writer { - return .{ .context = .{ .backend = self } }; + return .init(self); } pub fn beginFrame(self: *FeedBackend) void { @@ -828,7 +892,7 @@ pub const FeedBackend = struct { } pub fn endFrame(self: *FeedBackend) WriteStatus { - const writeStart = std.time.microTimestamp(); + const writeStart = std.Io.Clock.awake.now(io); var status: WriteStatus = .ok; if (self.frameWriteFailed) { @@ -839,7 +903,7 @@ pub const FeedBackend = struct { }; } - self.lastWriteTimeUs = @as(f64, @floatFromInt(std.time.microTimestamp() - writeStart)); + self.lastWriteTimeUs = @as(f64, @floatFromInt(writeStart.untilNow(io, .awake).toMicroseconds())); return status; } diff --git a/packages/core/src/zig/renderer.zig b/packages/core/src/zig/renderer.zig index d3fa5b4efd..833b82f961 100644 --- a/packages/core/src/zig/renderer.zig +++ b/packages/core/src/zig/renderer.zig @@ -1,4 +1,6 @@ const std = @import("std"); +const builtin = @import("builtin"); +const io = if (builtin.is_test) std.testing.io else @import("root").io; const Allocator = std.mem.Allocator; const ansi = @import("ansi.zig"); const buf = @import("buffer.zig"); @@ -292,8 +294,8 @@ pub const CliRenderer = struct { palette_epoch: u32, last_rendered_palette_epoch: ?u32 = null, force_full_repaint: bool = false, - palette_index_cache: std.AutoHashMapUnmanaged(u64, u8) = .{}, - sixelCache: std.AutoHashMapUnmanaged(SixelCacheKey, SixelCacheEntry) = .{}, + palette_index_cache: std.AutoHashMapUnmanaged(u64, u8) = .empty, + sixelCache: std.AutoHashMapUnmanaged(SixelCacheKey, SixelCacheEntry) = .empty, sixelCacheBytes: usize = 0, sixelCacheClock: u64 = 0, sixelCacheHits: u64 = 0, @@ -314,7 +316,7 @@ pub const CliRenderer = struct { clearOnShutdown: bool = true, // Optional override for terminal environment lookups. Borrowed: the // caller owns the map and must keep it alive for the renderer's lifetime. - env_map: ?*const std.process.EnvMap = null, + env_map: ?*const std.process.Environ.Map = null, }; pub fn create(allocator: Allocator, width: u32, height: u32, pool: *gp.GraphemePool) !*CliRenderer { @@ -337,19 +339,19 @@ pub const CliRenderer = struct { errdefer nextBuffer.deinit(); // stat sample arrays - var lastFrameTime: std.ArrayListUnmanaged(f64) = .{}; + var lastFrameTime: std.ArrayListUnmanaged(f64) = .empty; errdefer lastFrameTime.deinit(allocator); - var renderTime: std.ArrayListUnmanaged(f64) = .{}; + var renderTime: std.ArrayListUnmanaged(f64) = .empty; errdefer renderTime.deinit(allocator); - var overallFrameTime: std.ArrayListUnmanaged(f64) = .{}; + var overallFrameTime: std.ArrayListUnmanaged(f64) = .empty; errdefer overallFrameTime.deinit(allocator); - var bufferResetTime: std.ArrayListUnmanaged(f64) = .{}; + var bufferResetTime: std.ArrayListUnmanaged(f64) = .empty; errdefer bufferResetTime.deinit(allocator); - var outputWriteTime: std.ArrayListUnmanaged(f64) = .{}; + var outputWriteTime: std.ArrayListUnmanaged(f64) = .empty; errdefer outputWriteTime.deinit(allocator); - var cellsUpdated: std.ArrayListUnmanaged(u32) = .{}; + var cellsUpdated: std.ArrayListUnmanaged(u32) = .empty; errdefer cellsUpdated.deinit(allocator); - var frameCallbackTimes: std.ArrayListUnmanaged(f64) = .{}; + var frameCallbackTimes: std.ArrayListUnmanaged(f64) = .empty; errdefer frameCallbackTimes.deinit(allocator); try lastFrameTime.ensureTotalCapacity(allocator, STAT_SAMPLE_CAPACITY); @@ -367,7 +369,7 @@ pub const CliRenderer = struct { errdefer allocator.free(nextHitGrid); @memset(currentHitGrid, 0); // Initialize with 0 (no renderable) @memset(nextHitGrid, 0); - const hitScissorStack: std.ArrayListUnmanaged(buf.ClipRect) = .{}; + const hitScissorStack: std.ArrayListUnmanaged(buf.ClipRect) = .empty; // Backend variant selected once by opts.output. var backend: OutputBackend = switch (opts.output) { @@ -420,7 +422,7 @@ pub const CliRenderer = struct { .cellsUpdated = cellsUpdated, .frameCallbackTime = frameCallbackTimes, }, - .lastRenderTime = std.time.microTimestamp(), + .lastRenderTime = std.Io.Clock.now(.awake, io).toMicroseconds(), .allocator = allocator, .currentHitGrid = currentHitGrid, .nextHitGrid = nextHitGrid, @@ -485,19 +487,19 @@ pub const CliRenderer = struct { // Build capability query into a stack buffer, then emit via backend. // Buffer sized to accommodate all capability-query sequences with margin. var queryBuf: [4096]u8 = undefined; - var stream = std.io.fixedBufferStream(&queryBuf); - self.terminal.queryTerminalSend(stream.writer()) catch { + var writer: std.Io.Writer = .fixed(&queryBuf); + self.terminal.queryTerminalSend(&writer) catch { logger.warn("Failed to query terminal capabilities", .{}); }; - self.backend.writeOut(stream.getWritten()); + self.backend.writeOut(writer.buffered()); self.setupTerminalWithoutDetection(useAlternateScreen, true); } fn setupTerminalWithoutDetection(self: *CliRenderer, useAlternateScreen: bool, reserve_non_alt_surface: bool) void { var setupBuf: [4096]u8 = undefined; - var stream = std.io.fixedBufferStream(&setupBuf); - const writer = stream.writer(); + var fixed_writer: std.Io.Writer = .fixed(&setupBuf); + const writer = &fixed_writer; writer.writeAll(ansi.ANSI.saveCursorState) catch {}; @@ -511,7 +513,7 @@ pub const CliRenderer = struct { const useKitty = self.terminal.opts.kitty_keyboard_flags > 0; self.terminal.enableDetectedFeatures(writer, useKitty) catch {}; - self.backend.writeOut(stream.getWritten()); + self.backend.writeOut(fixed_writer.buffered()); } pub fn suspendRenderer(self: *CliRenderer) void { @@ -556,8 +558,8 @@ pub const CliRenderer = struct { // Build the shutdown ANSI sequence into a stack buffer, then emit. var shutdownBuf: [4096]u8 = undefined; - var stream = std.io.fixedBufferStream(&shutdownBuf); - const writer = stream.writer(); + var fixed_writer: std.Io.Writer = .fixed(&shutdownBuf); + const writer = &fixed_writer; self.terminal.resetState(writer) catch { logger.warn("Failed to reset terminal state", .{}); @@ -579,13 +581,13 @@ pub const CliRenderer = struct { writer.writeAll(ansi.ANSI.defaultCursorStyle) catch {}; writer.writeAll(ansi.ANSI.showCursor) catch {}; - self.backend.writeOut(stream.getWritten()); + self.backend.writeOut(fixed_writer.buffered()); // Workaround for Ghostty not showing the cursor after shutdown for some reason. // Keep this backend-agnostic: the active output transport owns delivery. - std.Thread.sleep(10 * std.time.ns_per_ms); + io.sleep(.fromMilliseconds(10), .awake) catch {}; self.backend.writeOut(ansi.ANSI.showCursor); - std.Thread.sleep(10 * std.time.ns_per_ms); + io.sleep(.fromMilliseconds(10), .awake) catch {}; } pub fn setClearOnShutdown(self: *CliRenderer, clear: bool) void { @@ -824,10 +826,10 @@ pub const CliRenderer = struct { pub fn setRenderOffset(self: *CliRenderer, offset: u32) void { if (self.terminalSetup and !self.useAlternateScreen and self.renderOffset > 0 and offset == 0) { var clearBuf: [256]u8 = undefined; - var stream = std.io.fixedBufferStream(&clearBuf); - const writer = stream.writer(); + var fixed_writer: std.Io.Writer = .fixed(&clearBuf); + const writer = &fixed_writer; self.clearSplitFooterSurface(writer); - self.backend.writeOut(stream.getWritten()); + self.backend.writeOut(fixed_writer.buffered()); } self.renderOffset = offset; @@ -909,7 +911,7 @@ pub const CliRenderer = struct { return self.finishSkippedFrame(); } - const now = std.time.microTimestamp(); + const now = std.Io.Clock.now(.awake, io).toMicroseconds(); const deltaTimeMs = @as(f64, @floatFromInt(now - self.lastRenderTime)); const deltaTime = deltaTimeMs / 1000.0; @@ -1045,7 +1047,7 @@ pub const CliRenderer = struct { return self.renderResult(status); } - const now = std.time.microTimestamp(); + const now = std.Io.Clock.now(.awake, io).toMicroseconds(); const deltaTimeMs = @as(f64, @floatFromInt(now - self.lastRenderTime)); const deltaTime = deltaTimeMs / 1000.0; @@ -1087,7 +1089,7 @@ pub const CliRenderer = struct { return self.renderResult(status); } - const now = std.time.microTimestamp(); + const now = std.Io.Clock.now(.awake, io).toMicroseconds(); const deltaTimeMs = @as(f64, @floatFromInt(now - self.lastRenderTime)); const deltaTime = deltaTimeMs / 1000.0; @@ -2286,7 +2288,7 @@ pub const CliRenderer = struct { /// `sync_started` is true only when the caller already opened the /// synchronized-update envelope for a batched split-footer commit. pub fn prepareRenderFrameWithWriter(self: *CliRenderer, writer: anytype, force: bool, sync_started: bool) void { - const renderStartTime = std.time.microTimestamp(); + const renderStartTime = std.Io.Clock.now(.awake, io).toMicroseconds(); var cellsUpdated: u32 = 0; const palette_force = self.last_rendered_palette_epoch == null or self.last_rendered_palette_epoch.? != self.palette_epoch; const should_force = force or self.force_full_repaint or palette_force; @@ -2725,7 +2727,7 @@ pub const CliRenderer = struct { writer.writeAll(ansi.ANSI.syncReset) catch {}; } - const renderEndTime = std.time.microTimestamp(); + const renderEndTime = std.Io.Clock.now(.awake, io).toMicroseconds(); const renderTime = @as(f64, @floatFromInt(renderEndTime - renderStartTime)); self.renderStats.cellsUpdated = cellsUpdated; @@ -2947,15 +2949,15 @@ pub const CliRenderer = struct { } pub fn dumpHitGrid(self: *CliRenderer) void { - const timestamp = std.time.timestamp(); + const timestamp = std.Io.Clock.now(.real, io).toSeconds(); var filename_buf: [64]u8 = undefined; const filename = std.fmt.bufPrint(&filename_buf, "hitgrid_{d}.txt", .{timestamp}) catch return; - const file = std.fs.cwd().createFile(filename, .{}) catch return; - defer file.close(); + const file = std.Io.Dir.cwd().createFile(io, filename, .{}) catch return; + defer file.close(io); var fileBuffer: [4096]u8 = undefined; - var fileWriter = file.writer(&fileBuffer); + var fileWriter = file.writer(io, &fileBuffer); const writer = &fileWriter.interface; for (0..self.hitGridHeight) |y| { @@ -2972,7 +2974,7 @@ pub const CliRenderer = struct { } fn dumpSingleBuffer(self: *CliRenderer, buffer: *OptimizedBuffer, buffer_name: []const u8, timestamp: i64) void { - std.fs.cwd().makeDir("buffer_dump") catch |err| switch (err) { + std.Io.Dir.cwd().createDir(io, "buffer_dump", .default_dir) catch |err| switch (err) { error.PathAlreadyExists => {}, else => return, }; @@ -2980,11 +2982,11 @@ pub const CliRenderer = struct { var filename_buf: [128]u8 = undefined; const filename = std.fmt.bufPrint(&filename_buf, "buffer_dump/{s}_buffer_{d}.txt", .{ buffer_name, timestamp }) catch return; - const file = std.fs.cwd().createFile(filename, .{}) catch return; - defer file.close(); + const file = std.Io.Dir.cwd().createFile(io, filename, .{}) catch return; + defer file.close(io); var fileBuffer: [4096]u8 = undefined; - var fileWriter = file.writer(&fileBuffer); + var fileWriter = file.writer(io, &fileBuffer); const writer = &fileWriter.interface; writer.print("{s} Buffer ({d}x{d}):\n", .{ buffer_name, self.width, self.height }) catch return; @@ -3022,7 +3024,7 @@ pub const CliRenderer = struct { /// Dump the last rendered output to a file. Backend-specific formatting /// is delegated to `backend.dumpTo(writer)` — no tag inspection here. pub fn dumpOutputBuffer(self: *CliRenderer, timestamp: i64) void { - std.fs.cwd().makeDir("buffer_dump") catch |err| switch (err) { + std.Io.Dir.cwd().createDir(io, "buffer_dump", .default_dir) catch |err| switch (err) { error.PathAlreadyExists => {}, else => return, }; @@ -3030,11 +3032,11 @@ pub const CliRenderer = struct { var filename_buf: [128]u8 = undefined; const filename = std.fmt.bufPrint(&filename_buf, "buffer_dump/output_buffer_{d}.txt", .{timestamp}) catch return; - const file = std.fs.cwd().createFile(filename, .{}) catch return; - defer file.close(); + const file = std.Io.Dir.cwd().createFile(io, filename, .{}) catch return; + defer file.close(io); var fileBuffer: [4096]u8 = undefined; - var fileWriter = file.writer(&fileBuffer); + var fileWriter = file.writer(io, &fileBuffer); const writer = &fileWriter.interface; writer.print("Output Buffer Dump (timestamp: {d}):\n", .{timestamp}) catch return; @@ -3053,15 +3055,15 @@ pub const CliRenderer = struct { } pub fn restoreTerminalModes(self: *CliRenderer) void { - var stream = std.io.fixedBufferStream(&self.writeOutBuf); - self.terminal.restoreTerminalModes(stream.writer()) catch {}; - self.writeOut(stream.getWritten()); + var writer: std.Io.Writer = .fixed(&self.writeOutBuf); + self.terminal.restoreTerminalModes(&writer) catch {}; + self.writeOut(writer.buffered()); } pub fn enableMouse(self: *CliRenderer, enableMovement: bool) void { - var stream = std.io.fixedBufferStream(&self.writeOutBuf); - self.terminal.setMouseMode(stream.writer(), true, enableMovement) catch {}; - self.writeOut(stream.getWritten()); + var writer: std.Io.Writer = .fixed(&self.writeOutBuf); + self.terminal.setMouseMode(&writer, true, enableMovement) catch {}; + self.writeOut(writer.buffered()); } pub fn queryPixelResolution(self: *CliRenderer) void { @@ -3069,27 +3071,27 @@ pub const CliRenderer = struct { } pub fn queryThemeColors(self: *CliRenderer) void { - var stream = std.io.fixedBufferStream(&self.writeOutBuf); - self.terminal.queryThemeColors(stream.writer()) catch {}; - self.writeOut(stream.getWritten()); + var writer: std.Io.Writer = .fixed(&self.writeOutBuf); + self.terminal.queryThemeColors(&writer) catch {}; + self.writeOut(writer.buffered()); } pub fn disableMouse(self: *CliRenderer) void { - var stream = std.io.fixedBufferStream(&self.writeOutBuf); - self.terminal.setMouseMode(stream.writer(), false, self.terminal.state.mouse_movement) catch {}; - self.writeOut(stream.getWritten()); + var writer: std.Io.Writer = .fixed(&self.writeOutBuf); + self.terminal.setMouseMode(&writer, false, self.terminal.state.mouse_movement) catch {}; + self.writeOut(writer.buffered()); } pub fn enableKittyKeyboard(self: *CliRenderer, flags: u8) void { - var stream = std.io.fixedBufferStream(&self.writeOutBuf); - self.terminal.setKittyKeyboard(stream.writer(), true, flags) catch {}; - self.writeOut(stream.getWritten()); + var writer: std.Io.Writer = .fixed(&self.writeOutBuf); + self.terminal.setKittyKeyboard(&writer, true, flags) catch {}; + self.writeOut(writer.buffered()); } pub fn disableKittyKeyboard(self: *CliRenderer) void { - var stream = std.io.fixedBufferStream(&self.writeOutBuf); - self.terminal.setKittyKeyboard(stream.writer(), false, 0) catch {}; - self.writeOut(stream.getWritten()); + var writer: std.Io.Writer = .fixed(&self.writeOutBuf); + self.terminal.setKittyKeyboard(&writer, false, 0) catch {}; + self.writeOut(writer.buffered()); } pub fn getTerminalCapabilities(self: *CliRenderer) Terminal.Capabilities { @@ -3103,14 +3105,14 @@ pub const CliRenderer = struct { pub fn processCapabilityResponse(self: *CliRenderer, response: []const u8) void { self.terminal.processCapabilityResponse(response); - var stream = std.io.fixedBufferStream(&self.writeOutBuf); - _ = self.terminal.sendPendingQueries(stream.writer()) catch |err| blk: { + var writer: std.Io.Writer = .fixed(&self.writeOutBuf); + _ = self.terminal.sendPendingQueries(&writer) catch |err| blk: { logger.warn("Failed to send pending queries: {}", .{err}); break :blk false; }; const useKitty = self.terminal.opts.kitty_keyboard_flags > 0; - self.terminal.enableDetectedFeatures(stream.writer(), useKitty) catch {}; - self.writeOut(stream.getWritten()); + self.terminal.enableDetectedFeatures(&writer, useKitty) catch {}; + self.writeOut(writer.buffered()); } pub fn setKittyKeyboardFlags(self: *CliRenderer, flags: u8) void { @@ -3122,9 +3124,9 @@ pub const CliRenderer = struct { } pub fn setTerminalTitle(self: *CliRenderer, title: []const u8) void { - var stream = std.io.fixedBufferStream(&self.writeOutBuf); - self.terminal.setTerminalTitle(stream.writer(), title); - self.writeOut(stream.getWritten()); + var writer: std.Io.Writer = .fixed(&self.writeOutBuf); + self.terminal.setTerminalTitle(&writer, title); + self.writeOut(writer.buffered()); } pub fn copyToClipboardOSC52(self: *CliRenderer, target: Terminal.ClipboardTarget, text_utf8: []const u8) bool { @@ -3132,29 +3134,29 @@ pub const CliRenderer = struct { const output_bytes = self.allocator.alloc(u8, output_len) catch return false; defer self.allocator.free(output_bytes); - var stream = std.io.fixedBufferStream(output_bytes); - self.terminal.writeClipboard(stream.writer(), target, text_utf8) catch return false; - const written = stream.getWritten(); + var writer: std.Io.Writer = .fixed(output_bytes); + self.terminal.writeClipboard(&writer, target, text_utf8) catch return false; + const written = writer.buffered(); std.debug.assert(written.len == output_len); self.writeOut(written); return true; } pub fn clearClipboardOSC52(self: *CliRenderer, target: Terminal.ClipboardTarget) bool { - var stream: std.ArrayListUnmanaged(u8) = .{}; - defer stream.deinit(self.allocator); - self.terminal.writeClipboard(stream.writer(self.allocator), target, "") catch return false; - self.writeOut(stream.items); + var stream: std.Io.Writer.Allocating = .init(self.allocator); + defer stream.deinit(); + self.terminal.writeClipboard(&stream.writer, target, "") catch return false; + self.writeOut(stream.written()); return true; } pub fn triggerNotification(self: *CliRenderer, message: []const u8, title: ?[]const u8) bool { - var stream: std.ArrayListUnmanaged(u8) = .{}; - defer stream.deinit(self.allocator); + var stream: std.Io.Writer.Allocating = .init(self.allocator); + defer stream.deinit(); - const ok = self.terminal.writeNotification(self.allocator, stream.writer(self.allocator), message, title) catch return false; + const ok = self.terminal.writeNotification(self.allocator, &stream.writer, message, title) catch return false; if (!ok) return false; - self.writeOut(stream.items); + self.writeOut(stream.written()); return true; } diff --git a/packages/core/src/zig/rope.zig b/packages/core/src/zig/rope.zig index 600dfd9ef3..4a1246c9d8 100644 --- a/packages/core/src/zig/rope.zig +++ b/packages/core/src/zig/rope.zig @@ -318,7 +318,7 @@ 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.ArrayListUnmanaged(*const Node) = .{}; + var leaves: std.ArrayListUnmanaged(*const Node) = .empty; defer leaves.deinit(tmp_allocator); try leaves.ensureTotalCapacity(tmp_allocator, self.count()); @@ -524,7 +524,7 @@ pub fn Rope(comptime T: type) type { return try initWithConfig(allocator, config); } - var leaves: std.ArrayListUnmanaged(*const Node) = .{}; + var leaves: std.ArrayListUnmanaged(*const Node) = .empty; defer leaves.deinit(allocator); try leaves.ensureTotalCapacity(allocator, items.len); @@ -691,7 +691,7 @@ pub fn Rope(comptime T: type) type { }; var context = SliceContext{ - .items = .{}, + .items = .empty, .allocator = allocator, .start = start, .end = end, @@ -742,7 +742,7 @@ pub fn Rope(comptime T: type) type { }; var context = ToArrayContext{ - .items = .{}, + .items = .empty, .allocator = allocator, }; errdefer context.items.deinit(allocator); @@ -752,27 +752,27 @@ pub fn Rope(comptime T: type) type { } pub fn toText(self: *const Self, allocator: Allocator) ![]u8 { - var buffer: std.ArrayListUnmanaged(u8) = .{}; - errdefer buffer.deinit(allocator); + var buffer: std.Io.Writer.Allocating = .init(allocator); + defer buffer.deinit(); - try buffer.appendSlice(allocator, "[root"); - try nodeToText(self.root, &buffer, allocator); - try buffer.append(allocator, ']'); + try buffer.writer.writeAll("[root"); + try nodeToText(self.root, &buffer.writer); + try buffer.writer.writeByte(']'); - return buffer.toOwnedSlice(allocator); + return buffer.toOwnedSlice(); } - fn nodeToText(node: *const Node, buffer: *std.ArrayListUnmanaged(u8), allocator: Allocator) !void { + fn nodeToText(node: *const Node, writer: *std.Io.Writer) !void { switch (node.*) { .branch => |*b| { - try buffer.appendSlice(allocator, "[branch"); - try nodeToText(b.left, buffer, allocator); - try nodeToText(b.right, buffer, allocator); - try buffer.append(allocator, ']'); + try writer.writeAll("[branch"); + try nodeToText(b.left, writer); + try nodeToText(b.right, writer); + try writer.writeByte(']'); }, .leaf => |*l| { if (l.is_sentinel) { - try buffer.appendSlice(allocator, "[empty]"); + try writer.writeAll("[empty]"); return; } @@ -780,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(allocator, '['); - try buffer.appendSlice(allocator, tag_name); + try writer.writeByte('['); + try writer.writeAll(tag_name); if (@hasDecl(T, "Metrics")) { const metrics = l.metrics(); - try buffer.append(allocator, ':'); - try buffer.writer(allocator).print("w{d}", .{metrics.weight()}); + try writer.writeByte(':'); + try writer.print("w{d}", .{metrics.weight()}); if (@hasDecl(T.Metrics, "total_width")) { - try buffer.writer(allocator).print(",tw{d}", .{metrics.custom.total_width}); + try writer.print(",tw{d}", .{metrics.custom.total_width}); } if (@hasDecl(T.Metrics, "total_bytes")) { - try buffer.writer(allocator).print(",b{d}", .{metrics.custom.total_bytes}); + try writer.print(",b{d}", .{metrics.custom.total_bytes}); } } - try buffer.append(allocator, ']'); + try writer.writeByte(']'); } else { - try buffer.appendSlice(allocator, "[leaf"); + try writer.writeAll("[leaf"); if (@hasDecl(T, "Metrics")) { const metrics = l.metrics(); - try buffer.append(allocator, ':'); - try buffer.writer(allocator).print("w{d}", .{metrics.weight()}); + try writer.writeByte(':'); + try writer.print("w{d}", .{metrics.weight()}); } - try buffer.append(allocator, ']'); + try writer.writeByte(']'); } }, } @@ -930,7 +930,7 @@ pub fn Rope(comptime T: type) type { // Handle insertion if (action.insert_between.len > 0) { - var leaves: std.ArrayListUnmanaged(*const Node) = .{}; + var leaves: std.ArrayListUnmanaged(*const Node) = .empty; defer leaves.deinit(self.allocator); try leaves.ensureTotalCapacity(self.allocator, action.insert_between.len); @@ -1121,7 +1121,7 @@ pub fn Rope(comptime T: type) type { return; } - var leaves: std.ArrayListUnmanaged(*const Node) = .{}; + var leaves: std.ArrayListUnmanaged(*const Node) = .empty; defer leaves.deinit(self.allocator); try leaves.ensureTotalCapacity(self.allocator, items.len); @@ -1171,7 +1171,7 @@ pub fn Rope(comptime T: type) type { return .{ .keep_walking = false, .err = e }; }; if (!gop.found_existing) { - gop.value_ptr.* = .{}; + gop.value_ptr.* = .empty; } gop.value_ptr.append(context.cache.allocator, .{ diff --git a/packages/core/src/zig/syntax-style.zig b/packages/core/src/zig/syntax-style.zig index b168feec08..f305c40041 100644 --- a/packages/core/src/zig/syntax-style.zig +++ b/packages/core/src/zig/syntax-style.zig @@ -48,10 +48,10 @@ pub const SyntaxStyle = struct { .allocator = internal_allocator, .global_allocator = global_allocator, .arena = internal_arena, - .name_to_id = .{}, - .id_to_style = .{}, + .name_to_id = .empty, + .id_to_style = .empty, .next_id = 1, // Start from 1, 0 can be used as "invalid" - .merged_cache = .{}, + .merged_cache = .empty, .emitter = events.EventEmitter(Event).init(internal_allocator), }; @@ -113,15 +113,14 @@ pub const SyntaxStyle = struct { pub fn mergeStyles(self: *SyntaxStyle, ids: []const u32) SyntaxStyleError!StyleDefinition { var cache_key_buffer: [512]u8 = undefined; - var cache_key_stream = std.io.fixedBufferStream(&cache_key_buffer); - const writer = cache_key_stream.writer(); + var writer: std.Io.Writer = .fixed(&cache_key_buffer); for (ids, 0..) |id, i| { if (i > 0) writer.writeByte(':') catch return SyntaxStyleError.OutOfMemory; writer.print("{d}", .{id}) catch return SyntaxStyleError.OutOfMemory; } - const cache_key = cache_key_stream.getWritten(); + const cache_key = writer.buffered(); if (self.merged_cache.get(cache_key)) |cached| { return cached; diff --git a/packages/core/src/zig/terminal.zig b/packages/core/src/zig/terminal.zig index 92dce32c81..852235fa61 100644 --- a/packages/core/src/zig/terminal.zig +++ b/packages/core/src/zig/terminal.zig @@ -4,7 +4,6 @@ const atomic = std.atomic; const assert = std.debug.assert; const ansi = @import("ansi.zig"); const utf8 = @import("utf8.zig"); -const logger = @import("logger.zig"); const WidthMethod = utf8.WidthMethod; @@ -136,8 +135,8 @@ pub const Options = struct { // Use 0b00111 (7) to also enable event types for key release detection kitty_keyboard_flags: u8 = 0b00101, remote_mode: RemoteMode = .local, - // Optional override for environment lookups. Caller owns the map. - env_map: ?*const std.process.EnvMap = null, + // Optional forwarded environment values. Caller owns the map. + env_map: ?*const std.process.Environ.Map = null, }; pub const TerminalInfo = struct { @@ -150,7 +149,7 @@ pub const TerminalInfo = struct { caps: Capabilities = .{}, opts: Options = .{}, -host_env_map: ?std.process.EnvMap = null, +host_env_map: ?std.process.Environ.Map = null, remote: bool = false, multiplexer: Multiplexer = .none, osc52_support: Osc52Support = .unknown, @@ -219,7 +218,7 @@ pub fn deinit(self: *Terminal) void { pub fn setHostEnvVar(self: *Terminal, allocator: std.mem.Allocator, key: []const u8, value: []const u8) !void { if (self.host_env_map == null) { - self.host_env_map = std.process.EnvMap.init(allocator); + self.host_env_map = std.process.Environ.Map.init(allocator); } const env_map = &self.host_env_map.?; @@ -536,8 +535,8 @@ fn detectNotificationProtocol(value: []const u8) ?NotificationProtocol { // OSC 99 is explicitly documented by kitty and supported by foot. Prefer it // where the terminal family is known because it has the only real queryable // notification protocol and robust text encoding. - if (std.ascii.indexOfIgnoreCase(value, "kitty") != null or - std.ascii.indexOfIgnoreCase(value, "foot") != null) + if (std.ascii.findIgnoreCase(value, "kitty") != null or + std.ascii.findIgnoreCase(value, "foot") != null) { return .osc99; } @@ -545,21 +544,21 @@ fn detectNotificationProtocol(value: []const u8) ?NotificationProtocol { // OSC 777 is documented by WezTerm, Warp, hterm/Blink, and is the // rxvt/VTE-style title+body notification sequence. Use it where sources // document support, or where the terminal is clearly VTE/rxvt-derived. - if (std.ascii.indexOfIgnoreCase(value, "ghostty") != null or - std.ascii.indexOfIgnoreCase(value, "wezterm") != null or - std.ascii.indexOfIgnoreCase(value, "warp") != null or - std.ascii.indexOfIgnoreCase(value, "hterm") != null or - std.ascii.indexOfIgnoreCase(value, "blink") != null or - std.ascii.indexOfIgnoreCase(value, "contour") != null or - std.ascii.indexOfIgnoreCase(value, "vte") != null or - std.ascii.indexOfIgnoreCase(value, "gnome") != null or - std.ascii.indexOfIgnoreCase(value, "tilix") != null or - std.ascii.indexOfIgnoreCase(value, "terminator") != null or - std.ascii.indexOfIgnoreCase(value, "xfce") != null or - std.ascii.indexOfIgnoreCase(value, "urxvt") != null or - std.ascii.indexOfIgnoreCase(value, "rxvt") != null or - std.ascii.indexOfIgnoreCase(value, "windows terminal") != null or - std.ascii.indexOfIgnoreCase(value, "windows_terminal") != null) + if (std.ascii.findIgnoreCase(value, "ghostty") != null or + std.ascii.findIgnoreCase(value, "wezterm") != null or + std.ascii.findIgnoreCase(value, "warp") != null or + std.ascii.findIgnoreCase(value, "hterm") != null or + std.ascii.findIgnoreCase(value, "blink") != null or + std.ascii.findIgnoreCase(value, "contour") != null or + std.ascii.findIgnoreCase(value, "vte") != null or + std.ascii.findIgnoreCase(value, "gnome") != null or + std.ascii.findIgnoreCase(value, "tilix") != null or + std.ascii.findIgnoreCase(value, "terminator") != null or + std.ascii.findIgnoreCase(value, "xfce") != null or + std.ascii.findIgnoreCase(value, "urxvt") != null or + std.ascii.findIgnoreCase(value, "rxvt") != null or + std.ascii.findIgnoreCase(value, "windows terminal") != null or + std.ascii.findIgnoreCase(value, "windows_terminal") != null) { return .osc777; } @@ -567,10 +566,10 @@ fn detectNotificationProtocol(value: []const u8) ?NotificationProtocol { // OSC 9 is the iTerm2 notification sequence. ConEmu also uses OSC 9 for // multiple extensions, so use it only for terminal families with documented // notification support rather than as a generic fallback. - if (std.ascii.indexOfIgnoreCase(value, "iterm") != null or - std.ascii.indexOfIgnoreCase(value, "Apple_Terminal") != null or - std.ascii.indexOfIgnoreCase(value, "Terminal.app") != null or - std.ascii.indexOfIgnoreCase(value, "conemu") != null) + if (std.ascii.findIgnoreCase(value, "iterm") != null or + std.ascii.findIgnoreCase(value, "Apple_Terminal") != null or + std.ascii.findIgnoreCase(value, "Terminal.app") != null or + std.ascii.findIgnoreCase(value, "conemu") != null) { return .osc9; } @@ -599,8 +598,8 @@ fn termFeaturesHasCode(features: []const u8, code: []const u8) bool { } fn findOscTerminator(payload: []const u8) usize { - const bel_end = std.mem.indexOfScalar(u8, payload, '\x07'); - const st_end = std.mem.indexOf(u8, payload, "\x1b\\"); + const bel_end = std.mem.findScalar(u8, payload, '\x07'); + const st_end = std.mem.find(u8, payload, "\x1b\\"); if (bel_end) |bel| { if (st_end) |st| return @min(bel, st); @@ -614,7 +613,7 @@ fn findOscTerminator(payload: []const u8) usize { fn parseItermCapabilities(self: *Terminal, response: []const u8) void { var search_start: usize = 0; const prefix = "\x1b]1337;Capabilities="; - while (std.mem.indexOf(u8, response[search_start..], prefix)) |rel_pos| { + while (std.mem.find(u8, response[search_start..], prefix)) |rel_pos| { const start = search_start + rel_pos + prefix.len; const end = start + findOscTerminator(response[start..]); @@ -630,16 +629,16 @@ fn parseItermCapabilities(self: *Terminal, response: []const u8) void { fn parseOsc99NotificationQuery(self: *Terminal, response: []const u8) void { var search_start: usize = 0; const prefix = "\x1b]99;"; - while (std.mem.indexOf(u8, response[search_start..], prefix)) |rel_pos| { + while (std.mem.find(u8, response[search_start..], prefix)) |rel_pos| { const start = search_start + rel_pos; const payload_start = start + prefix.len; const end = payload_start + findOscTerminator(response[payload_start..]); const payload = response[payload_start..end]; - if (std.mem.indexOf(u8, payload, "i=" ++ NOTIFICATION_QUERY_ID) != null and - std.mem.indexOf(u8, payload, "p=?") != null and - std.mem.indexOf(u8, payload, "p=") != null and - std.mem.indexOf(u8, payload, "title") != null) + if (std.mem.find(u8, payload, "i=" ++ NOTIFICATION_QUERY_ID) != null and + std.mem.find(u8, payload, "p=?") != null and + std.mem.find(u8, payload, "p=") != null and + std.mem.find(u8, payload, "title") != null) { self.setNotificationProtocol(.osc99, .query); return; @@ -659,7 +658,7 @@ fn checkEnvironmentOverrides(self: *Terminal) void { } else { self.multiplexer = .none; } - self.is_foot = self.term_info.from_xtversion and std.ascii.indexOfIgnoreCase(self.getTerminalName(), "foot") != null; + self.is_foot = self.term_info.from_xtversion and std.ascii.findIgnoreCase(self.getTerminalName(), "foot") != null; self.skip_graphics_query = false; self.graphics_enabled = true; self.image_protocol = .auto; @@ -681,29 +680,15 @@ fn checkEnvironmentOverrides(self: *Terminal) void { self.caps.remote = false; } - var env_map_storage: ?std.process.EnvMap = null; - const maybe_env_map: ?*const std.process.EnvMap = self.opts.env_map orelse blk: { - if (self.opts.remote_mode == .remote) break :blk null; - env_map_storage = std.process.getEnvMap(std.heap.page_allocator) catch |err| { - logger.err("Failed to get environment map: {}", .{err}); - return; - }; - break :blk &env_map_storage.?; - }; - defer if (env_map_storage) |*map| map.deinit(); - - if (maybe_env_map == null) { - return; - } - - const env_map = maybe_env_map.?; + const env_map = self.opts.env_map orelse return; if (self.opts.remote_mode == .auto) { self.remote = self.remote or isRemoteSessionEnv(env_map); self.caps.remote = self.remote; } - if (self.remote and self.opts.env_map == null) { + const env_is_forwarded = if (self.host_env_map) |*host_env_map| env_map == host_env_map else false; + if (self.opts.remote_mode == .auto and self.remote and env_is_forwarded) { return; } @@ -735,18 +720,18 @@ fn checkEnvironmentOverrides(self: *Terminal) void { self.caps.unicode = .wcwidth; self.caps.explicit_cursor_positioning = true; } - if (std.mem.indexOf(u8, term, "alacritty") != null) { + if (std.mem.find(u8, term, "alacritty") != null) { self.caps.explicit_cursor_positioning = true; } } } if (env_map.get("TERM")) |term| { - if (std.ascii.indexOfIgnoreCase(term, "256color") != null) { + if (std.ascii.findIgnoreCase(term, "256color") != null) { self.caps.ansi256 = true; } self.applyNotificationHeuristic(term); - self.is_foot = self.is_foot or std.ascii.indexOfIgnoreCase(term, "foot") != null; + self.is_foot = self.is_foot or std.ascii.findIgnoreCase(term, "foot") != null; } if (env_map.get("TERM_FEATURES")) |features| { @@ -929,7 +914,7 @@ fn checkEnvironmentOverrides(self: *Terminal) void { if (!self.caps.osc52) { if (env_map.get("TERM")) |term| { - if (isOsc52Term(term) or std.mem.indexOf(u8, term, "256color") != null or std.mem.indexOf(u8, term, "xterm") != null) { + if (isOsc52Term(term) or std.mem.find(u8, term, "256color") != null or std.mem.find(u8, term, "xterm") != null) { self.caps.osc52 = true; } } @@ -937,7 +922,7 @@ fn checkEnvironmentOverrides(self: *Terminal) void { } } -fn isRemoteSessionEnv(env_map: *const std.process.EnvMap) bool { +fn isRemoteSessionEnv(env_map: *const std.process.Environ.Map) bool { return env_map.get("SSH_CONNECTION") != null or env_map.get("SSH_CLIENT") != null or env_map.get("SSH_TTY") != null or @@ -1222,22 +1207,22 @@ pub fn processCapabilityResponse(self: *Terminal, response: []const u8) void { self.parseSixelDeviceAttributes(response); // DECRPM responses - if (std.mem.indexOf(u8, response, "1016;2$y")) |_| { + if (std.mem.find(u8, response, "1016;2$y")) |_| { self.caps.sgr_pixels = true; } - if (std.mem.indexOf(u8, response, "2027;2$y")) |_| { + if (std.mem.find(u8, response, "2027;2$y")) |_| { self.caps.unicode = .unicode; } - if (std.mem.indexOf(u8, response, "2031;1$y") != null or std.mem.indexOf(u8, response, "2031;2$y") != null) { + if (std.mem.find(u8, response, "2031;1$y") != null or std.mem.find(u8, response, "2031;2$y") != null) { self.caps.color_scheme_updates = true; } - if (std.mem.indexOf(u8, response, "1004;1$y") != null or std.mem.indexOf(u8, response, "1004;2$y") != null) { + if (std.mem.find(u8, response, "1004;1$y") != null or std.mem.find(u8, response, "1004;2$y") != null) { self.caps.focus_tracking = true; } - if (std.mem.indexOf(u8, response, "2026;1$y") != null or std.mem.indexOf(u8, response, "2026;2$y") != null) { + if (std.mem.find(u8, response, "2026;1$y") != null or std.mem.find(u8, response, "2026;2$y") != null) { self.caps.sync = true; } - if (std.mem.indexOf(u8, response, "2004;1$y") != null or std.mem.indexOf(u8, response, "2004;2$y") != null) { + if (std.mem.find(u8, response, "2004;1$y") != null or std.mem.find(u8, response, "2004;2$y") != null) { self.caps.bracketed_paste = true; } @@ -1245,7 +1230,7 @@ pub fn processCapabilityResponse(self: *Terminal, response: []const u8) void { // The first report after queryTerminalSend is the pre-home cursor position. var scan_pos: usize = 0; while (scan_pos < response.len) { - const esc_rel = std.mem.indexOf(u8, response[scan_pos..], "\x1b[") orelse break; + const esc_rel = std.mem.find(u8, response[scan_pos..], "\x1b[") orelse break; const esc = scan_pos + esc_rel; var pos = esc + 2; @@ -1294,9 +1279,9 @@ pub fn processCapabilityResponse(self: *Terminal, response: []const u8) void { // Parse xtversion response: ESC P > | name version ESC \ // Examples: "\x1BP>|kitty(0.40.1)\x1B\\" or "\x1BP>|ghostty 1.1.3\x1B\\" or "\x1BP>|tmux 3.5a\x1B\\" - if (std.mem.indexOf(u8, response, "\x1bP>|")) |pos| { + if (std.mem.find(u8, response, "\x1bP>|")) |pos| { const start = pos + 4; // Skip past "\x1BP>|" - if (std.mem.indexOf(u8, response[start..], "\x1b\\")) |end_offset| { + if (std.mem.find(u8, response[start..], "\x1b\\")) |end_offset| { const term_str = response[start .. start + end_offset]; self.parseXtversion(term_str); } @@ -1316,7 +1301,7 @@ pub fn processCapabilityResponse(self: *Terminal, response: []const u8) void { // Kitty keyboard protocol detection via CSI ? u response // Terminals supporting the protocol respond to CSI ? u with CSI ? u // Examples: \x1b[?0u (ghostty, alacritty), \x1b[?1u, etc. - if (std.mem.indexOf(u8, response, "\x1b[?") != null and std.mem.indexOf(u8, response, "u") != null) { + if (std.mem.find(u8, response, "\x1b[?") != null and std.mem.find(u8, response, "u") != null) { // Look for pattern \x1b[?Nu where N is 0-31 var i: usize = 0; while (i + 4 < response.len) : (i += 1) { @@ -1331,12 +1316,12 @@ pub fn processCapabilityResponse(self: *Terminal, response: []const u8) void { } } - if (std.mem.indexOf(u8, response, "tmux")) |_| { + if (std.mem.find(u8, response, "tmux")) |_| { self.caps.unicode = .wcwidth; self.caps.explicit_cursor_positioning = true; } - if (std.mem.indexOf(u8, response, "alacritty")) |_| { + if (std.mem.find(u8, response, "alacritty")) |_| { self.caps.explicit_cursor_positioning = true; } @@ -1352,9 +1337,9 @@ pub fn processCapabilityResponse(self: *Terminal, response: []const u8) void { fn parseXtgettcapMs(self: *Terminal, response: []const u8) void { const prefix = "\x1bP"; var scan_pos: usize = 0; - while (std.mem.indexOfPos(u8, response, scan_pos, prefix)) |start| { + while (std.mem.findPos(u8, response, scan_pos, prefix)) |start| { const body_start = start + prefix.len; - const end = std.mem.indexOfPos(u8, response, body_start, "\x1b\\") orelse return; + const end = std.mem.findPos(u8, response, body_start, "\x1b\\") orelse return; const body = response[body_start..end]; scan_pos = end + 2; @@ -1362,7 +1347,7 @@ fn parseXtgettcapMs(self: *Terminal, response: []const u8) void { if (!std.mem.eql(u8, body[1..3], "+r")) continue; const result = body[3..]; - const separator = std.mem.indexOfScalar(u8, result, '=') orelse continue; + const separator = std.mem.findScalar(u8, result, '=') orelse continue; if (!std.ascii.eqlIgnoreCase(result[0..separator], "4d73")) continue; const value = result[separator + 1 ..]; @@ -1377,24 +1362,24 @@ fn parseXtgettcapMs(self: *Terminal, response: []const u8) void { } fn isOsc52Term(value: []const u8) bool { - return std.ascii.indexOfIgnoreCase(value, "iterm") != null or - std.ascii.indexOfIgnoreCase(value, "kitty") != null or - std.ascii.indexOfIgnoreCase(value, "alacritty") != null or - std.ascii.indexOfIgnoreCase(value, "wezterm") != null or - std.ascii.indexOfIgnoreCase(value, "contour") != null or - std.ascii.indexOfIgnoreCase(value, "foot") != null or - std.ascii.indexOfIgnoreCase(value, "rio") != null or - std.ascii.indexOfIgnoreCase(value, "ghostty") != null or - std.ascii.indexOfIgnoreCase(value, "tmux") != null or - std.ascii.indexOfIgnoreCase(value, "screen") != null; + return std.ascii.findIgnoreCase(value, "iterm") != null or + std.ascii.findIgnoreCase(value, "kitty") != null or + std.ascii.findIgnoreCase(value, "alacritty") != null or + std.ascii.findIgnoreCase(value, "wezterm") != null or + std.ascii.findIgnoreCase(value, "contour") != null or + std.ascii.findIgnoreCase(value, "foot") != null or + std.ascii.findIgnoreCase(value, "rio") != null or + std.ascii.findIgnoreCase(value, "ghostty") != null or + std.ascii.findIgnoreCase(value, "tmux") != null or + std.ascii.findIgnoreCase(value, "screen") != null; } fn isHyperlinkTerm(value: []const u8) bool { - return std.ascii.indexOfIgnoreCase(value, "ghostty") != null or - std.ascii.indexOfIgnoreCase(value, "kitty") != null or - std.ascii.indexOfIgnoreCase(value, "wezterm") != null or - std.ascii.indexOfIgnoreCase(value, "alacritty") != null or - std.ascii.indexOfIgnoreCase(value, "iterm") != null; + return std.ascii.findIgnoreCase(value, "ghostty") != null or + std.ascii.findIgnoreCase(value, "kitty") != null or + std.ascii.findIgnoreCase(value, "wezterm") != null or + std.ascii.findIgnoreCase(value, "alacritty") != null or + std.ascii.findIgnoreCase(value, "iterm") != null; } pub fn getCapabilities(self: *Terminal) Capabilities { @@ -1470,14 +1455,7 @@ fn writePassthroughSequence(self: *Terminal, tty: anytype, sequence: []const u8) } if (!self.remote) { - var env_map_storage: ?std.process.EnvMap = null; - const env_map: ?*const std.process.EnvMap = self.opts.env_map orelse blk: { - env_map_storage = std.process.getEnvMap(std.heap.page_allocator) catch null; - break :blk if (env_map_storage) |*map| map else null; - }; - defer if (env_map_storage) |*map| map.deinit(); - - if (env_map) |map| { + if (self.opts.env_map) |map| { if (map.get("STY") != null) { try tty.writeAll(ansi.ANSI.screenDcsStart); for (sequence) |c| { @@ -1521,9 +1499,9 @@ pub fn writeNotification(self: *Terminal, allocator: std.mem.Allocator, tty: any self.notification_id_counter +%= 1; - var buffer: std.ArrayListUnmanaged(u8) = .{}; - defer buffer.deinit(allocator); - const writer = buffer.writer(allocator); + var buffer: std.Io.Writer.Allocating = .init(allocator); + defer buffer.deinit(); + const writer = &buffer.writer; switch (self.notification_protocol) { .none => return false, @@ -1567,7 +1545,7 @@ pub fn writeNotification(self: *Terminal, allocator: std.mem.Allocator, tty: any }, } - try self.writePassthroughSequence(tty, buffer.items); + try self.writePassthroughSequence(tty, buffer.written()); return true; } @@ -1711,12 +1689,12 @@ fn parseXtversion(self: *Terminal, term_str: []const u8) void { self.caps.kitty_graphics = self.kitty_graphics_queried; self.caps.sixel = self.sixel_queried; - if (std.mem.indexOf(u8, term_str, "(")) |paren_pos| { + if (std.mem.find(u8, term_str, "(")) |paren_pos| { const name_len = @min(paren_pos, self.term_info.name.len); @memcpy(self.term_info.name[0..name_len], term_str[0..name_len]); self.term_info.name_len = name_len; - if (std.mem.indexOf(u8, term_str[paren_pos..], ")")) |close_offset| { + if (std.mem.find(u8, term_str[paren_pos..], ")")) |close_offset| { const ver_start = paren_pos + 1; const ver_end = paren_pos + close_offset; const ver_len = @min(ver_end - ver_start, self.term_info.version.len); @@ -1724,7 +1702,7 @@ fn parseXtversion(self: *Terminal, term_str: []const u8) void { self.term_info.version_len = ver_len; } } else { - if (std.mem.indexOf(u8, term_str, " ")) |space_pos| { + if (std.mem.find(u8, term_str, " ")) |space_pos| { const name_len = @min(space_pos, self.term_info.name.len); @memcpy(self.term_info.name[0..name_len], term_str[0..name_len]); self.term_info.name_len = name_len; @@ -1742,7 +1720,7 @@ fn parseXtversion(self: *Terminal, term_str: []const u8) void { } self.term_info.from_xtversion = true; - self.is_foot = std.ascii.indexOfIgnoreCase(self.getTerminalName(), "foot") != null; + self.is_foot = std.ascii.findIgnoreCase(self.getTerminalName(), "foot") != null; if (std.mem.eql(u8, self.getTerminalName(), "tmux")) { self.multiplexer = .tmux; } else if (std.ascii.eqlIgnoreCase(self.getTerminalName(), "Zellij")) { diff --git a/packages/core/src/zig/tests/audio_test.zig b/packages/core/src/zig/tests/audio_test.zig index 2eec855320..1e83d372c7 100644 --- a/packages/core/src/zig/tests/audio_test.zig +++ b/packages/core/src/zig/tests/audio_test.zig @@ -34,8 +34,7 @@ fn buildPcm16Wav(allocator: std.mem.Allocator, channels: u16, sample_rate: u32, const total_size = 44 + data_size; const out = try allocator.alloc(u8, total_size); - var stream = std.io.fixedBufferStream(out); - const writer = stream.writer(); + var writer = std.Io.Writer.fixed(out); const channels_u32: u32 = channels; const byte_rate: u32 = sample_rate * channels_u32 * 2; @@ -109,7 +108,7 @@ fn writeAllStreamBytes(engine: *audio.Engine, stream_id: u32, bytes: []const u8) const written = audio.writeStream(engine, stream_id, bytes[offset..].ptr, @intCast(bytes.len - offset)); try testing.expect(written >= 0); offset += @intCast(written); - if (written == 0) std.Thread.sleep(std.time.ns_per_ms); + if (written == 0) testing.io.sleep(.fromMilliseconds(1), .awake) catch {}; } try testing.expectEqual(bytes.len, offset); } @@ -120,7 +119,7 @@ fn waitForBufferedFrames(engine: *audio.Engine, stream_id: u32, minimum: u32) !a try expectStatusOk(audio.getStreamStats(engine, stream_id, &stats)); if (stats.buffered_frames >= minimum) return stats; if (stats.state == audio.StreamState.failed) return error.TestUnexpectedResult; - std.Thread.sleep(std.time.ns_per_ms); + testing.io.sleep(.fromMilliseconds(1), .awake) catch {}; } return error.TestUnexpectedResult; } @@ -138,7 +137,7 @@ fn mixStreamToEnd(engine: *audio.Engine, stream_id: u32) !audio.StreamStats { return stats; } if (stats.state == audio.StreamState.failed) return error.TestUnexpectedResult; - std.Thread.sleep(std.time.ns_per_ms); + testing.io.sleep(.fromMilliseconds(1), .awake) catch {}; } return error.TestUnexpectedResult; } @@ -1048,7 +1047,7 @@ test "audio stream restart retains PCM and resumes decoding with the same id" { const engine = try createEngine(null); defer audio.destroy(engine); try expectStatusOk(audio.startMixer(engine)); - const mp3 = try std.fs.cwd().readFileAlloc(testing.allocator, TEST_MP3_PATH, 16 * 1024); + const mp3 = try std.Io.Dir.cwd().readFileAlloc(testing.io, TEST_MP3_PATH, testing.allocator, .limited(16 * 1024)); defer testing.allocator.free(mp3); var options = streamOptions(); @@ -1118,7 +1117,7 @@ test "audio stream restart retains PCM and resumes decoding with the same id" { try expectStatusOk(audio.getStreamStats(engine, stream_id, &final_stats)); if (final_stats.state == audio.StreamState.ended) break; if (final_stats.state == audio.StreamState.failed) return error.TestUnexpectedResult; - std.Thread.sleep(std.time.ns_per_ms); + testing.io.sleep(.fromMilliseconds(1), .awake) catch {}; } try testing.expectEqual(audio.StreamState.ended, final_stats.state); @@ -1132,7 +1131,7 @@ test "audio stream repeatedly restarts after clean EOF with one persistent voice const engine = try createEngine(null); defer audio.destroy(engine); try expectStatusOk(audio.startMixer(engine)); - const mp3 = try std.fs.cwd().readFileAlloc(testing.allocator, TEST_MP3_PATH, 16 * 1024); + const mp3 = try std.Io.Dir.cwd().readFileAlloc(testing.io, TEST_MP3_PATH, testing.allocator, .limited(16 * 1024)); defer testing.allocator.free(mp3); const first_group = try createGroup(engine, "session-a"); @@ -1226,7 +1225,7 @@ test "audio stream repeatedly restarts after clean EOF with one persistent voice test "audio stream ready generation wraps to one" { const engine = try createEngine(null); defer audio.destroy(engine); - const mp3 = try std.fs.cwd().readFileAlloc(testing.allocator, TEST_MP3_PATH, 16 * 1024); + const mp3 = try std.Io.Dir.cwd().readFileAlloc(testing.io, TEST_MP3_PATH, testing.allocator, .limited(16 * 1024)); defer testing.allocator.free(mp3); const options = streamOptions(); @@ -1242,7 +1241,7 @@ test "audio stream ready generation wraps to one" { try expectStatusOk(audio.getStreamStats(engine, stream_id, &stats)); if (stats.ready_generation == 1) break; if (stats.state == audio.StreamState.failed) return error.TestUnexpectedResult; - std.Thread.sleep(std.time.ns_per_ms); + testing.io.sleep(.fromMilliseconds(1), .awake) catch {}; } try testing.expectEqual(@as(u32, 1), stats.ready_generation); @@ -1266,7 +1265,7 @@ test "audio stream restart rejects failed streams" { for (0..5_000) |_| { try expectStatusOk(audio.getStreamStats(engine, stream_id, &stats)); if (stats.state == audio.StreamState.failed) break; - std.Thread.sleep(std.time.ns_per_ms); + testing.io.sleep(.fromMilliseconds(1), .awake) catch {}; } try testing.expectEqual(audio.StreamState.failed, stats.state); try testing.expectEqual(audio.Status.err_invalid, audio.writeStream(engine, stream_id, invalid_mp3, invalid_mp3.len)); @@ -1282,7 +1281,7 @@ test "audio stream restart rejects failed streams" { test "audio stream close wakes a restarted decoder waiting for bytes" { const engine = try createEngine(null); defer audio.destroy(engine); - const mp3 = try std.fs.cwd().readFileAlloc(testing.allocator, TEST_MP3_PATH, 16 * 1024); + const mp3 = try std.Io.Dir.cwd().readFileAlloc(testing.io, TEST_MP3_PATH, testing.allocator, .limited(16 * 1024)); defer testing.allocator.free(mp3); var options = streamOptions(); diff --git a/packages/core/src/zig/tests/buffer_test.zig b/packages/core/src/zig/tests/buffer_test.zig index effd5dd283..4241fc0642 100644 --- a/packages/core/src/zig/tests/buffer_test.zig +++ b/packages/core/src/zig/tests/buffer_test.zig @@ -1092,17 +1092,17 @@ 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.ArrayListUnmanaged(u8) = .{}; - defer text_builder.deinit(std.testing.allocator); + var text_builder: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer text_builder.deinit(); var line: u32 = 0; while (line < 20) : (line += 1) { - 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 text_builder.writer.writeAll("Line "); + try text_builder.writer.print("{d}", .{line}); + try text_builder.writer.writeAll(": 🌟 测试 🎨 Test 🚀\n"); } - try tb.setText(text_builder.items); + try tb.setText(text_builder.written()); view.setWrapMode(.char); view.setWrapWidth(40); @@ -1267,7 +1267,7 @@ test "OptimizedBuffer - stress test with many graphemes" { var view = try TextBufferView.init(std.testing.allocator, tb); defer view.deinit(); - var text_builder: std.ArrayListUnmanaged(u8) = .{}; + var text_builder: std.ArrayListUnmanaged(u8) = .empty; defer text_builder.deinit(std.testing.allocator); var line: u32 = 0; @@ -1368,7 +1368,7 @@ test "OptimizedBuffer - many unique graphemes with small pool" { var failure_count: u32 = 0; while (render_count < 1000) : (render_count += 1) { - var text_builder: std.ArrayListUnmanaged(u8) = .{}; + var text_builder: std.ArrayListUnmanaged(u8) = .empty; defer text_builder.deinit(std.testing.allocator); const base_codepoint: u21 = 0x2600 + @as(u21, @intCast(render_count % 500)); diff --git a/packages/core/src/zig/tests/editor-view_test.zig b/packages/core/src/zig/tests/editor-view_test.zig index 1addb0bde6..6f0551109d 100644 --- a/packages/core/src/zig/tests/editor-view_test.zig +++ b/packages/core/src/zig/tests/editor-view_test.zig @@ -1720,7 +1720,7 @@ test "EditorView - horizontal scroll: editing in scrolled view" { var out_buffer: [200]u8 = undefined; const written = eb.getText(&out_buffer); const text = out_buffer[0..written]; - try std.testing.expect(std.mem.indexOf(u8, text, "XYZ") != null); + try std.testing.expect(std.mem.find(u8, text, "XYZ") != null); } test "EditorView - horizontal scroll: backspace in scrolled view" { @@ -2343,15 +2343,14 @@ test "EditorView - horizontal scroll: combined vertical and horizontal scrolling const repeated_line = "\nAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDDDEEEEEEEEEEFFFFFFFFFFGGGGGGGGGGHHHHHHHHHHIIIIIIIIIIJJJJJJJJJJ"; var buffer: [3000]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buffer); - const writer = fbs.writer(); - writer.writeAll(line0) catch unreachable; + var writer: std.Io.Writer = .fixed(&buffer); + try writer.writeAll(line0); var i: u32 = 1; while (i < 20) : (i += 1) { - writer.writeAll(repeated_line) catch unreachable; + try writer.writeAll(repeated_line); } - const text = fbs.getWritten(); + const text = writer.buffered(); try eb.setText(text); try eb.setCursor(15, 60); @@ -2900,9 +2899,9 @@ test "EditorView - placeholder shrink clears tail and preserves background" { const written = try opt_buffer.writeResolvedChars(&out_buffer, false); const line = out_buffer[0..written]; - try std.testing.expect(std.mem.indexOf(u8, line, short_text) != null); - try std.testing.expect(std.mem.indexOf(u8, line, "roken tests") == null); - try std.testing.expect(std.mem.indexOf(u8, line, "TODO in the codebase") == null); + try std.testing.expect(std.mem.find(u8, line, short_text) != null); + try std.testing.expect(std.mem.find(u8, line, "roken tests") == null); + try std.testing.expect(std.mem.find(u8, line, "TODO in the codebase") == null); const tail = opt_buffer.get(35, 0) orelse return error.TestUnexpectedResult; try std.testing.expectEqual(@as(u32, 32), tail.char); diff --git a/packages/core/src/zig/tests/grapheme_test.zig b/packages/core/src/zig/tests/grapheme_test.zig index 2bac0c1983..e9e45eab26 100644 --- a/packages/core/src/zig/tests/grapheme_test.zig +++ b/packages/core/src/zig/tests/grapheme_test.zig @@ -325,7 +325,7 @@ test "GraphemePool - allocations with varying sizes" { var pool = GraphemePool.init(std.testing.allocator); defer pool.deinit(); - var ids: std.ArrayListUnmanaged(u32) = .{}; + var ids: std.ArrayListUnmanaged(u32) = .empty; defer ids.deinit(std.testing.allocator); for (0..50) |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 22bdf0b5b1..488bb29a6a 100644 --- a/packages/core/src/zig/tests/memory_leak_regression_test.zig +++ b/packages/core/src/zig/tests/memory_leak_regression_test.zig @@ -98,7 +98,7 @@ test "GraphemePool - defer cleanup on failure path" { var pool = GraphemePool.init(std.testing.allocator); defer pool.deinit(); - var allocated_ids: std.ArrayListUnmanaged(u32) = .{}; + var allocated_ids: std.ArrayListUnmanaged(u32) = .empty; defer allocated_ids.deinit(std.testing.allocator); for (0..5) |i| { @@ -128,7 +128,7 @@ test "GraphemePool - pending grapheme cleanup on failure" { var pool = GraphemePool.init(std.testing.allocator); defer pool.deinit(); - var result_graphemes: std.ArrayListUnmanaged(u32) = .{}; + var result_graphemes: std.ArrayListUnmanaged(u32) = .empty; defer result_graphemes.deinit(std.testing.allocator); var pending_gid: ?u32 = null; diff --git a/packages/core/src/zig/tests/renderer_test.zig b/packages/core/src/zig/tests/renderer_test.zig index 33e2159aa1..68ff11e801 100644 --- a/packages/core/src/zig/tests/renderer_test.zig +++ b/packages/core/src/zig/tests/renderer_test.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const io = std.testing.io; const renderer = @import("../renderer.zig"); const text_buffer = @import("../text-buffer.zig"); const text_buffer_view = @import("../text-buffer-view.zig"); @@ -80,7 +81,7 @@ const SlowThreadSafeOutput = struct { self.lengths[index] = @min(data.len, self.payloads[index].len); @memcpy(self.payloads[index][0..self.lengths[index]], data[0..self.lengths[index]]); } - std.Thread.sleep(self.delay_ns); + io.sleep(.fromNanoseconds(self.delay_ns), .awake) catch unreachable; } }; test "renderer emits Kitty image once and leaves unchanged frame empty" { @@ -1649,7 +1650,7 @@ test "renderer - unchanged grapheme should not churn IDs across frames" { _ = cli_renderer.render(false); const first_output = test_cli_renderer.lastOutput(); - try std.testing.expect(std.mem.indexOf(u8, first_output, "👋") != null); + try std.testing.expect(std.mem.find(u8, first_output, "👋") != null); const current_buffer = cli_renderer.getCurrentBuffer(); const first_cell = current_buffer.get(0, 0); @@ -1672,7 +1673,7 @@ test "renderer - unchanged grapheme should not churn IDs across frames" { _ = cli_renderer.render(false); const second_output = test_cli_renderer.lastOutput(); - try std.testing.expect(std.mem.indexOf(u8, second_output, "👋") == null); + try std.testing.expect(std.mem.find(u8, second_output, "👋") == null); } test "renderer - grows frame output instead of committing cells whose ANSI was dropped" { @@ -1718,8 +1719,8 @@ test "renderer - grows frame output instead of committing cells whose ANSI was d // must not fit in the default output buffer. try std.testing.expect(output.len > renderer.OUTPUT_BUFFER_SIZE); try std.testing.expect(current.get(width - 24, height - 1).?.char == 'F'); - try std.testing.expect(std.mem.indexOf(u8, output, "EARLY_SCROLL_CHANGED") != null); - try std.testing.expect(std.mem.indexOf(u8, output, "FULL_TOOL_RESULT_MARKER") != null); + try std.testing.expect(std.mem.find(u8, output, "EARLY_SCROLL_CHANGED") != null); + try std.testing.expect(std.mem.find(u8, output, "FULL_TOOL_RESULT_MARKER") != null); } } @@ -1756,15 +1757,15 @@ test "renderer - hyperlinks enabled with OSC 8 output" { const output = test_cli_renderer.lastOutput(); // Verify OSC 8 contains id parameter - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b]8;id=") != null); + try std.testing.expect(std.mem.find(u8, output, "\x1b]8;id=") != null); // Verify OSC 8 contains the URL - try std.testing.expect(std.mem.indexOf(u8, output, ";https://example.com\x1b\\") != null); + try std.testing.expect(std.mem.find(u8, output, ";https://example.com\x1b\\") != null); // Verify output contains OSC 8 end sequence const end_seq = "\x1b]8;;\x1b\\"; var count: usize = 0; var pos: usize = 0; - while (std.mem.indexOf(u8, output[pos..], end_seq)) |found| { + while (std.mem.find(u8, output[pos..], end_seq)) |found| { count += 1; pos += found + end_seq.len; } @@ -1804,8 +1805,8 @@ test "renderer - hyperlinks disabled no OSC 8 output" { const output = test_cli_renderer.lastOutput(); // Verify output does NOT contain OSC 8 sequences - try std.testing.expect(std.mem.indexOf(u8, output, "]8;;") == null); - try std.testing.expect(std.mem.indexOf(u8, output, "]8;id=") == null); + try std.testing.expect(std.mem.find(u8, output, "]8;;") == null); + try std.testing.expect(std.mem.find(u8, output, "]8;id=") == null); } test "renderer - link transition mid-line" { @@ -1850,14 +1851,14 @@ test "renderer - link transition mid-line" { const output = test_cli_renderer.lastOutput(); // Should contain both URLs - try std.testing.expect(std.mem.indexOf(u8, output, "https://first.com") != null); - try std.testing.expect(std.mem.indexOf(u8, output, "https://second.com") != null); + try std.testing.expect(std.mem.find(u8, output, "https://first.com") != null); + try std.testing.expect(std.mem.find(u8, output, "https://second.com") != null); // Should have multiple OSC 8 end sequences (at least 2 transitions) const end_seq = "\x1b]8;;\x1b\\"; var count: usize = 0; var pos: usize = 0; - while (std.mem.indexOf(u8, output[pos..], end_seq)) |found| { + while (std.mem.find(u8, output[pos..], end_seq)) |found| { count += 1; pos += found + end_seq.len; } @@ -1906,7 +1907,7 @@ test "renderer - hyperlink spanning multiple rows uses same id" { const expected_open = std.fmt.bufPrint(&buf, "\x1b]8;id={d};https://example.com/long-url\x1b\\", .{link_id}) catch unreachable; var count: usize = 0; var pos: usize = 0; - while (std.mem.indexOf(u8, output[pos..], expected_open)) |found| { + while (std.mem.find(u8, output[pos..], expected_open)) |found| { count += 1; pos += found + expected_open.len; } @@ -1950,8 +1951,8 @@ test "renderer - explicit default and indexed tags use ANSI default/indexed outp _ = cli_renderer.render(false); const output = test_cli_renderer.lastOutput(); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[39m") != null); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[38;5;6m") != null); + try std.testing.expect(std.mem.find(u8, output, "\x1b[39m") != null); + try std.testing.expect(std.mem.find(u8, output, "\x1b[38;5;6m") != null); } test "renderer - indexed snapshots fall back to rgb and explicit bg default resets without ansi256" { @@ -1983,9 +1984,9 @@ test "renderer - indexed snapshots fall back to rgb and explicit bg default rese _ = cli_renderer.render(false); const output = test_cli_renderer.lastOutput(); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[38;2;51;102;153m") != null); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[38;5;6m") == null); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[49m") != null); + try std.testing.expect(std.mem.find(u8, output, "\x1b[38;2;51;102;153m") != null); + try std.testing.expect(std.mem.find(u8, output, "\x1b[38;5;6m") == null); + try std.testing.expect(std.mem.find(u8, output, "\x1b[49m") != null); } test "renderer - rgb colors fall back to ANSI256 mapping when rgb is unavailable" { @@ -2012,8 +2013,8 @@ test "renderer - rgb colors fall back to ANSI256 mapping when rgb is unavailable _ = cli_renderer.render(false); const output = test_cli_renderer.lastOutput(); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[38;5;") != null); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[38;2;") == null); + try std.testing.expect(std.mem.find(u8, output, "\x1b[38;5;") != null); + try std.testing.expect(std.mem.find(u8, output, "\x1b[38;2;") == null); } test "renderer - rgb fallback uses published palette state" { @@ -2045,7 +2046,7 @@ test "renderer - rgb fallback uses published palette state" { _ = cli_renderer.render(false); const output = test_cli_renderer.lastOutput(); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[38;5;42m") != null); + try std.testing.expect(std.mem.find(u8, output, "\x1b[38;5;42m") != null); } test "renderer - palette epoch changes force repaint and use new palette mapping" { @@ -2078,13 +2079,13 @@ test "renderer - palette epoch changes force repaint and use new palette mapping _ = cli_renderer.render(false); const first_output = test_cli_renderer.lastOutput(); - try std.testing.expect(std.mem.indexOf(u8, first_output, "\x1b[38;5;42m") != null); + try std.testing.expect(std.mem.find(u8, first_output, "\x1b[38;5;42m") != null); try next_buffer.drawText("A", 0, 0, target, bg, 0); _ = cli_renderer.render(false); const second_output = test_cli_renderer.lastOutput(); - try std.testing.expect(std.mem.indexOf(u8, second_output, "A") == null); + try std.testing.expect(std.mem.find(u8, second_output, "A") == null); var palette_b = [_]RGBA{ansi.rgbaFromFloats(0.0, 0.0, 0.0, 1.0)} ** 256; palette_b[77] = target; @@ -2094,8 +2095,8 @@ test "renderer - palette epoch changes force repaint and use new palette mapping _ = cli_renderer.render(false); const third_output = test_cli_renderer.lastOutput(); - try std.testing.expect(std.mem.indexOf(u8, third_output, "A") != null); - try std.testing.expect(std.mem.indexOf(u8, third_output, "\x1b[38;5;77m") != null); + try std.testing.expect(std.mem.find(u8, third_output, "A") != null); + try std.testing.expect(std.mem.find(u8, third_output, "\x1b[38;5;77m") != null); } test "renderer - transparent rgb backgrounds still emit 49 reset" { @@ -2121,7 +2122,7 @@ test "renderer - transparent rgb backgrounds still emit 49 reset" { _ = cli_renderer.render(false); const output = test_cli_renderer.lastOutput(); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[49m") != null); + try std.testing.expect(std.mem.find(u8, output, "\x1b[49m") != null); } // ============================================================================ @@ -2148,8 +2149,8 @@ test "renderer - default cursor style emits reset cursor ANSI" { const output = test_cli_renderer.lastOutput(); - try std.testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.defaultCursorStyle) != null); - try std.testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.cursorBlock) == null); + try std.testing.expect(std.mem.find(u8, output, ansi.ANSI.defaultCursorStyle) != null); + try std.testing.expect(std.mem.find(u8, output, ansi.ANSI.cursorBlock) == null); } test "renderer - explicit_cursor_positioning emits cursor move after wide graphemes" { @@ -2185,7 +2186,7 @@ test "renderer - explicit_cursor_positioning emits cursor move after wide graphe const output = test_cli_renderer.lastOutput(); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[1;3H") != null); + try std.testing.expect(std.mem.find(u8, output, "\x1b[1;3H") != null); } test "renderer - explicit_cursor_positioning produces more cursor moves" { @@ -2296,7 +2297,7 @@ test "renderer - explicit_cursor_positioning with CJK characters" { const output = test_cli_renderer.lastOutput(); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[1;3H") != null); + try std.testing.expect(std.mem.find(u8, output, "\x1b[1;3H") != null); } test "renderer - commitSplitFooterSnapshot writes append before footer repaint in one output" { @@ -2340,12 +2341,12 @@ test "renderer - commitSplitFooterSnapshot writes append before footer repaint i _ = cli_renderer.commitSplitFooterSnapshotBatched(snapshot, 11, false, true, 2, false, true, true); const output = test_cli_renderer.lastOutput(); - const append_index = std.mem.indexOf(u8, output, appended); - const scroll_region_set_index = std.mem.indexOf(u8, output, "\x1b[1;2r"); - const scroll_region_reset_index = std.mem.indexOf(u8, output, "\x1b[r"); - const sync_index = std.mem.indexOf(u8, output, ansi.ANSI.syncSet); + const append_index = std.mem.find(u8, output, appended); + const scroll_region_set_index = std.mem.find(u8, output, "\x1b[1;2r"); + const scroll_region_reset_index = std.mem.find(u8, output, "\x1b[r"); + const sync_index = std.mem.find(u8, output, ansi.ANSI.syncSet); const footer_move_after_sync = if (sync_index) |sync_start| - std.mem.indexOf(u8, output[sync_start + ansi.ANSI.syncSet.len ..], "\x1b[3;1H") + std.mem.find(u8, output[sync_start + ansi.ANSI.syncSet.len ..], "\x1b[3;1H") else null; @@ -2360,7 +2361,7 @@ test "renderer - commitSplitFooterSnapshot writes append before footer repaint i var sync_count: usize = 0; var pos: usize = 0; - while (std.mem.indexOf(u8, output[pos..], ansi.ANSI.syncSet)) |found| { + while (std.mem.find(u8, output[pos..], ansi.ANSI.syncSet)) |found| { sync_count += 1; pos += found + ansi.ANSI.syncSet.len; } @@ -2583,14 +2584,14 @@ test "renderer - commitSplitFooterSnapshot settling phase moves footer downward" _ = cli_renderer.commitSplitFooterSnapshotBatched(snapshot, 6, false, true, 3, false, true, true); const output = test_cli_renderer.lastOutput(); - const settling_clear_index = std.mem.indexOf(u8, output, "\x1b[1;1H\x1b[J"); - const stale_footer_clear_top_index = std.mem.indexOf(u8, output, "\x1b[1;1H\x1b[2K"); - const stale_footer_clear_next_index = std.mem.indexOf(u8, output, "\x1b[2;1H\x1b[2K"); - const append_index = std.mem.indexOf(u8, output, appended); - const scroll_region_reset_index = std.mem.indexOf(u8, output, "\x1b[r"); - const footer_clear_index = std.mem.indexOf(u8, output, "\x1b[3;1H\x1b[2K"); - const footer_erase_below_index = std.mem.indexOf(u8, output, "\x1b[3;1H\x1b[J"); - const sync_index = std.mem.indexOf(u8, output, ansi.ANSI.syncSet); + const settling_clear_index = std.mem.find(u8, output, "\x1b[1;1H\x1b[J"); + const stale_footer_clear_top_index = std.mem.find(u8, output, "\x1b[1;1H\x1b[2K"); + const stale_footer_clear_next_index = std.mem.find(u8, output, "\x1b[2;1H\x1b[2K"); + const append_index = std.mem.find(u8, output, appended); + const scroll_region_reset_index = std.mem.find(u8, output, "\x1b[r"); + const footer_clear_index = std.mem.find(u8, output, "\x1b[3;1H\x1b[2K"); + const footer_erase_below_index = std.mem.find(u8, output, "\x1b[3;1H\x1b[J"); + const sync_index = std.mem.find(u8, output, ansi.ANSI.syncSet); try std.testing.expectEqual(@as(u32, 2), cli_renderer.renderOffset); try std.testing.expect(settling_clear_index == null); @@ -2642,8 +2643,8 @@ test "renderer - commitSplitFooterSnapshot multiline settling enables bounded sc _ = cli_renderer.commitSplitFooterSnapshotBatched(snapshot, 6, false, true, 3, false, true, true); const output = test_cli_renderer.lastOutput(); - const expanded_region_index = std.mem.indexOf(u8, output, "\x1b[1;3r"); - const collapsed_region_index = std.mem.indexOf(u8, output, "\x1b[r"); + const expanded_region_index = std.mem.find(u8, output, "\x1b[1;3r"); + const collapsed_region_index = std.mem.find(u8, output, "\x1b[r"); try std.testing.expectEqual(@as(u32, 3), cli_renderer.renderOffset); try std.testing.expect(expanded_region_index != null); @@ -2703,13 +2704,13 @@ test "renderer - commitSplitFooterSnapshot multiline short final row keeps conti _ = cli_renderer.commitSplitFooterSnapshotBatched(second_snapshot, 1, false, false, 2, false, true, true); const output = test_cli_renderer.lastOutput(); - const move_index = std.mem.indexOf(u8, output, "\x1b[2;6H"); - const comma_index = std.mem.indexOf(u8, output, ","); + const move_index = std.mem.find(u8, output, "\x1b[2;6H"); + const comma_index = std.mem.find(u8, output, ","); try std.testing.expect(move_index != null); try std.testing.expect(comma_index != null); try std.testing.expect(move_index.? < comma_index.?); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[2;17H") == null); + try std.testing.expect(std.mem.find(u8, output, "\x1b[2;17H") == null); } test "renderer - commitSplitFooterSnapshot exact-width continuation preserves autowrap" { @@ -2761,8 +2762,8 @@ test "renderer - commitSplitFooterSnapshot exact-width continuation preserves au _ = cli_renderer.commitSplitFooterSnapshotBatched(second_snapshot, 8, false, false, 2, false, true, true); const output = test_cli_renderer.lastOutput(); - const wrap_index = std.mem.indexOf(u8, output, "\x1b[2;20H\r\n"); - const text_index = std.mem.indexOf(u8, output, " letters"); + const wrap_index = std.mem.find(u8, output, "\x1b[2;20H\r\n"); + const text_index = std.mem.find(u8, output, " letters"); try std.testing.expectEqual(@as(u32, 8), cli_renderer.splitScrollback.tail_column); try std.testing.expect(wrap_index != null); @@ -2808,14 +2809,14 @@ test "renderer - commitSplitFooterSnapshot does not emit continuation spaces for const output = test_cli_renderer.lastOutput(); - try std.testing.expect(std.mem.indexOf(u8, output, "│甲│乙│丙│") != null); - try std.testing.expect(std.mem.indexOf(u8, output, "│😀│🚀│🧪│") != null); - try std.testing.expect(std.mem.indexOf(u8, output, "甲 ") == null); - try std.testing.expect(std.mem.indexOf(u8, output, "乙 ") == null); - try std.testing.expect(std.mem.indexOf(u8, output, "丙 ") == null); - try std.testing.expect(std.mem.indexOf(u8, output, "😀 ") == null); - try std.testing.expect(std.mem.indexOf(u8, output, "🚀 ") == null); - try std.testing.expect(std.mem.indexOf(u8, output, "🧪 ") == null); + try std.testing.expect(std.mem.find(u8, output, "│甲│乙│丙│") != null); + try std.testing.expect(std.mem.find(u8, output, "│😀│🚀│🧪│") != null); + try std.testing.expect(std.mem.find(u8, output, "甲 ") == null); + try std.testing.expect(std.mem.find(u8, output, "乙 ") == null); + try std.testing.expect(std.mem.find(u8, output, "丙 ") == null); + try std.testing.expect(std.mem.find(u8, output, "😀 ") == null); + try std.testing.expect(std.mem.find(u8, output, "🚀 ") == null); + try std.testing.expect(std.mem.find(u8, output, "🧪 ") == null); } test "renderer - repaintSplitFooter repaints footer without append payload" { @@ -2843,10 +2844,10 @@ test "renderer - repaintSplitFooter repaints footer without append payload" { _ = cli_renderer.repaintSplitFooter(2, true); const output = test_cli_renderer.lastOutput(); - const footer_text_index = std.mem.indexOf(u8, output, "FOOT"); - const footer_clear_index = std.mem.indexOf(u8, output, "\x1b[3;1H\x1b[2K"); - const footer_erase_below_index = std.mem.indexOf(u8, output, "\x1b[3;1H\x1b[J"); - const sync_index = std.mem.indexOf(u8, output, ansi.ANSI.syncSet); + const footer_text_index = std.mem.find(u8, output, "FOOT"); + const footer_clear_index = std.mem.find(u8, output, "\x1b[3;1H\x1b[2K"); + const footer_erase_below_index = std.mem.find(u8, output, "\x1b[3;1H\x1b[J"); + const sync_index = std.mem.find(u8, output, ansi.ANSI.syncSet); try std.testing.expect(footer_text_index != null); try std.testing.expect(footer_clear_index == null); @@ -2882,10 +2883,10 @@ test "renderer - repaintSplitFooter applies pending viewport scroll transition i _ = cli_renderer.repaintSplitFooter(4, true); const output = test_cli_renderer.lastOutput(); - const sync_index = std.mem.indexOf(u8, output, ansi.ANSI.syncSet); - const scroll_index = std.mem.indexOf(u8, output, "\x1b[1T"); - const footer_move_index = std.mem.indexOf(u8, output, "\x1b[5;1H"); - const footer_text_index = std.mem.indexOf(u8, output, "FOOT"); + const sync_index = std.mem.find(u8, output, ansi.ANSI.syncSet); + const scroll_index = std.mem.find(u8, output, "\x1b[1T"); + const footer_move_index = std.mem.find(u8, output, "\x1b[5;1H"); + const footer_text_index = std.mem.find(u8, output, "FOOT"); try std.testing.expect(sync_index != null); try std.testing.expect(scroll_index != null); @@ -2926,8 +2927,8 @@ test "renderer - repaintSplitFooter viewport scroll uses explicit split scroll d _ = cli_renderer.repaintSplitFooter(2, true); const output = test_cli_renderer.lastOutput(); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[2S") != null); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[4S") == null); + try std.testing.expect(std.mem.find(u8, output, "\x1b[2S") != null); + try std.testing.expect(std.mem.find(u8, output, "\x1b[4S") == null); try std.testing.expectEqual(@as(u32, 2), cli_renderer.getSplitOutputOffset(2)); } @@ -2957,9 +2958,9 @@ test "renderer - repaintSplitFooter applies pending stale row clear transition i _ = cli_renderer.repaintSplitFooter(7, true); const output = test_cli_renderer.lastOutput(); - const sync_index = std.mem.indexOf(u8, output, ansi.ANSI.syncSet); - const clear_index = std.mem.indexOf(u8, output, "\x1b[5;1H\x1b[2K"); - const footer_text_index = std.mem.indexOf(u8, output, "FOOT"); + const sync_index = std.mem.find(u8, output, ansi.ANSI.syncSet); + const clear_index = std.mem.find(u8, output, "\x1b[5;1H\x1b[2K"); + const footer_text_index = std.mem.find(u8, output, "FOOT"); try std.testing.expect(sync_index != null); try std.testing.expect(clear_index != null); @@ -3010,11 +3011,11 @@ test "renderer - commitSplitFooterSnapshot appends styled snapshot before footer _ = cli_renderer.commitSplitFooterSnapshotBatched(snapshot, 8, true, true, 2, false, true, true); const output = test_cli_renderer.lastOutput(); - const snapshot_text_index = std.mem.indexOf(u8, output, "SNAP"); - const orange_fg_index = std.mem.indexOf(u8, output, "\x1b[38;2;255;128;0m"); - const bold_index = std.mem.indexOf(u8, output, "\x1b[1m"); - const sync_index = std.mem.indexOf(u8, output, ansi.ANSI.syncSet); - const footer_clear_index = std.mem.indexOf(u8, output, "\x1b[3;1H\x1b[2K"); + const snapshot_text_index = std.mem.find(u8, output, "SNAP"); + const orange_fg_index = std.mem.find(u8, output, "\x1b[38;2;255;128;0m"); + const bold_index = std.mem.find(u8, output, "\x1b[1m"); + const sync_index = std.mem.find(u8, output, ansi.ANSI.syncSet); + const footer_clear_index = std.mem.find(u8, output, "\x1b[3;1H\x1b[2K"); try std.testing.expect(snapshot_text_index != null); try std.testing.expect(orange_fg_index != null); @@ -3069,12 +3070,12 @@ test "renderer - commitSplitFooterSnapshot preserves indexed and default color t _ = cli_renderer.commitSplitFooterSnapshotBatched(snapshot, 2, true, false, 2, false, true, true); const output = test_cli_renderer.lastOutput(); - try std.testing.expect(std.mem.indexOf(u8, output, "A") != null); - try std.testing.expect(std.mem.indexOf(u8, output, "B") != null); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[39m") != null); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[38;5;6m") != null); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[49m") != null); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[38;2;51;102;153m") == null); + try std.testing.expect(std.mem.find(u8, output, "A") != null); + try std.testing.expect(std.mem.find(u8, output, "B") != null); + try std.testing.expect(std.mem.find(u8, output, "\x1b[39m") != null); + try std.testing.expect(std.mem.find(u8, output, "\x1b[38;5;6m") != null); + try std.testing.expect(std.mem.find(u8, output, "\x1b[49m") != null); + try std.testing.expect(std.mem.find(u8, output, "\x1b[38;2;51;102;153m") == null); } test "renderer - commitSplitFooterSnapshot does not emit NUL padding for short rows" { @@ -3114,9 +3115,9 @@ test "renderer - commitSplitFooterSnapshot does not emit NUL padding for short r _ = cli_renderer.commitSplitFooterSnapshotBatched(snapshot, 16, true, true, 2, false, true, true); const output = test_cli_renderer.lastOutput(); - try std.testing.expect(std.mem.indexOfScalar(u8, output, 0) == null); - try std.testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.eraseToEndOfLine) != null); - try std.testing.expect(std.mem.indexOf(u8, output, "\r\n\x1b[0m\x1b[K") != null); + try std.testing.expect(std.mem.findScalar(u8, output, 0) == null); + try std.testing.expect(std.mem.find(u8, output, ansi.ANSI.eraseToEndOfLine) != null); + try std.testing.expect(std.mem.find(u8, output, "\r\n\x1b[0m\x1b[K") != null); } test "renderer - batched split commits share single sync frame" { @@ -3185,8 +3186,8 @@ test "renderer - batched split commits share single sync frame" { const output = test_cli_renderer.lastOutput(); // Batching should keep both commits inside one synchronized update envelope. - try std.testing.expect(std.mem.indexOf(u8, output, "FIRST") != null); - try std.testing.expect(std.mem.indexOf(u8, output, "SECOND") != null); + try std.testing.expect(std.mem.find(u8, output, "FIRST") != null); + try std.testing.expect(std.mem.find(u8, output, "SECOND") != null); try std.testing.expectEqual(@as(usize, 1), std.mem.count(u8, output, ansi.ANSI.syncSet)); try std.testing.expectEqual(@as(usize, 1), std.mem.count(u8, output, ansi.ANSI.syncReset)); } @@ -3241,12 +3242,12 @@ test "renderer - buffered debug dump includes non-threaded last render" { _ = cli_renderer.render(false); var dump_buf: [4096]u8 = undefined; - var stream = std.io.fixedBufferStream(&dump_buf); - cli_renderer.backend.dumpTo(stream.writer()); - const dump = stream.getWritten(); + var writer: std.Io.Writer = .fixed(&dump_buf); + cli_renderer.backend.dumpTo(&writer); + const dump = writer.buffered(); - try std.testing.expect(std.mem.indexOf(u8, dump, "DUMP") != null); - try std.testing.expect(std.mem.indexOf(u8, dump, "(no output rendered yet)") == null); + try std.testing.expect(std.mem.find(u8, dump, "DUMP") != null); + try std.testing.expect(std.mem.find(u8, dump, "(no output rendered yet)") == null); } // ---- FeedBackend tests ---- @@ -3284,7 +3285,7 @@ test "FeedBackend - renderer writes through feed" { for (span_out[0..count]) |span| { total_bytes += span.len; const slice = span.slice(); - if (std.mem.indexOf(u8, slice, "Hello") != null) { + if (std.mem.find(u8, slice, "Hello") != null) { found_hello = true; } } @@ -3776,10 +3777,10 @@ test "two renderers on buffered backend have independent buffers" { const out1 = test_r1.lastOutput(); const out2 = test_r2.lastOutput(); - try std.testing.expect(std.mem.indexOf(u8, out1, "AAA") != null); - try std.testing.expect(std.mem.indexOf(u8, out1, "BBB") == null); - try std.testing.expect(std.mem.indexOf(u8, out2, "BBB") != null); - try std.testing.expect(std.mem.indexOf(u8, out2, "AAA") == null); + try std.testing.expect(std.mem.find(u8, out1, "AAA") != null); + try std.testing.expect(std.mem.find(u8, out1, "BBB") == null); + try std.testing.expect(std.mem.find(u8, out2, "BBB") != null); + try std.testing.expect(std.mem.find(u8, out2, "AAA") == null); } test "threaded buffered destroy: no stale write after shutdown ANSI" { @@ -3824,9 +3825,9 @@ test "threaded buffered backend skips instead of blocking behind output" { try backend.writer().writeAll("large graphics frame"); try std.testing.expectEqual(@import("../renderer-output.zig").WriteStatus.ok, backend.endFrame()); - var timer = try std.time.Timer.start(); + const start_time: std.Io.Timestamp = .now(io, .awake); try std.testing.expectEqual(@import("../renderer-output.zig").WriteStatus.skipped, backend.prepareFrame()); - try std.testing.expect(timer.read() < 100 * std.time.ns_per_ms); + try std.testing.expect(start_time.untilNow(io, .awake).toNanoseconds() < 100 * std.time.ns_per_ms); backend.setUseThread(false); try std.testing.expectEqualStrings("large graphics frame", output.payloads[0][0..output.lengths[0]]); @@ -3880,7 +3881,7 @@ test "buffered backend reports a failed frame when growth allocation fails" { } test "buffered backend releases oversized frame buffers after the spike passes" { - var gpa = std.heap.GeneralPurposeAllocator(.{ .enable_memory_limit = true }){}; + var gpa: std.heap.DebugAllocator(.{ .enable_memory_limit = true }) = .init; defer std.debug.assert(gpa.deinit() == .ok); var out = CountingOutput{}; diff --git a/packages/core/src/zig/tests/rope_test.zig b/packages/core/src/zig/tests/rope_test.zig index cc1a5e5a5b..4460fc346a 100644 --- a/packages/core/src/zig/tests/rope_test.zig +++ b/packages/core/src/zig/tests/rope_test.zig @@ -1810,8 +1810,8 @@ test "Rope - toText shows basic structure" { const debug_text = try rope.toText(arena.allocator()); - try std.testing.expect(std.mem.indexOf(u8, debug_text, "[root") != null); - try std.testing.expect(std.mem.indexOf(u8, debug_text, "branch") != null or std.mem.indexOf(u8, debug_text, "leaf") != null); + try std.testing.expect(std.mem.find(u8, debug_text, "[root") != null); + try std.testing.expect(std.mem.find(u8, debug_text, "branch") != null or std.mem.find(u8, debug_text, "leaf") != null); } test "Rope - toText shows empty rope" { @@ -1823,8 +1823,8 @@ test "Rope - toText shows empty rope" { const debug_text = try rope.toText(arena.allocator()); - try std.testing.expect(std.mem.indexOf(u8, debug_text, "[root") != null); - try std.testing.expect(std.mem.indexOf(u8, debug_text, "[empty]") != null); + try std.testing.expect(std.mem.find(u8, debug_text, "[root") != null); + try std.testing.expect(std.mem.find(u8, debug_text, "[empty]") != null); } test "Rope - toText with union type shows tags" { @@ -1881,11 +1881,11 @@ test "Rope - toText with union type shows tags" { const debug_text = try rope.toText(arena.allocator()); - try std.testing.expect(std.mem.indexOf(u8, debug_text, "text") != null); - try std.testing.expect(std.mem.indexOf(u8, debug_text, "brk") != null); - try std.testing.expect(std.mem.indexOf(u8, debug_text, "linestart") != null); - try std.testing.expect(std.mem.indexOf(u8, debug_text, "w5") != null); - try std.testing.expect(std.mem.indexOf(u8, debug_text, "w10") != null); + try std.testing.expect(std.mem.find(u8, debug_text, "text") != null); + try std.testing.expect(std.mem.find(u8, debug_text, "brk") != null); + try std.testing.expect(std.mem.find(u8, debug_text, "linestart") != null); + try std.testing.expect(std.mem.find(u8, debug_text, "w5") != null); + try std.testing.expect(std.mem.find(u8, debug_text, "w10") != null); } test "Rope - toText with nested structure" { @@ -1903,8 +1903,8 @@ test "Rope - toText with nested structure" { const debug_text = try rope.toText(arena.allocator()); - try std.testing.expect(std.mem.indexOf(u8, debug_text, "[root") != null); - try std.testing.expect(std.mem.indexOf(u8, debug_text, "[branch") != null); + try std.testing.expect(std.mem.find(u8, debug_text, "[root") != null); + try std.testing.expect(std.mem.find(u8, debug_text, "[branch") != null); } test "Rope - toText after insertions shows updated structure" { @@ -1915,13 +1915,13 @@ test "Rope - toText after insertions shows updated structure" { var rope = try RopeType.from_item(arena.allocator(), .{ .value = 1 }); const before = try rope.toText(arena.allocator()); - try std.testing.expect(std.mem.indexOf(u8, before, "[root") != null); + try std.testing.expect(std.mem.find(u8, before, "[root") != null); try rope.append(.{ .value = 2 }); try rope.append(.{ .value = 3 }); const after = try rope.toText(arena.allocator()); - try std.testing.expect(std.mem.indexOf(u8, after, "[root") != null); + try std.testing.expect(std.mem.find(u8, after, "[root") != null); try std.testing.expect(after.len >= before.len); } @@ -1938,8 +1938,8 @@ test "Rope - toText with custom metrics shows width info" { const debug_text = try rope.toText(arena.allocator()); - try std.testing.expect(std.mem.indexOf(u8, debug_text, "[root") != null); - try std.testing.expect(std.mem.indexOf(u8, debug_text, "w") != null); + try std.testing.expect(std.mem.find(u8, debug_text, "[root") != null); + try std.testing.expect(std.mem.find(u8, debug_text, "w") != null); } test "Rope - toText shows single leaf" { @@ -1951,9 +1951,9 @@ test "Rope - toText shows single leaf" { const debug_text = try rope.toText(arena.allocator()); - try std.testing.expect(std.mem.indexOf(u8, debug_text, "[root") != null); - try std.testing.expect(std.mem.indexOf(u8, debug_text, "[leaf") != null); - try std.testing.expect(std.mem.indexOf(u8, debug_text, "]") != null); + try std.testing.expect(std.mem.find(u8, debug_text, "[root") != null); + try std.testing.expect(std.mem.find(u8, debug_text, "[leaf") != null); + try std.testing.expect(std.mem.find(u8, debug_text, "]") != null); } test "Rope - marker cache MUST update after delete operations" { diff --git a/packages/core/src/zig/tests/terminal_test.zig b/packages/core/src/zig/tests/terminal_test.zig index 36a9acac9f..c802499a9e 100644 --- a/packages/core/src/zig/tests/terminal_test.zig +++ b/packages/core/src/zig/tests/terminal_test.zig @@ -370,7 +370,7 @@ test "notifications - xtversion heuristics prefer documented protocols" { } test "notifications - TERM_FEATURES enables OSC9 protocol" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("TERM_FEATURES", "T2NoH"); @@ -381,7 +381,7 @@ test "notifications - TERM_FEATURES enables OSC9 protocol" { } test "notifications - Zellij env suppresses inherited host notification heuristics" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("ZELLIJ", "0"); try env.put("ZELLIJ_PANE_ID", "1"); @@ -407,7 +407,7 @@ test "notifications - Zellij env suppresses inherited host notification heuristi } test "notifications - Zellij XTVERSION overrides inherited tmux and clears heuristics" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("TMUX", "/tmp/tmux-1000/default,12345,0"); try env.put("TERM", "screen-256color"); @@ -431,7 +431,7 @@ test "notifications - Zellij XTVERSION overrides inherited tmux and clears heuri } test "notifications - explicit protocol override works in tmux" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("TMUX", "/tmp/tmux-1000/default,12345,0"); try env.put("TERM", "screen-256color"); @@ -445,7 +445,7 @@ test "notifications - explicit protocol override works in tmux" { } test "notifications - explicit disable blocks later queries" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("OPENTUI_NOTIFICATION_PROTOCOL", "none"); @@ -459,7 +459,7 @@ test "notifications - explicit disable blocks later queries" { } test "remote detection - auto mode detects SSH environment" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("SSH_CONNECTION", "192.0.2.1 54231 192.0.2.2 22"); @@ -470,7 +470,7 @@ test "remote detection - auto mode detects SSH environment" { } test "remote detection - auto mode detects mosh environment" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("MOSH_CONNECTION", "192.0.2.1 60001"); @@ -480,8 +480,22 @@ test "remote detection - auto mode detects mosh environment" { try testing.expect(term.caps.remote); } +test "remote detection - auto mode ignores local capabilities after forwarded SSH marker" { + var term = Terminal.init(.{ .remote_mode = .auto }); + defer term.deinit(); + + try term.setHostEnvVar(testing.allocator, "SSH_CONNECTION", "192.0.2.1 54231 192.0.2.2 22"); + try term.setHostEnvVar(testing.allocator, "TERM", "xterm-256color"); + try term.setHostEnvVar(testing.allocator, "TERM_PROGRAM", "ghostty"); + + try testing.expect(term.caps.remote); + try testing.expect(!term.caps.ansi256); + try testing.expect(!term.caps.notifications); + try testing.expectEqualStrings("", term.getTerminalName()); +} + test "remote detection - explicit local mode ignores SSH environment" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("SSH_TTY", "/dev/pts/1"); @@ -543,7 +557,7 @@ test "remote without forwarded env map ignores local env overrides" { test "TERM_PROGRAM tmux provides initial tmux version before xtversion" { if (builtin.os.tag == .windows) return error.SkipZigTest; - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("TERM_PROGRAM", "tmux"); try env.put("TERM_PROGRAM_VERSION", "3.6a"); @@ -560,7 +574,7 @@ test "TERM_PROGRAM tmux provides initial tmux version before xtversion" { test "remote applies forwarded env overrides and capability responses" { if (builtin.os.tag == .windows) return error.SkipZigTest; - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("TMUX", "/tmp/tmux-1000/default,12345,0"); try env.put("TERM", "screen-256color"); @@ -599,7 +613,7 @@ test "setHostEnvVar applies env overrides in shared library mode" { } test "environment overrides - enables hyperlinks for WSL Windows Terminal xterm" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("WSL_DISTRO_NAME", "Ubuntu"); try env.put("WT_SESSION", "test-session"); @@ -611,7 +625,7 @@ test "environment overrides - enables hyperlinks for WSL Windows Terminal xterm" } test "environment overrides - does not enable hyperlinks for WSL without WT_SESSION" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("WSL_DISTRO_NAME", "Ubuntu"); try env.put("TERM", "xterm-256color"); @@ -622,7 +636,7 @@ test "environment overrides - does not enable hyperlinks for WSL without WT_SESS } test "environment overrides - does not enable hyperlinks for WSL non-xterm terms" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("WSL_INTEROP", "/run/WSL/123_interop"); try env.put("WT_SESSION", "test-session"); @@ -634,7 +648,7 @@ test "environment overrides - does not enable hyperlinks for WSL non-xterm terms } test "setHostEnvVar detects ansi256 separately from rgb" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("TERM", "screen-256color"); @@ -650,7 +664,7 @@ test "setHostEnvVar detects ansi256 separately from rgb" { } test "environment overrides - WT_SESSION enables rgb and ansi256" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("TERM", "xterm-256color"); try env.put("WT_SESSION", "test-session"); @@ -687,32 +701,31 @@ test "parseXtversion - empty response" { // Test buffer for capturing terminal output const TestWriter = struct { - buffer: std.ArrayListUnmanaged(u8), - allocator: std.mem.Allocator, + buffer: std.Io.Writer.Allocating, pub fn init(allocator: std.mem.Allocator) TestWriter { - return .{ .buffer = .{}, .allocator = allocator }; + return .{ .buffer = .init(allocator) }; } pub fn deinit(self: *TestWriter) void { - self.buffer.deinit(self.allocator); + self.buffer.deinit(); self.* = undefined; } pub fn writeAll(self: *TestWriter, data: []const u8) !void { - try self.buffer.appendSlice(self.allocator, data); + try self.buffer.writer.writeAll(data); } pub fn writeByte(self: *TestWriter, byte: u8) !void { - try self.buffer.append(self.allocator, byte); + try self.buffer.writer.writeByte(byte); } pub fn print(self: *TestWriter, comptime fmt: []const u8, args: anytype) !void { - try self.buffer.writer(self.allocator).print(fmt, args); + try self.buffer.writer.print(fmt, args); } pub fn getWritten(self: *TestWriter) []const u8 { - return self.buffer.items; + return self.buffer.written(); } pub fn reset(self: *TestWriter) void { @@ -721,13 +734,8 @@ const TestWriter = struct { }; test "queryTerminalSend - sends unwrapped queries when not in tmux" { - // Note: This test may fail if running inside tmux since checkEnvironmentOverrides - // reads TMUX/TERM env vars. We test the logic directly instead. var term = Terminal.init(.{}); - // Skip test if actually running in tmux - if (term.isInTmux()) return error.SkipZigTest; - var writer = TestWriter.init(testing.allocator); defer writer.deinit(); @@ -735,24 +743,24 @@ test "queryTerminalSend - sends unwrapped queries when not in tmux" { const output = writer.getWritten(); - const idx_osc_theme_queries = std.mem.indexOf(u8, output, ansi.ANSI.oscThemeQueries).?; - const idx_xtversion = std.mem.indexOf(u8, output, "\x1b[>0q").?; + const idx_osc_theme_queries = std.mem.find(u8, output, ansi.ANSI.oscThemeQueries).?; + const idx_xtversion = std.mem.find(u8, output, "\x1b[>0q").?; // Should contain xtversion - try testing.expect(std.mem.indexOf(u8, output, "\x1b[>0q") != null); + try testing.expect(std.mem.find(u8, output, "\x1b[>0q") != null); try testing.expect(idx_osc_theme_queries < idx_xtversion); - try testing.expect(std.mem.indexOf(u8, output, "\x1b[?996n") == null); + try testing.expect(std.mem.find(u8, output, "\x1b[?996n") == null); // Should contain unwrapped DECRQM queries (single ESC) - try testing.expect(std.mem.indexOf(u8, output, "\x1b[?1016$p") != null); - try testing.expect(std.mem.indexOf(u8, output, "\x1b[?2027$p") != null); - try testing.expect(std.mem.indexOf(u8, output, "\x1b[?u") != null); - try testing.expect(std.mem.indexOf(u8, output, "\x1bP+q4d73\x1b\\") != null); - try testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.kittyGraphicsQuery) != null); - try testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.primaryDeviceAttrs) != null); + try testing.expect(std.mem.find(u8, output, "\x1b[?1016$p") != null); + try testing.expect(std.mem.find(u8, output, "\x1b[?2027$p") != null); + try testing.expect(std.mem.find(u8, output, "\x1b[?u") != null); + try testing.expect(std.mem.find(u8, output, "\x1bP+q4d73\x1b\\") != null); + try testing.expect(std.mem.find(u8, output, ansi.ANSI.kittyGraphicsQuery) != null); + try testing.expect(std.mem.find(u8, output, ansi.ANSI.primaryDeviceAttrs) != null); // Should NOT contain tmux DCS wrapper - try testing.expect(std.mem.indexOf(u8, output, "\x1bPtmux;") == null); + try testing.expect(std.mem.find(u8, output, "\x1bPtmux;") == null); // Should mark capability queries as pending try testing.expect(term.capability_queries_pending); @@ -761,13 +769,10 @@ test "queryTerminalSend - sends unwrapped queries when not in tmux" { } test "queryTerminalSend - sends DCS wrapped queries when in tmux" { - // Note: This test checks logic when tmux is detected. - // We can't easily force tmux detection since checkEnvironmentOverrides resets it, - // so we test this via sendPendingQueries tests instead. - var term = Terminal.init(.{}); - - // Only run the DCS wrapping test if actually in tmux - if (!term.isInTmux()) return error.SkipZigTest; + var env = std.process.Environ.Map.init(testing.allocator); + defer env.deinit(); + try env.put("TMUX", "/tmp/tmux-1000/default,12345,0"); + var term = Terminal.init(.{ .env_map = &env }); var writer = TestWriter.init(testing.allocator); defer writer.deinit(); @@ -776,19 +781,19 @@ test "queryTerminalSend - sends DCS wrapped queries when in tmux" { const output = writer.getWritten(); - const idx_osc_theme_queries = std.mem.indexOf(u8, output, ansi.ANSI.oscThemeQueries).?; - const idx_xtversion = std.mem.indexOf(u8, output, "\x1b[>0q").?; + const idx_osc_theme_queries = std.mem.find(u8, output, ansi.ANSI.oscThemeQueries).?; + const idx_xtversion = std.mem.find(u8, output, "\x1b[>0q").?; // Should contain xtversion (unwrapped - used for detection) - try testing.expect(std.mem.indexOf(u8, output, "\x1b[>0q") != null); + try testing.expect(std.mem.find(u8, output, "\x1b[>0q") != null); try testing.expect(idx_osc_theme_queries < idx_xtversion); - try testing.expect(std.mem.indexOf(u8, output, "\x1b[?996n") == null); - try testing.expect(std.mem.indexOf(u8, output, "\x1bPtmux;\x1b\x1b]10;?") == null); + try testing.expect(std.mem.find(u8, output, "\x1b[?996n") == null); + try testing.expect(std.mem.find(u8, output, "\x1bPtmux;\x1b\x1b]10;?") == null); // Should contain tmux DCS wrapper start and doubled ESC for queries // wrapForTmux wraps all queries together with one DCS envelope - try testing.expect(std.mem.indexOf(u8, output, "\x1bPtmux;\x1b\x1bP+q4d73\x1b\x1b\\") != null); - try testing.expect(std.mem.indexOf(u8, output, "\x1b\x1b[?1016$p") != null); + try testing.expect(std.mem.find(u8, output, "\x1bPtmux;\x1b\x1bP+q4d73\x1b\x1b\\") != null); + try testing.expect(std.mem.find(u8, output, "\x1b\x1b[?1016$p") != null); // Should NOT mark capability queries as pending (already sent wrapped) try testing.expect(!term.capability_queries_pending); @@ -797,7 +802,7 @@ test "queryTerminalSend - sends DCS wrapped queries when in tmux" { test "queryTerminalSend - sends plain theme queries when TMUX is set" { if (builtin.os.tag == .windows) return error.SkipZigTest; - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("TMUX", "/tmp/tmux-1000/default,12345,0"); try env.put("TERM", "screen-256color"); @@ -811,9 +816,9 @@ test "queryTerminalSend - sends plain theme queries when TMUX is set" { const output = writer.getWritten(); try testing.expectEqual(Terminal.Multiplexer.tmux, term.multiplexer); - try testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.oscThemeQueries) != null); - try testing.expect(std.mem.indexOf(u8, output, "\x1bPtmux;\x1b\x1b]10;?") == null); - try testing.expect(std.mem.indexOf(u8, output, "\x1b[?996n") == null); + try testing.expect(std.mem.find(u8, output, ansi.ANSI.oscThemeQueries) != null); + try testing.expect(std.mem.find(u8, output, "\x1bPtmux;\x1b\x1b]10;?") == null); + try testing.expect(std.mem.find(u8, output, "\x1b[?996n") == null); } test "sendPendingQueries - sends wrapped queries after tmux detected via xtversion" { @@ -835,11 +840,11 @@ test "sendPendingQueries - sends wrapped queries after tmux detected via xtversi const output = writer.getWritten(); // Should send DCS wrapped capability queries (wrapForTmux wraps all queries together) - try testing.expect(std.mem.indexOf(u8, output, "\x1bPtmux;\x1b\x1bP+q4d73\x1b\x1b\\") != null); - try testing.expect(std.mem.indexOf(u8, output, "\x1b\x1b[?1016$p") != null); + try testing.expect(std.mem.find(u8, output, "\x1bPtmux;\x1b\x1bP+q4d73\x1b\x1b\\") != null); + try testing.expect(std.mem.find(u8, output, "\x1b\x1b[?1016$p") != null); // Should send DCS wrapped graphics query - try testing.expect(std.mem.indexOf(u8, output, "\x1bPtmux;\x1b\x1b_G") != null); + try testing.expect(std.mem.find(u8, output, "\x1bPtmux;\x1b\x1b_G") != null); // Should clear pending flags try testing.expect(!term.capability_queries_pending); @@ -867,10 +872,10 @@ test "sendPendingQueries - clears already-sent direct graphics probes after non- const output = writer.getWritten(); // Should NOT send DCS wrapped capability queries (not tmux) - try testing.expect(std.mem.indexOf(u8, output, "\x1bPtmux;") == null); + try testing.expect(std.mem.find(u8, output, "\x1bPtmux;") == null); // Initial startup already sent the direct graphics query. - try testing.expect(std.mem.indexOf(u8, output, "\x1b_Gi=31337") == null); + try testing.expect(std.mem.find(u8, output, "\x1b_Gi=31337") == null); // Should clear pending flags try testing.expect(!term.capability_queries_pending); @@ -894,8 +899,8 @@ test "sendPendingQueries - waits for xtversion before any passthrough retry" { const output = writer.getWritten(); // Initial startup already sent the direct graphics query. - try testing.expect(std.mem.indexOf(u8, output, "\x1b_Gi=31337") == null); - try testing.expect(std.mem.indexOf(u8, output, "\x1bPtmux;") == null); + try testing.expect(std.mem.find(u8, output, "\x1b_Gi=31337") == null); + try testing.expect(std.mem.find(u8, output, "\x1bPtmux;") == null); try testing.expect(term.graphics_query_pending); @@ -919,7 +924,7 @@ test "sendPendingQueries - skips graphics when skip_graphics_query is set" { try testing.expect(!did_send); const output = writer.getWritten(); - try testing.expect(std.mem.indexOf(u8, output, "Gi=31337") == null); + try testing.expect(std.mem.find(u8, output, "Gi=31337") == null); } test "isXtversionTmux - detects tmux from xtversion" { @@ -1055,7 +1060,7 @@ test "processCapabilityResponse - XTGETTCAP Ms only establishes positive support test "writeClipboard - generates basic OSC52 sequence" { if (builtin.os.tag == .windows) return error.SkipZigTest; - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); var term = Terminal.init(.{ .env_map = &env }); @@ -1104,7 +1109,7 @@ test "writeNotification - returns false when unsupported" { } test "writeNotification - writes OSC99 title and body with base64 payloads" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); var term = Terminal.init(.{ .env_map = &env }); term.caps.notifications = true; @@ -1120,7 +1125,7 @@ test "writeNotification - writes OSC99 title and body with base64 payloads" { } test "writeNotification - writes OSC777 and sanitizes semicolons and controls" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); var term = Terminal.init(.{ .env_map = &env }); term.caps.notifications = true; @@ -1136,7 +1141,7 @@ test "writeNotification - writes OSC777 and sanitizes semicolons and controls" { } test "writeNotification - writes OSC9 combined title and message" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); var term = Terminal.init(.{ .env_map = &env }); term.caps.notifications = true; @@ -1152,7 +1157,7 @@ test "writeNotification - writes OSC9 combined title and message" { } test "writeNotification - wraps OSC777 in tmux passthrough" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("TMUX", "/tmp/tmux-1000/default,12345,0"); @@ -1167,12 +1172,12 @@ test "writeNotification - wraps OSC777 in tmux passthrough" { try testing.expect(ok); try testing.expect(std.mem.startsWith(u8, writer.getWritten(), "\x1bPtmux;")); - try testing.expect(std.mem.indexOf(u8, writer.getWritten(), "\x1b\x1b]777;notify;Title;Body") != null); + try testing.expect(std.mem.find(u8, writer.getWritten(), "\x1b\x1b]777;notify;Title;Body") != null); try testing.expect(std.mem.endsWith(u8, writer.getWritten(), "\x1b\\")); } test "writeNotification - wraps OSC99 in tmux passthrough" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("TMUX", "/tmp/tmux-1000/default,12345,0"); @@ -1187,12 +1192,12 @@ test "writeNotification - wraps OSC99 in tmux passthrough" { try testing.expect(ok); try testing.expect(std.mem.startsWith(u8, writer.getWritten(), "\x1bPtmux;")); - try testing.expect(std.mem.indexOf(u8, writer.getWritten(), "\x1b\x1b]99;i=opentui-1:p=body:e=1:d=1;Qm9keQ==") != null); + try testing.expect(std.mem.find(u8, writer.getWritten(), "\x1b\x1b]99;i=opentui-1:p=body:e=1:d=1;Qm9keQ==") != null); try testing.expect(std.mem.endsWith(u8, writer.getWritten(), "\x1b\\")); } test "writeNotification - writes raw OSC99 in Zellij" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("TMUX", "/tmp/tmux-1000/default,12345,0"); try env.put("TERM_PROGRAM", "ghostty"); @@ -1214,7 +1219,7 @@ test "writeNotification - writes raw OSC99 in Zellij" { test "writeClipboard - supports different targets" { if (builtin.os.tag == .windows) return error.SkipZigTest; - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); var term = Terminal.init(.{ .env_map = &env }); @@ -1224,15 +1229,15 @@ test "writeClipboard - supports different targets" { defer writer.deinit(); try term.writeClipboard(&writer, .primary, "test"); - try testing.expect(std.mem.indexOf(u8, writer.getWritten(), "\x1b]52;p;") != null); + try testing.expect(std.mem.find(u8, writer.getWritten(), "\x1b]52;p;") != null); writer.reset(); try term.writeClipboard(&writer, .select, "test"); - try testing.expect(std.mem.indexOf(u8, writer.getWritten(), "\x1b]52;s;") != null); + try testing.expect(std.mem.find(u8, writer.getWritten(), "\x1b]52;s;") != null); writer.reset(); try term.writeClipboard(&writer, .secondary, "test"); - try testing.expect(std.mem.indexOf(u8, writer.getWritten(), "\x1b]52;q;") != null); + try testing.expect(std.mem.find(u8, writer.getWritten(), "\x1b]52;q;") != null); } test "writeClipboard - returns error when OSC52 not supported" { @@ -1251,7 +1256,7 @@ test "writeClipboard - returns error when OSC52 not supported" { test "writeClipboard - emits optimistically when XTGETTCAP state is unknown" { if (builtin.os.tag == .windows) return error.SkipZigTest; - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); var term = Terminal.init(.{ .env_map = &env }); try testing.expectEqual(Terminal.Osc52Support.unknown, term.osc52_support); @@ -1266,7 +1271,7 @@ test "writeClipboard - emits optimistically when XTGETTCAP state is unknown" { test "writeClipboard - writes large payload without a fixed buffer limit" { if (builtin.os.tag == .windows) return error.SkipZigTest; - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); var term = Terminal.init(.{ .env_map = &env }); @@ -1287,7 +1292,7 @@ test "writeClipboard - writes large payload without a fixed buffer limit" { test "writeClipboard - writes large payload through tmux passthrough" { if (builtin.os.tag == .windows) return error.SkipZigTest; - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("TMUX", "/tmp/tmux-1000/default,12345,0"); var term = Terminal.init(.{ .env_map = &env }); @@ -1310,7 +1315,7 @@ test "writeClipboard - writes large payload through tmux passthrough" { test "writeClipboard - chunks large payload through GNU Screen passthrough" { if (builtin.os.tag == .windows) return error.SkipZigTest; - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("STY", "12345.pts-0.hostname"); var term = Terminal.init(.{ .env_map = &env }); @@ -1328,9 +1333,9 @@ test "writeClipboard - chunks large payload through GNU Screen passthrough" { try testing.expect(std.mem.endsWith(u8, output, ansi.ANSI.screenDcsEnd)); var frame_start: usize = 0; - while (std.mem.indexOfPos(u8, output, frame_start, ansi.ANSI.screenDcsStart)) |start| { + while (std.mem.findPos(u8, output, frame_start, ansi.ANSI.screenDcsStart)) |start| { const content_start = start + ansi.ANSI.screenDcsStart.len; - const next_start = std.mem.indexOfPos(u8, output, content_start, ansi.ANSI.screenDcsStart) orelse output.len; + const next_start = std.mem.findPos(u8, output, content_start, ansi.ANSI.screenDcsStart) orelse output.len; try testing.expect(next_start - content_start <= Terminal.SCREEN_PASSTHROUGH_CHUNK_SIZE + ansi.ANSI.screenDcsEnd.len); frame_start = next_start; } @@ -1339,7 +1344,7 @@ test "writeClipboard - chunks large payload through GNU Screen passthrough" { test "writeClipboard - base64 encodes raw UTF-8 bytes" { if (builtin.os.tag == .windows) return error.SkipZigTest; - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); var term = Terminal.init(.{ .env_map = &env }); @@ -1353,7 +1358,7 @@ test "writeClipboard - base64 encodes raw UTF-8 bytes" { test "writeClipboard - handles base64 padding and encoding chunk boundaries" { if (builtin.os.tag == .windows) return error.SkipZigTest; - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); var term = Terminal.init(.{ .env_map = &env }); var writer = TestWriter.init(testing.allocator); @@ -1395,7 +1400,7 @@ test "clipboardSequenceSize - matches plain, tmux, and Screen output" { }; for (environments) |environment| { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); if (environment.key) |key| try env.put(key, environment.value); var term = Terminal.init(.{ .env_map = &env }); @@ -1421,7 +1426,7 @@ test "clipboardSequenceSize - rejects payloads beyond the FFI limit" { test "writeClipboard - Screen framing crosses the 252-byte boundary" { if (builtin.os.tag == .windows) return error.SkipZigTest; - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("STY", "12345.pts-0.hostname"); var term = Terminal.init(.{ .env_map = &env }); @@ -1443,7 +1448,7 @@ test "writeClipboard - Screen framing crosses the 252-byte boundary" { test "writeClipboard - wraps in DCS passthrough for tmux" { if (builtin.os.tag == .windows) return error.SkipZigTest; - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("TMUX", "/tmp/tmux-1000/default,12345,0"); @@ -1461,13 +1466,13 @@ test "writeClipboard - wraps in DCS passthrough for tmux" { // Should end with DCS terminator try testing.expect(std.mem.endsWith(u8, output, "\x1b\\")); // Should have doubled ESC characters inside - try testing.expect(std.mem.indexOf(u8, output, "\x1b\x1b") != null); + try testing.expect(std.mem.find(u8, output, "\x1b\x1b") != null); } test "writeClipboard - wraps in DCS passthrough for GNU Screen" { if (builtin.os.tag == .windows) return error.SkipZigTest; - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("STY", "12345.pts-0.hostname"); @@ -1486,13 +1491,13 @@ test "writeClipboard - wraps in DCS passthrough for GNU Screen" { // Should end with DCS terminator try testing.expect(std.mem.endsWith(u8, output, "\x1b\\")); // Should have doubled ESC characters - try testing.expect(std.mem.indexOf(u8, output, "\x1b\x1b") != null); + try testing.expect(std.mem.find(u8, output, "\x1b\x1b") != null); } test "writeClipboard - handles tmux sessions" { if (builtin.os.tag == .windows) return error.SkipZigTest; - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("TMUX", "/tmp/tmux-1000/default,12345,0"); @@ -1510,7 +1515,7 @@ test "writeClipboard - handles tmux sessions" { // Should end with DCS terminator try testing.expect(std.mem.endsWith(u8, output, "\x1b\\")); // Should have doubled ESC characters - try testing.expect(std.mem.indexOf(u8, output, "\x1b\x1b") != null); + try testing.expect(std.mem.find(u8, output, "\x1b\x1b") != null); } test "caps.osc52 - clipboard capability flag" { @@ -1540,7 +1545,7 @@ fn countSubstring(haystack: []const u8, needle: []const u8) usize { test "queryTerminalSend - skips OSC 66 queries when OPENTUI_FORCE_EXPLICIT_WIDTH=false" { if (builtin.os.tag == .windows) return error.SkipZigTest; - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("OPENTUI_FORCE_EXPLICIT_WIDTH", "false"); @@ -1554,10 +1559,10 @@ test "queryTerminalSend - skips OSC 66 queries when OPENTUI_FORCE_EXPLICIT_WIDTH const output = writer.getWritten(); // Should not contain OSC 66 queries - try testing.expect(std.mem.indexOf(u8, output, "\x1b]66;") == null); + try testing.expect(std.mem.find(u8, output, "\x1b]66;") == null); // Should still contain other queries - try testing.expect(std.mem.indexOf(u8, output, "\x1b[>0q") != null); // xtversion + try testing.expect(std.mem.find(u8, output, "\x1b[>0q") != null); // xtversion // Verify the flag was set correctly try testing.expect(term.skip_explicit_width_query); @@ -1567,7 +1572,7 @@ test "queryTerminalSend - skips OSC 66 queries when OPENTUI_FORCE_EXPLICIT_WIDTH test "queryTerminalSend - sends OSC 66 queries by default" { if (builtin.os.tag == .windows) return error.SkipZigTest; - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); var term = Terminal.init(.{ .env_map = &env }); @@ -1580,10 +1585,10 @@ test "queryTerminalSend - sends OSC 66 queries by default" { const output = writer.getWritten(); // Should contain OSC 66 explicit width query - try testing.expect(std.mem.indexOf(u8, output, "\x1b]66;w=1; \x1b\\") != null); + try testing.expect(std.mem.find(u8, output, "\x1b]66;w=1; \x1b\\") != null); // Should contain OSC 66 scaled text query - try testing.expect(std.mem.indexOf(u8, output, "\x1b]66;s=2; \x1b\\") != null); + try testing.expect(std.mem.find(u8, output, "\x1b]66;s=2; \x1b\\") != null); // Verify the flag was not set try testing.expect(!term.skip_explicit_width_query); @@ -1592,7 +1597,7 @@ test "queryTerminalSend - sends OSC 66 queries by default" { test "queryTerminalSend - sends OSC 66 queries when OPENTUI_FORCE_EXPLICIT_WIDTH=true" { if (builtin.os.tag == .windows) return error.SkipZigTest; - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("OPENTUI_FORCE_EXPLICIT_WIDTH", "true"); @@ -1606,8 +1611,8 @@ test "queryTerminalSend - sends OSC 66 queries when OPENTUI_FORCE_EXPLICIT_WIDTH const output = writer.getWritten(); // Should contain OSC 66 queries - try testing.expect(std.mem.indexOf(u8, output, "\x1b]66;w=1; \x1b\\") != null); - try testing.expect(std.mem.indexOf(u8, output, "\x1b]66;s=2; \x1b\\") != null); + try testing.expect(std.mem.find(u8, output, "\x1b]66;w=1; \x1b\\") != null); + try testing.expect(std.mem.find(u8, output, "\x1b]66;s=2; \x1b\\") != null); // Verify the capability was forced on try testing.expect(term.caps.explicit_width); @@ -1615,7 +1620,7 @@ test "queryTerminalSend - sends OSC 66 queries when OPENTUI_FORCE_EXPLICIT_WIDTH } test "enableDetectedFeatures - sends initial theme queries" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); var term = Terminal.init(.{ .env_map = &env }); @@ -1626,10 +1631,10 @@ test "enableDetectedFeatures - sends initial theme queries" { const output = writer.getWritten(); - try testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.colorSchemeSet) != null); - try testing.expect(std.mem.indexOf(u8, output, "\x1b]10;?\x07") != null); - try testing.expect(std.mem.indexOf(u8, output, "\x1b]11;?\x07") != null); - try testing.expect(std.mem.indexOf(u8, output, "\x1b[?996n") == null); + try testing.expect(std.mem.find(u8, output, ansi.ANSI.colorSchemeSet) != null); + try testing.expect(std.mem.find(u8, output, "\x1b]10;?\x07") != null); + try testing.expect(std.mem.find(u8, output, "\x1b]11;?\x07") != null); + try testing.expect(std.mem.find(u8, output, "\x1b[?996n") == null); try testing.expect(term.state.theme_queries_sent); } @@ -1641,11 +1646,11 @@ test "setMouseMode - enable without movement keeps click/drag only" { try term.setMouseMode(&writer, true, false); const output = writer.getWritten(); - const idx_disable_any = std.mem.indexOf(u8, output, ansi.ANSI.disableAnyEventTracking).?; - const idx_enable_mouse = std.mem.indexOf(u8, output, ansi.ANSI.enableMouseTracking).?; - const idx_enable_button = std.mem.indexOf(u8, output, ansi.ANSI.enableButtonEventTracking).?; - const idx_enable_sgr = std.mem.indexOf(u8, output, ansi.ANSI.enableSGRMouseMode).?; - try testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.enableAnyEventTracking) == null); + const idx_disable_any = std.mem.find(u8, output, ansi.ANSI.disableAnyEventTracking).?; + const idx_enable_mouse = std.mem.find(u8, output, ansi.ANSI.enableMouseTracking).?; + const idx_enable_button = std.mem.find(u8, output, ansi.ANSI.enableButtonEventTracking).?; + const idx_enable_sgr = std.mem.find(u8, output, ansi.ANSI.enableSGRMouseMode).?; + try testing.expect(std.mem.find(u8, output, ansi.ANSI.enableAnyEventTracking) == null); try testing.expect(idx_disable_any < idx_enable_mouse); try testing.expect(idx_enable_mouse < idx_enable_button); try testing.expect(idx_enable_button < idx_enable_sgr); @@ -1662,14 +1667,14 @@ test "setMouseMode - enable with movement enables any-event tracking" { try term.setMouseMode(&writer, true, true); const output = writer.getWritten(); - const idx_enable_mouse = std.mem.indexOf(u8, output, ansi.ANSI.enableMouseTracking).?; - const idx_enable_button = std.mem.indexOf(u8, output, ansi.ANSI.enableButtonEventTracking).?; - const idx_enable_any = std.mem.indexOf(u8, output, ansi.ANSI.enableAnyEventTracking).?; - const idx_enable_sgr = std.mem.indexOf(u8, output, ansi.ANSI.enableSGRMouseMode).?; + const idx_enable_mouse = std.mem.find(u8, output, ansi.ANSI.enableMouseTracking).?; + const idx_enable_button = std.mem.find(u8, output, ansi.ANSI.enableButtonEventTracking).?; + const idx_enable_any = std.mem.find(u8, output, ansi.ANSI.enableAnyEventTracking).?; + const idx_enable_sgr = std.mem.find(u8, output, ansi.ANSI.enableSGRMouseMode).?; try testing.expect(idx_enable_mouse < idx_enable_button); try testing.expect(idx_enable_button < idx_enable_any); try testing.expect(idx_enable_any < idx_enable_sgr); - try testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.disableAnyEventTracking) == null); + try testing.expect(std.mem.find(u8, output, ansi.ANSI.disableAnyEventTracking) == null); try testing.expect(term.state.mouse); try testing.expect(term.state.mouse_movement); @@ -1686,14 +1691,14 @@ test "restoreTerminalModes - respects mouse movement setting" { try term.restoreTerminalModes(&writer); const output = writer.getWritten(); - const idx_disable_any = std.mem.indexOf(u8, output, ansi.ANSI.disableAnyEventTracking).?; - const idx_enable_mouse = std.mem.indexOf(u8, output, ansi.ANSI.enableMouseTracking).?; - const idx_enable_button = std.mem.indexOf(u8, output, ansi.ANSI.enableButtonEventTracking).?; - const idx_enable_sgr = std.mem.indexOf(u8, output, ansi.ANSI.enableSGRMouseMode).?; + const idx_disable_any = std.mem.find(u8, output, ansi.ANSI.disableAnyEventTracking).?; + const idx_enable_mouse = std.mem.find(u8, output, ansi.ANSI.enableMouseTracking).?; + const idx_enable_button = std.mem.find(u8, output, ansi.ANSI.enableButtonEventTracking).?; + const idx_enable_sgr = std.mem.find(u8, output, ansi.ANSI.enableSGRMouseMode).?; try testing.expect(idx_disable_any < idx_enable_mouse); try testing.expect(idx_enable_mouse < idx_enable_button); try testing.expect(idx_enable_button < idx_enable_sgr); - try testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.enableAnyEventTracking) == null); + try testing.expect(std.mem.find(u8, output, ansi.ANSI.enableAnyEventTracking) == null); } test "resetState - force-disables mouse when cleanup is pending and state drifted false" { @@ -1708,10 +1713,10 @@ test "resetState - force-disables mouse when cleanup is pending and state drifte try term.resetState(&writer); const output = writer.getWritten(); - const idx_disable_any = std.mem.indexOf(u8, output, ansi.ANSI.disableAnyEventTracking).?; - const idx_disable_button = std.mem.indexOf(u8, output, ansi.ANSI.disableButtonEventTracking).?; - const idx_disable_mouse = std.mem.indexOf(u8, output, ansi.ANSI.disableMouseTracking).?; - const idx_disable_sgr = std.mem.indexOf(u8, output, ansi.ANSI.disableSGRMouseMode).?; + const idx_disable_any = std.mem.find(u8, output, ansi.ANSI.disableAnyEventTracking).?; + const idx_disable_button = std.mem.find(u8, output, ansi.ANSI.disableButtonEventTracking).?; + const idx_disable_mouse = std.mem.find(u8, output, ansi.ANSI.disableMouseTracking).?; + const idx_disable_sgr = std.mem.find(u8, output, ansi.ANSI.disableSGRMouseMode).?; try testing.expect(idx_disable_any < idx_disable_button); try testing.expect(idx_disable_button < idx_disable_mouse); try testing.expect(idx_disable_mouse < idx_disable_sgr); @@ -1727,8 +1732,8 @@ test "resetState - skips mouse disable when mouse was never enabled" { try term.resetState(&writer); const output = writer.getWritten(); - try testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.disableAnyEventTracking) == null); - try testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.disableButtonEventTracking) == null); - try testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.disableMouseTracking) == null); - try testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.disableSGRMouseMode) == null); + try testing.expect(std.mem.find(u8, output, ansi.ANSI.disableAnyEventTracking) == null); + try testing.expect(std.mem.find(u8, output, ansi.ANSI.disableButtonEventTracking) == null); + try testing.expect(std.mem.find(u8, output, ansi.ANSI.disableMouseTracking) == null); + try testing.expect(std.mem.find(u8, output, ansi.ANSI.disableSGRMouseMode) == null); } diff --git a/packages/core/src/zig/tests/test-renderer.zig b/packages/core/src/zig/tests/test-renderer.zig index 2edc65a735..8f61831b8f 100644 --- a/packages/core/src/zig/tests/test-renderer.zig +++ b/packages/core/src/zig/tests/test-renderer.zig @@ -5,10 +5,10 @@ const gp = @import("../grapheme.zig"); pub const TestMemoryOutput = struct { allocator: std.mem.Allocator, - bytes: std.ArrayListUnmanaged(u8) = .{}, + bytes: std.ArrayListUnmanaged(u8) = .empty, last_write_start: usize = 0, last_write_len: usize = 0, - mutex: std.Thread.Mutex = .{}, + mutex: std.Io.Mutex = .init, thread_safe: bool = false, pub fn init(allocator: std.mem.Allocator) TestMemoryOutput { @@ -25,8 +25,8 @@ pub const TestMemoryOutput = struct { fn write(ctx: *anyopaque, data: []const u8) void { const self: *TestMemoryOutput = @ptrCast(@alignCast(ctx)); - if (self.thread_safe) self.mutex.lock(); - defer if (self.thread_safe) self.mutex.unlock(); + if (self.thread_safe) self.mutex.lockUncancelable(std.testing.io); + defer if (self.thread_safe) self.mutex.unlock(std.testing.io); const start = self.bytes.items.len; self.bytes.appendSlice(self.allocator, data) catch @panic("memory output write failed"); @@ -51,7 +51,7 @@ pub const TestRenderer = struct { // empty map so tests never observe the host environment (TMUX, STY, TERM, // ...). Heap-allocated because Terminal borrows a stable pointer while // TestRenderer is returned by value. - env_map: *std.process.EnvMap, + env_map: *std.process.Environ.Map, renderer: *renderer.CliRenderer, const CreateConfig = struct { @@ -90,9 +90,9 @@ pub const TestRenderer = struct { memory.thread_safe = config.thread_safe; errdefer memory.deinit(); - const env_map = try allocator.create(std.process.EnvMap); + const env_map = try allocator.create(std.process.Environ.Map); errdefer allocator.destroy(env_map); - env_map.* = std.process.EnvMap.init(allocator); + env_map.* = std.process.Environ.Map.init(allocator); errdefer env_map.deinit(); for (config.env_vars) |env_var| { try env_map.put(env_var.key, env_var.value); 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 b979067bdf..fba852d429 100644 --- a/packages/core/src/zig/tests/text-buffer-drawing_test.zig +++ b/packages/core/src/zig/tests/text-buffer-drawing_test.zig @@ -586,7 +586,7 @@ test "drawTextBuffer - very long unwrapped line clipping" { var view = try TextBufferView.init(std.testing.allocator, tb); defer view.deinit(); - var long_text: std.ArrayListUnmanaged(u8) = .{}; + var long_text: std.ArrayListUnmanaged(u8) = .empty; defer long_text.deinit(std.testing.allocator); try long_text.appendNTimes(std.testing.allocator, 'A', 200); @@ -1304,14 +1304,8 @@ test "loadFile - loads and renders file correctly" { var tmp = tmpdir; defer tmp.cleanup(); - const file = try tmp.dir.createFile("test.txt", .{}); - try file.writeAll(test_content); - file.close(); - - const dir_path = try tmp.dir.realpathAlloc(std.testing.allocator, "."); - defer std.testing.allocator.free(dir_path); - - const file_path = try std.fs.path.join(std.testing.allocator, &[_][]const u8{ dir_path, "test.txt" }); + try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "test.txt", .data = test_content }); + const file_path = try tmp.dir.realPathFileAlloc(std.testing.io, "test.txt", std.testing.allocator); defer std.testing.allocator.free(file_path); try tb.loadFile(file_path); @@ -1409,9 +1403,9 @@ test "drawTextBuffer - horizontal viewport offset with multiple lines" { const written = try opt_buffer.writeResolvedChars(&out_buffer, false); const result = out_buffer[0..written]; - try std.testing.expect(std.mem.indexOf(u8, result, "DEFGHIJK") != null); - try std.testing.expect(std.mem.indexOf(u8, result, "3456789!") != null); - try std.testing.expect(std.mem.indexOf(u8, result, "[\\]^_`{|") != null); + try std.testing.expect(std.mem.find(u8, result, "DEFGHIJK") != null); + try std.testing.expect(std.mem.find(u8, result, "3456789!") != null); + try std.testing.expect(std.mem.find(u8, result, "[\\]^_`{|") != null); } test "drawTextBuffer - combined horizontal and vertical viewport offsets" { @@ -1447,8 +1441,8 @@ test "drawTextBuffer - combined horizontal and vertical viewport offsets" { const written = try opt_buffer.writeResolvedChars(&out_buffer, false); const result = out_buffer[0..written]; - try std.testing.expect(std.mem.indexOf(u8, result, "KLMNOPQRST") != null); - try std.testing.expect(std.mem.indexOf(u8, result, "UVWXYZ0123") != null); + try std.testing.expect(std.mem.find(u8, result, "KLMNOPQRST") != null); + try std.testing.expect(std.mem.find(u8, result, "UVWXYZ0123") != null); } test "drawTextBuffer - horizontal viewport stops rendering at viewport width" { @@ -1549,7 +1543,7 @@ 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.ArrayListUnmanaged(u8) = .{}; + var long_line: std.ArrayListUnmanaged(u8) = .empty; defer long_line.deinit(std.testing.allocator); try long_line.appendNTimes(std.testing.allocator, 'A', 1000); @@ -1955,7 +1949,7 @@ test "drawTextBuffer - mixed ASCII and Unicode with emoji renders completely" { const written = try opt_buffer.writeResolvedChars(&out_buffer, false); const result = out_buffer[0..written]; - try std.testing.expect(std.mem.indexOf(u8, result, "- ✅ All 881 native tests passs") != null); + try std.testing.expect(std.mem.find(u8, result, "- ✅ All 881 native tests passs") != null); const plain_text = tb.getPlainTextIntoBuffer(&out_buffer); const plain_result = out_buffer[0..plain_text]; @@ -2062,16 +2056,16 @@ test "drawTextBuffer - complex multilingual text with diverse scripts and emojis const plain_text = plain_buffer[0..plain_len]; // Verify some key multilingual content is present - try std.testing.expect(std.mem.indexOf(u8, plain_text, "संस्कृति") != null); - try std.testing.expect(std.mem.indexOf(u8, plain_text, "नमस्ते") != null); - try std.testing.expect(std.mem.indexOf(u8, plain_text, "漢字") != null); - try std.testing.expect(std.mem.indexOf(u8, plain_text, "한글") != null); - try std.testing.expect(std.mem.indexOf(u8, plain_text, "தமிழ்") != null); - try std.testing.expect(std.mem.indexOf(u8, plain_text, "বাংলা") != null); - try std.testing.expect(std.mem.indexOf(u8, plain_text, "ಕನ್ನಡ") != null); - try std.testing.expect(std.mem.indexOf(u8, plain_text, "മലയാളം") != null); - try std.testing.expect(std.mem.indexOf(u8, plain_text, "🌟") != null); - try std.testing.expect(std.mem.indexOf(u8, plain_text, "🙏") != null); + try std.testing.expect(std.mem.find(u8, plain_text, "संस्कृति") != null); + try std.testing.expect(std.mem.find(u8, plain_text, "नमस्ते") != null); + try std.testing.expect(std.mem.find(u8, plain_text, "漢字") != null); + try std.testing.expect(std.mem.find(u8, plain_text, "한글") != null); + try std.testing.expect(std.mem.find(u8, plain_text, "தமிழ்") != null); + try std.testing.expect(std.mem.find(u8, plain_text, "বাংলা") != null); + try std.testing.expect(std.mem.find(u8, plain_text, "ಕನ್ನಡ") != null); + try std.testing.expect(std.mem.find(u8, plain_text, "മലയാളം") != null); + try std.testing.expect(std.mem.find(u8, plain_text, "🌟") != null); + try std.testing.expect(std.mem.find(u8, plain_text, "🙏") != null); // Test with no wrapping view.setWrapMode(.none); @@ -3009,17 +3003,17 @@ test "drawTextBuffer - Chinese text with wrapping no stray bytes" { try std.testing.expect(std.unicode.utf8ValidateSlice(result)); // Verify that the original text is contained in the output (with possible spaces/newlines from wrapping) - try std.testing.expect(std.mem.indexOf(u8, result, "完整的验证") != null); - try std.testing.expect(std.mem.indexOf(u8, result, "实时输入验证和错误处理") != null); + try std.testing.expect(std.mem.find(u8, result, "完整的验证") != null); + try std.testing.expect(std.mem.find(u8, result, "实时输入验证和错误处理") != null); // Check specific problematic line - should NOT contain stray bytes // The line should be present correctly (possibly wrapped with spaces) // But there should be NO stray å character or partial UTF-8 sequences - try std.testing.expect(std.mem.indexOf(u8, result, "å式") == null); // This should NOT appear - try std.testing.expect(std.mem.indexOf(u8, result, "å") == null); // No stray partial bytes + try std.testing.expect(std.mem.find(u8, result, "å式") == null); // This should NOT appear + try std.testing.expect(std.mem.find(u8, result, "å") == null); // No stray partial bytes // Verify the problematic characters appear correctly - try std.testing.expect(std.mem.indexOf(u8, result, "形式") != null); + try std.testing.expect(std.mem.find(u8, result, "形式") != null); } test "drawTextBuffer - Chinese text WITHOUT wrapping no duplicate chunks" { @@ -3078,10 +3072,10 @@ test "drawTextBuffer - Chinese text WITHOUT wrapping no duplicate chunks" { try std.testing.expect(std.unicode.utf8ValidateSlice(result)); // Should NOT contain stray bytes - try std.testing.expect(std.mem.indexOf(u8, result, "å") == null); + try std.testing.expect(std.mem.find(u8, result, "å") == null); // All text should be present - try std.testing.expect(std.mem.indexOf(u8, result, "完整的验证 - 实时输入验证和错误处理") != null); + try std.testing.expect(std.mem.find(u8, result, "完整的验证 - 实时输入验证和错误处理") != null); } test "drawTextBuffer - Chinese text with CHAR wrapping no stray bytes" { @@ -3132,11 +3126,11 @@ test "drawTextBuffer - Chinese text with CHAR wrapping no stray bytes" { try std.testing.expect(std.unicode.utf8ValidateSlice(result)); // Should NOT contain stray bytes - try std.testing.expect(std.mem.indexOf(u8, result, "å") == null); + try std.testing.expect(std.mem.find(u8, result, "å") == null); // Verify the problematic characters appear correctly - try std.testing.expect(std.mem.indexOf(u8, result, "形式") != null); - try std.testing.expect(std.mem.indexOf(u8, result, "完整的验证") != null); + try std.testing.expect(std.mem.find(u8, result, "形式") != null); + try std.testing.expect(std.mem.find(u8, result, "完整的验证") != null); } test "drawTextBuffer - word wrap CJK mixed text without break points" { @@ -3220,7 +3214,7 @@ test "drawTextBuffer - word wrap CJK text preserves UTF-8 boundaries" { const result = out_buffer[0..written]; try std.testing.expect(std.unicode.utf8ValidateSlice(result)); - try std.testing.expect(std.mem.indexOf(u8, result, "ä") == null); + try std.testing.expect(std.mem.find(u8, result, "ä") == null); var i: usize = 0; while (i < result.len) : (i += 1) { @@ -3287,5 +3281,5 @@ test "drawTextBuffer - Thai ว่ grapheme in quotes occupies one cell" { const written = try opt_buffer.writeResolvedChars(&out_buffer, false); const result = out_buffer[0..written]; - try std.testing.expect(std.mem.indexOf(u8, result, "\"ว่\"") != null); + try std.testing.expect(std.mem.find(u8, result, "\"ว่\"") != null); } 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 97e74ce5da..847bd06a05 100644 --- a/packages/core/src/zig/tests/text-buffer-iterators_test.zig +++ b/packages/core/src/zig/tests/text-buffer-iterators_test.zig @@ -65,7 +65,7 @@ test "walkLines - single text segment" { } }; - var ctx: Context = .{ .lines = .{}, .allocator = allocator }; + var ctx: Context = .{ .lines = .empty, .allocator = allocator }; defer ctx.lines.deinit(allocator); iter_mod.walkLines(&rope, &ctx, Context.callback, true); @@ -112,7 +112,7 @@ test "walkLines - text + break + text" { } }; - var ctx: Context = .{ .lines = .{}, .allocator = allocator }; + var ctx: Context = .{ .lines = .empty, .allocator = allocator }; defer ctx.lines.deinit(allocator); iter_mod.walkLines(&rope, &ctx, Context.callback, true); @@ -166,7 +166,7 @@ test "walkLines - exclude newlines in offset" { } }; - var ctx: Context = .{ .lines = .{}, .allocator = allocator }; + var ctx: Context = .{ .lines = .empty, .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-selection_test.zig b/packages/core/src/zig/tests/text-buffer-selection_test.zig index b7dbc0626a..6fe485039f 100644 --- a/packages/core/src/zig/tests/text-buffer-selection_test.zig +++ b/packages/core/src/zig/tests/text-buffer-selection_test.zig @@ -104,8 +104,8 @@ test "Selection - with newline characters" { const len = view.getSelectedTextIntoBuffer(&out_buffer); const text = out_buffer[0..len]; - try std.testing.expect(std.mem.indexOf(u8, text, "ne 2") != null); - try std.testing.expect(std.mem.indexOf(u8, text, "\n") != null); + try std.testing.expect(std.mem.find(u8, text, "ne 2") != null); + try std.testing.expect(std.mem.find(u8, text, "\n") != null); } test "Selection - across empty lines" { @@ -201,9 +201,9 @@ test "Selection - including multiple line breaks" { const len = view.getSelectedTextIntoBuffer(&out_buffer); const text = out_buffer[0..len]; - try std.testing.expect(std.mem.indexOf(u8, text, "\n") != null); - try std.testing.expect(std.mem.indexOf(u8, text, "B") != null); - try std.testing.expect(std.mem.indexOf(u8, text, "C") != null); + try std.testing.expect(std.mem.find(u8, text, "\n") != null); + try std.testing.expect(std.mem.find(u8, text, "B") != null); + try std.testing.expect(std.mem.find(u8, text, "C") != null); } test "Selection - at line boundaries" { @@ -226,9 +226,9 @@ test "Selection - at line boundaries" { const len = view.getSelectedTextIntoBuffer(&out_buffer); const text = out_buffer[0..len]; - try std.testing.expect(std.mem.indexOf(u8, text, "1") != null); - try std.testing.expect(std.mem.indexOf(u8, text, "\n") != null); - try std.testing.expect(std.mem.indexOf(u8, text, "Li") != null); + try std.testing.expect(std.mem.find(u8, text, "1") != null); + try std.testing.expect(std.mem.find(u8, text, "\n") != null); + try std.testing.expect(std.mem.find(u8, text, "Li") != null); } test "Selection - empty text" { @@ -578,8 +578,8 @@ test "Selection - with graphemes" { const len = view.getSelectedTextIntoBuffer(&out_buffer); const text = out_buffer[0..len]; - try std.testing.expect(std.mem.indexOf(u8, text, "Hello") != null); - try std.testing.expect(std.mem.indexOf(u8, text, "🌍") != null); + try std.testing.expect(std.mem.find(u8, text, "Hello") != null); + try std.testing.expect(std.mem.find(u8, text, "🌍") != null); } test "Selection - wide emoji at boundary" { @@ -920,9 +920,9 @@ test "Selection - updateLocalSelection across multiple lines" { const len = view.getSelectedTextIntoBuffer(&out_buffer); const text = out_buffer[0..len]; - try std.testing.expect(std.mem.indexOf(u8, text, "ne 1") != null); - try std.testing.expect(std.mem.indexOf(u8, text, "\n") != null); - try std.testing.expect(std.mem.indexOf(u8, text, "Line") != null); + try std.testing.expect(std.mem.find(u8, text, "ne 1") != null); + try std.testing.expect(std.mem.find(u8, text, "\n") != null); + try std.testing.expect(std.mem.find(u8, text, "Line") != null); } test "Selection - updateLocalSelection backward selection" { @@ -1051,6 +1051,6 @@ test "Selection - updateLocalSelection preserves anchor correctly" { const text = out_buffer[0..len]; // Should include "e 2\nLine 3" since anchor is at col 3 of line 1 and focus at end of line 2 - try std.testing.expect(std.mem.indexOf(u8, text, "e 2") != null); - try std.testing.expect(std.mem.indexOf(u8, text, "\nLine 3") != null); + try std.testing.expect(std.mem.find(u8, text, "e 2") != null); + try std.testing.expect(std.mem.find(u8, text, "\nLine 3") != null); } diff --git a/packages/core/src/zig/tests/text-buffer-selection_viewport_test.zig b/packages/core/src/zig/tests/text-buffer-selection_viewport_test.zig index faafb3f348..c6993dc82e 100644 --- a/packages/core/src/zig/tests/text-buffer-selection_viewport_test.zig +++ b/packages/core/src/zig/tests/text-buffer-selection_viewport_test.zig @@ -33,10 +33,10 @@ test "Selection - vertical viewport selection without wrapping" { const len = view.getSelectedTextIntoBuffer(&buffer); const text = buffer[0..len]; - try std.testing.expect(std.mem.indexOf(u8, text, "Line 5") != null); - try std.testing.expect(std.mem.indexOf(u8, text, "Line 6") != null); - try std.testing.expect(std.mem.indexOf(u8, text, "Li") != null); - try std.testing.expect(std.mem.indexOf(u8, text, "Line 7") == null); + try std.testing.expect(std.mem.find(u8, text, "Line 5") != null); + try std.testing.expect(std.mem.find(u8, text, "Line 6") != null); + try std.testing.expect(std.mem.find(u8, text, "Li") != null); + try std.testing.expect(std.mem.find(u8, text, "Line 7") == null); } test "Selection - horizontal viewport selection without wrapping" { @@ -144,8 +144,8 @@ test "Selection - across empty line with viewport offset" { var buffer: [100]u8 = undefined; const len = view.getSelectedTextIntoBuffer(&buffer); const text = buffer[0..len]; - try std.testing.expect(std.mem.indexOf(u8, text, "Line2") != null); - try std.testing.expect(std.mem.indexOf(u8, text, "Lin") != null); + try std.testing.expect(std.mem.find(u8, text, "Line2") != null); + try std.testing.expect(std.mem.find(u8, text, "Lin") != null); } test "Selection - viewport offset with multi-line selection" { 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 afa6c86dee..77bedeee24 100644 --- a/packages/core/src/zig/tests/text-buffer-view_test.zig +++ b/packages/core/src/zig/tests/text-buffer-view_test.zig @@ -840,7 +840,7 @@ 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.ArrayListUnmanaged(Segment) = .{}; + var segments: std.ArrayListUnmanaged(Segment) = .empty; defer segments.deinit(std.testing.allocator); try segments.append(std.testing.allocator, .{ .linestart = {} }); @@ -1645,7 +1645,7 @@ test "TextBufferView line info - lines with different widths" { var view = try TextBufferView.init(std.testing.allocator, tb); defer view.deinit(); - var text_builder: std.ArrayListUnmanaged(u8) = .{}; + var text_builder: std.ArrayListUnmanaged(u8) = .empty; 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); @@ -1697,16 +1697,16 @@ test "TextBufferView line info - thousands of lines" { var view = try TextBufferView.init(std.testing.allocator, tb); defer view.deinit(); - var text_builder: std.ArrayListUnmanaged(u8) = .{}; - defer text_builder.deinit(std.testing.allocator); + var text_builder: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer text_builder.deinit(); var i: u32 = 0; while (i < 999) : (i += 1) { - try text_builder.writer(std.testing.allocator).print("Line {}\n", .{i}); + try text_builder.writer.print("Line {}\n", .{i}); } - try text_builder.writer(std.testing.allocator).print("Line {}", .{i}); + try text_builder.writer.print("Line {}", .{i}); - try tb.setText(text_builder.items); + try tb.setText(text_builder.written()); const line_count = view.getVirtualLineCount(); try std.testing.expectEqual(@as(u32, 1000), line_count); @@ -3147,16 +3147,16 @@ test "TextBufferView line info - line starts monotonically increasing" { var view = try TextBufferView.init(std.testing.allocator, tb); defer view.deinit(); - var text_builder: std.ArrayListUnmanaged(u8) = .{}; - defer text_builder.deinit(std.testing.allocator); + var text_builder: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer text_builder.deinit(); var i: u32 = 0; while (i < 99) : (i += 1) { - try text_builder.writer(std.testing.allocator).print("Line {}\n", .{i}); + try text_builder.writer.print("Line {}\n", .{i}); } - try text_builder.writer(std.testing.allocator).print("Line {}", .{i}); + try text_builder.writer.print("Line {}", .{i}); - try tb.setText(text_builder.items); + try tb.setText(text_builder.written()); const line_count = view.getVirtualLineCount(); try std.testing.expectEqual(@as(u32, 100), line_count); @@ -3587,7 +3587,7 @@ test "TextBufferView word wrapping - chunk at exact wrap boundary" { const Segment = seg_mod.Segment; - var segments: std.ArrayListUnmanaged(Segment) = .{}; + var segments: std.ArrayListUnmanaged(Segment) = .empty; defer segments.deinit(std.testing.allocator); try segments.append(std.testing.allocator, .{ .linestart = {} }); diff --git a/packages/core/src/zig/tests/text-buffer_test.zig b/packages/core/src/zig/tests/text-buffer_test.zig index 16714ce97e..afaf69ccac 100644 --- a/packages/core/src/zig/tests/text-buffer_test.zig +++ b/packages/core/src/zig/tests/text-buffer_test.zig @@ -252,7 +252,7 @@ test "TextBuffer line info - lines with different widths" { defer tb.deinit(); // Create text with different line lengths - var text_builder: std.ArrayListUnmanaged(u8) = .{}; + var text_builder: std.ArrayListUnmanaged(u8) = .empty; 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); @@ -355,7 +355,7 @@ test "TextBuffer line info - buffer resize operations" { defer tb.deinit(); // Add text that will cause multiple resizes - var text_builder: std.ArrayListUnmanaged(u8) = .{}; + var text_builder: std.ArrayListUnmanaged(u8) = .empty; defer text_builder.deinit(std.testing.allocator); try text_builder.appendNTimes(std.testing.allocator, 'A', 100); try text_builder.appendSlice(std.testing.allocator, "\n"); @@ -376,17 +376,17 @@ test "TextBuffer line info - thousands of lines" { defer tb.deinit(); // Create text with 1000 lines - var text_builder: std.ArrayListUnmanaged(u8) = .{}; - defer text_builder.deinit(std.testing.allocator); + var text_builder: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer text_builder.deinit(); var i: u32 = 0; while (i < 999) : (i += 1) { - try text_builder.writer(std.testing.allocator).print("Line {}\n", .{i}); + try text_builder.writer.print("Line {}\n", .{i}); } // Last line without newline - try text_builder.writer(std.testing.allocator).print("Line {}", .{i}); + try text_builder.writer.print("Line {}", .{i}); - try tb.setText(text_builder.items); + try tb.setText(text_builder.written()); try std.testing.expectEqual(@as(u32, 1000), tb.getLineCount()); try std.testing.expectEqual(@as(u32, 0), iter_mod.coordsToOffset(tb.rope(), 0, 0).?); @@ -583,7 +583,7 @@ test "TextBuffer line iteration - walkLines callback" { } }; - var ctx: Context = .{ .lines = .{}, .allocator = std.testing.allocator }; + var ctx: Context = .{ .lines = .empty, .allocator = std.testing.allocator }; defer ctx.lines.deinit(std.testing.allocator); iter_mod.walkLines(tb.rope(), &ctx, Context.callback, true); @@ -1449,7 +1449,7 @@ 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.ArrayListUnmanaged(u8) = .{}; + var text_builder: std.ArrayListUnmanaged(u8) = .empty; defer text_builder.deinit(std.testing.allocator); try text_builder.appendNTimes(std.testing.allocator, 'A', 100); @@ -1510,7 +1510,7 @@ test "TextBuffer setText - SIMD boundary conditions" { defer tb.deinit(); // Create text with newlines at SIMD vector boundaries (16 bytes) - var text_builder: std.ArrayListUnmanaged(u8) = .{}; + var text_builder: std.ArrayListUnmanaged(u8) = .empty; defer text_builder.deinit(std.testing.allocator); // 15 chars + \n = exactly 16 bytes @@ -1540,7 +1540,7 @@ 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.ArrayListUnmanaged(u8) = .{}; + var text_builder: std.ArrayListUnmanaged(u8) = .empty; defer text_builder.deinit(std.testing.allocator); // 15 chars + \r = 16 bytes, then \n at position 16 @@ -1963,7 +1963,7 @@ test "TextBuffer append - streaming/chunked append vs ground truth" { try tb.append(" end"); // Build expected ground truth - var expected: std.ArrayListUnmanaged(u8) = .{}; + var expected: std.ArrayListUnmanaged(u8) = .empty; defer expected.deinit(std.testing.allocator); try expected.appendSlice(std.testing.allocator, "First"); try expected.appendSlice(std.testing.allocator, "\nLine2"); diff --git a/packages/core/src/zig/tests/unicode-width-map.zon b/packages/core/src/zig/tests/unicode-width-map.zon index c15c1e7ba4..7e962e545a 100644 --- a/packages/core/src/zig/tests/unicode-width-map.zon +++ b/packages/core/src/zig/tests/unicode-width-map.zon @@ -3606,7 +3606,7 @@ .{ .codepoint = "U+1F6D5", .width = 2 }, .{ .codepoint = "U+1F6D6", .width = 2 }, .{ .codepoint = "U+1F6D7", .width = 2 }, - .{ .codepoint = "U+1F6D8", .width = 1 }, + .{ .codepoint = "U+1F6D8", .width = 2 }, .{ .codepoint = "U+1F6D9", .width = 1 }, .{ .codepoint = "U+1F6DA", .width = 1 }, .{ .codepoint = "U+1F6DB", .width = 1 }, 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 b3912fe4f4..3eea8fd820 100644 --- a/packages/core/src/zig/tests/utf8_no_zwj_test.zig +++ b/packages/core/src/zig/tests/utf8_no_zwj_test.zig @@ -91,9 +91,9 @@ test "no_zwj: mixed text with ZWJ emoji" { } test "no_zwj: findGraphemeInfo splits ZWJ sequences" { - var result_unicode: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result_unicode: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result_unicode.deinit(testing.allocator); - var result_no_zwj: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result_no_zwj: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result_no_zwj.deinit(testing.allocator); const text = "Hi👩‍🚀Bye"; diff --git a/packages/core/src/zig/tests/utf8_test.zig b/packages/core/src/zig/tests/utf8_test.zig index 590d2e54e9..03fd7d3fc9 100644 --- a/packages/core/src/zig/tests/utf8_test.zig +++ b/packages/core/src/zig/tests/utf8_test.zig @@ -2347,7 +2347,7 @@ test "calculateTextWidth: U+269B atom symbol should be width 2" { // ============================================================================ test "findGraphemeInfo: empty string" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); try utf8.findGraphemeInfo(testing.allocator, "", 4, false, .unicode, &result); @@ -2355,7 +2355,7 @@ test "findGraphemeInfo: empty string" { } test "findGraphemeInfo: ASCII-only returns empty" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); try utf8.findGraphemeInfo(testing.allocator, "hello world", 4, true, .unicode, &result); @@ -2363,7 +2363,7 @@ test "findGraphemeInfo: ASCII-only returns empty" { } test "findGraphemeInfo: ASCII with tab" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); try utf8.findGraphemeInfo(testing.allocator, "hello\tworld", 4, false, .unicode, &result); @@ -2377,7 +2377,7 @@ test "findGraphemeInfo: ASCII with tab" { } test "findGraphemeInfo: multiple tabs" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); try utf8.findGraphemeInfo(testing.allocator, "a\tb\tc", 4, false, .unicode, &result); @@ -2399,7 +2399,7 @@ test "findGraphemeInfo: multiple tabs" { } test "findGraphemeInfo: CJK characters" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); const text = "hello世界"; @@ -2422,7 +2422,7 @@ test "findGraphemeInfo: CJK characters" { } test "findGraphemeInfo: emoji with skin tone" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); const text = "Hi👋🏿Bye"; // Hi + wave + dark skin tone + Bye @@ -2438,7 +2438,7 @@ test "findGraphemeInfo: emoji with skin tone" { } test "findGraphemeInfo: emoji with ZWJ" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); const text = "a👩‍🚀b"; // a + woman astronaut + b @@ -2453,7 +2453,7 @@ test "findGraphemeInfo: emoji with ZWJ" { } test "findGraphemeInfo: combining mark" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); const text = "cafe\u{0301}"; // café with combining acute @@ -2469,7 +2469,7 @@ test "findGraphemeInfo: combining mark" { } test "findGraphemeInfo: flag emoji" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); const text = "US🇺🇸"; // US + flag @@ -2485,7 +2485,7 @@ test "findGraphemeInfo: flag emoji" { } test "findGraphemeInfo: mixed content" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); const text = "Hi\t世界!"; // Hi + tab + CJK + ! @@ -2514,7 +2514,7 @@ test "findGraphemeInfo: mixed content" { } test "findGraphemeInfo: only ASCII letters no cache" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); try utf8.findGraphemeInfo(testing.allocator, "abcdefghij", 4, false, .unicode, &result); @@ -2524,7 +2524,7 @@ test "findGraphemeInfo: only ASCII letters no cache" { } test "findGraphemeInfo: emoji with VS16" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); const text = "I ❤️ U"; // I + space + heart + VS16 + space + U @@ -2539,7 +2539,7 @@ test "findGraphemeInfo: emoji with VS16" { } test "findGraphemeInfo: realistic text" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); const text = "function test() {\n\tconst 世界 = 10;\n}"; @@ -2550,7 +2550,7 @@ test "findGraphemeInfo: realistic text" { } test "findGraphemeInfo: hiragana" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); const text = "こんにちは"; @@ -2566,7 +2566,7 @@ test "findGraphemeInfo: hiragana" { } test "findGraphemeInfo: at SIMD boundary" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); // Create text with multibyte char near SIMD boundary (16 bytes) @@ -3189,7 +3189,7 @@ test "calculateTextWidth: surrogate pair edge cases" { test "calculateTextWidth: long grapheme cluster chain" { // Create a base + many combining marks - var text: std.ArrayListUnmanaged(u8) = .{}; + var text: std.ArrayListUnmanaged(u8) = .empty; defer text.deinit(testing.allocator); try text.appendSlice(testing.allocator, "e"); @@ -3792,7 +3792,7 @@ test "calculateTextWidth: validate against unicode-width-map.zon" { width: i32, }; - const width_entries = std.zon.parse.fromSlice( + const width_entries = std.zon.parse.fromSliceAlloc( []const WidthEntry, allocator, zon_with_null, @@ -3858,7 +3858,7 @@ test "findGraphemeInfo: comprehensive multilingual text" { const expected_width = utf8.calculateTextWidth(text, 4, false, .unicode); - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); try utf8.findGraphemeInfo(testing.allocator, text, 4, false, .unicode, &result); @@ -3996,7 +3996,7 @@ test "Thai: wrap by width with tone marks" { test "Thai: grapheme info for combining marks" { const text = "กี่"; - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); try utf8.findGraphemeInfo(testing.allocator, text, 4, false, .unicode, &result); @@ -4008,7 +4008,7 @@ test "Thai: grapheme info for combining marks" { test "Thai: grapheme info for word with combining marks" { const text = "คือ"; - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); try utf8.findGraphemeInfo(testing.allocator, text, 4, false, .unicode, &result); @@ -4050,7 +4050,7 @@ test "Thai: ว่ wcwidth vs unicode mode comparison" { test "Thai: ว่ is a single grapheme cluster" { const text = "ว่"; - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); try utf8.findGraphemeInfo(testing.allocator, text, 4, false, .unicode, &result); diff --git a/packages/core/src/zig/tests/utf8_wcwidth_cursor_test.zig b/packages/core/src/zig/tests/utf8_wcwidth_cursor_test.zig index 11d6107fd8..a04e4dd426 100644 --- a/packages/core/src/zig/tests/utf8_wcwidth_cursor_test.zig +++ b/packages/core/src/zig/tests/utf8_wcwidth_cursor_test.zig @@ -200,7 +200,7 @@ test "wcwidth: mixed content with cursor movement" { } test "wcwidth: findGraphemeInfo with emoji" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); const text = "👋🏿"; // Wave + skin tone modifier @@ -214,7 +214,7 @@ test "wcwidth: findGraphemeInfo with emoji" { } test "wcwidth: findGraphemeInfo with ZWJ sequence" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); const text = "👩‍🚀"; // Woman + ZWJ + Rocket @@ -228,7 +228,7 @@ test "wcwidth: findGraphemeInfo with ZWJ sequence" { } test "wcwidth: findGraphemeInfo with combining marks" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); const text = "e\u{0301}"; // e + combining acute diff --git a/packages/core/src/zig/tests/utf8_wcwidth_test.zig b/packages/core/src/zig/tests/utf8_wcwidth_test.zig index f17aeffc30..004a7773a2 100644 --- a/packages/core/src/zig/tests/utf8_wcwidth_test.zig +++ b/packages/core/src/zig/tests/utf8_wcwidth_test.zig @@ -3,7 +3,7 @@ const testing = std.testing; const utf8 = @import("../utf8.zig"); test "findGraphemeInfo wcwidth: empty string" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); try utf8.findGraphemeInfo(testing.allocator, "", 4, false, .wcwidth, &result); @@ -11,7 +11,7 @@ test "findGraphemeInfo wcwidth: empty string" { } test "findGraphemeInfo wcwidth: ASCII-only returns empty" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); try utf8.findGraphemeInfo(testing.allocator, "hello world", 4, true, .wcwidth, &result); @@ -19,7 +19,7 @@ test "findGraphemeInfo wcwidth: ASCII-only returns empty" { } test "findGraphemeInfo wcwidth: ASCII with tab" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); try utf8.findGraphemeInfo(testing.allocator, "hello\tworld", 4, false, .wcwidth, &result); @@ -33,7 +33,7 @@ test "findGraphemeInfo wcwidth: ASCII with tab" { } test "findGraphemeInfo wcwidth: CJK characters" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); const text = "hello世界"; @@ -56,7 +56,7 @@ test "findGraphemeInfo wcwidth: CJK characters" { } test "findGraphemeInfo wcwidth: emoji with skin tone - single grapheme cluster" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); const text = "👋🏿"; // Wave + skin tone modifier @@ -70,7 +70,7 @@ test "findGraphemeInfo wcwidth: emoji with skin tone - single grapheme cluster" } test "findGraphemeInfo wcwidth: emoji with ZWJ - single grapheme cluster" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); const text = "👩‍🚀"; // Woman + ZWJ + Rocket (11 bytes total) @@ -83,7 +83,7 @@ test "findGraphemeInfo wcwidth: emoji with ZWJ - single grapheme cluster" { } test "findGraphemeInfo wcwidth: combining mark - part of base grapheme" { - var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result.deinit(testing.allocator); const text = "e\u{0301}test"; // e + combining acute accent + test @@ -96,9 +96,9 @@ test "findGraphemeInfo wcwidth: combining mark - part of base grapheme" { } test "findGraphemeInfo wcwidth vs unicode: emoji with skin tone" { - var result_wcwidth: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result_wcwidth: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result_wcwidth.deinit(testing.allocator); - var result_unicode: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result_unicode: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result_unicode.deinit(testing.allocator); const text = "Hi👋🏿Bye"; @@ -120,9 +120,9 @@ test "findGraphemeInfo wcwidth vs unicode: emoji with skin tone" { } test "findGraphemeInfo wcwidth vs unicode: flag emoji" { - var result_wcwidth: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result_wcwidth: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result_wcwidth.deinit(testing.allocator); - var result_unicode: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; + var result_unicode: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .empty; defer result_unicode.deinit(testing.allocator); const text = "🇺🇸"; // US flag (two regional indicators) diff --git a/packages/core/src/zig/tests/wrap-cache-perf_test.zig b/packages/core/src/zig/tests/wrap-cache-perf_test.zig index 3f57b6a22f..41a1150f1e 100644 --- a/packages/core/src/zig/tests/wrap-cache-perf_test.zig +++ b/packages/core/src/zig/tests/wrap-cache-perf_test.zig @@ -42,9 +42,9 @@ test "word wrap complexity - width changes are O(n)" { _ = view.getVirtualLineCount(); view.setWrapWidth(width); - var timer = std.time.Timer.start() catch unreachable; + const start = std.Io.Clock.awake.now(std.testing.io); _ = view.getVirtualLineCount(); - iter_times[iter] = timer.read(); + iter_times[iter] = @intCast(start.untilNow(std.testing.io, .awake).toNanoseconds()); } // Sort and take median diff --git a/packages/core/src/zig/text-buffer-segment.zig b/packages/core/src/zig/text-buffer-segment.zig index 220edfc177..2c129c3c87 100644 --- a/packages/core/src/zig/text-buffer-segment.zig +++ b/packages/core/src/zig/text-buffer-segment.zig @@ -92,7 +92,7 @@ pub const TextChunk = struct { const chunk_bytes = self.getBytes(mem_registry); - var grapheme_list: std.ArrayListUnmanaged(GraphemeInfo) = .{}; + var grapheme_list: std.ArrayListUnmanaged(GraphemeInfo) = .empty; errdefer grapheme_list.deinit(allocator); try utf8.findGraphemeInfo(allocator, chunk_bytes, tabwidth, self.isAsciiOnly(), width_method, &grapheme_list); diff --git a/packages/core/src/zig/text-buffer-view.zig b/packages/core/src/zig/text-buffer-view.zig index af5b365a8d..2899c0c839 100644 --- a/packages/core/src/zig/text-buffer-view.zig +++ b/packages/core/src/zig/text-buffer-view.zig @@ -95,7 +95,7 @@ pub const VirtualLine = struct { pub fn init() VirtualLine { return .{ - .chunks = .{}, + .chunks = .empty, .width_cols = 0, .col_offset = 0, .source_line = 0, @@ -192,14 +192,14 @@ pub const UnifiedTextBufferView = struct { .wrap_width = null, .wrap_mode = .none, .first_line_offset = 0, - .virtual_lines = .{}, + .virtual_lines = .empty, .virtual_lines_dirty = true, - .cached_line_starts = .{}, - .cached_line_widths = .{}, - .cached_line_sources = .{}, - .cached_line_wrap_indices = .{}, - .cached_line_first_vline = .{}, - .cached_line_vline_counts = .{}, + .cached_line_starts = .empty, + .cached_line_widths = .empty, + .cached_line_sources = .empty, + .cached_line_wrap_indices = .empty, + .cached_line_first_vline = .empty, + .cached_line_vline_counts = .empty, .global_allocator = global_allocator, .virtual_lines_arena = virtual_lines_internal_arena, .measure_arena = std.heap.ArenaAllocator.init(global_allocator), @@ -346,13 +346,13 @@ pub const UnifiedTextBufferView = struct { if (!self.virtual_lines_dirty and !buffer_dirty) return; _ = self.virtual_lines_arena.reset(.free_all); - self.virtual_lines = .{}; - self.cached_line_starts = .{}; - self.cached_line_widths = .{}; - self.cached_line_sources = .{}; - self.cached_line_wrap_indices = .{}; - self.cached_line_first_vline = .{}; - self.cached_line_vline_counts = .{}; + self.virtual_lines = .empty; + self.cached_line_starts = .empty; + self.cached_line_widths = .empty; + self.cached_line_sources = .empty; + self.cached_line_wrap_indices = .empty; + self.cached_line_first_vline = .empty; + self.cached_line_vline_counts = .empty; self.truncation_applied = false; const virtual_allocator = self.virtual_lines_arena.allocator(); @@ -852,7 +852,7 @@ pub const UnifiedTextBufferView = struct { const prefix_width = available_width / 2; const suffix_width = available_width - prefix_width; - var new_chunks: std.ArrayListUnmanaged(VirtualChunk) = .{}; + var new_chunks: std.ArrayListUnmanaged(VirtualChunk) = .empty; var prefix_accumulated: u32 = 0; for (vline.chunks.items) |chunk| { @@ -958,13 +958,13 @@ pub const UnifiedTextBufferView = struct { const measure_allocator = self.measure_arena.allocator(); // Create temporary output structures - var temp_virtual_lines: std.ArrayListUnmanaged(VirtualLine) = .{}; - var temp_line_starts: std.ArrayListUnmanaged(u32) = .{}; - var temp_line_widths: std.ArrayListUnmanaged(u32) = .{}; - var temp_line_sources: std.ArrayListUnmanaged(u32) = .{}; - var temp_line_wrap_indices: std.ArrayListUnmanaged(u32) = .{}; - var temp_line_first_vline: std.ArrayListUnmanaged(u32) = .{}; - var temp_line_vline_counts: std.ArrayListUnmanaged(u32) = .{}; + var temp_virtual_lines: std.ArrayListUnmanaged(VirtualLine) = .empty; + var temp_line_starts: std.ArrayListUnmanaged(u32) = .empty; + var temp_line_widths: std.ArrayListUnmanaged(u32) = .empty; + var temp_line_sources: std.ArrayListUnmanaged(u32) = .empty; + var temp_line_wrap_indices: std.ArrayListUnmanaged(u32) = .empty; + var temp_line_first_vline: std.ArrayListUnmanaged(u32) = .empty; + var temp_line_vline_counts: std.ArrayListUnmanaged(u32) = .empty; const output: VirtualLineOutput = .{ .virtual_lines = &temp_virtual_lines, diff --git a/packages/core/src/zig/text-buffer.zig b/packages/core/src/zig/text-buffer.zig index cef075d554..19687ed276 100644 --- a/packages/core/src/zig/text-buffer.zig +++ b/packages/core/src/zig/text-buffer.zig @@ -1,4 +1,6 @@ const std = @import("std"); +const builtin = @import("builtin"); +const io = if (builtin.is_test) std.testing.io else @import("root").io; const Allocator = std.mem.Allocator; const seg_mod = @import("text-buffer-segment.zig"); const iter_mod = @import("text-buffer-iterators.zig"); @@ -187,10 +189,10 @@ pub const UnifiedTextBuffer = struct { const init_rope = UnifiedRope.init(internal_allocator) catch return TextBufferError.OutOfMemory; - var view_dirty_flags: std.ArrayListUnmanaged(bool) = .{}; + var view_dirty_flags: std.ArrayListUnmanaged(bool) = .empty; errdefer view_dirty_flags.deinit(global_allocator); - var free_view_ids: std.ArrayListUnmanaged(u32) = .{}; + var free_view_ids: std.ArrayListUnmanaged(u32) = .empty; errdefer free_view_ids.deinit(global_allocator); var mem_registry = MemRegistry.init(global_allocator); @@ -217,8 +219,8 @@ pub const UnifiedTextBuffer = struct { .next_view_id = 0, .free_view_ids = free_view_ids, .content_epoch = 0, - .line_highlights = .{}, - .line_spans = .{}, + .line_highlights = .empty, + .line_spans = .empty, .internal_highlight_count = 0, .highlight_batch_depth = 0, .dirty_span_lines = dirty_span_lines, @@ -549,7 +551,7 @@ pub const UnifiedTextBuffer = struct { defer break_result.deinit(); try utf8.findLineBreaks(text, &break_result); - var segments: std.ArrayListUnmanaged(Segment) = .{}; + var segments: std.ArrayListUnmanaged(Segment) = .empty; errdefer segments.deinit(allocator); if (prepend_linestart) { @@ -713,10 +715,10 @@ pub const UnifiedTextBuffer = struct { // Highlight system fn ensureLineHighlightStorage(self: *Self, line_idx: usize) TextBufferError!void { while (self.line_highlights.items.len <= line_idx) { - try self.line_highlights.append(self.global_allocator, .{}); + try self.line_highlights.append(self.global_allocator, .empty); } while (self.line_spans.items.len <= line_idx) { - try self.line_spans.append(self.global_allocator, .{}); + try self.line_spans.append(self.global_allocator, .empty); } } @@ -808,7 +810,7 @@ pub const UnifiedTextBuffer = struct { hl_idx: usize, }; - var events: std.ArrayListUnmanaged(Event) = .{}; + var events: std.ArrayListUnmanaged(Event) = .empty; defer events.deinit(self.global_allocator); for (highlights, 0..) |hl, idx| { @@ -1136,7 +1138,7 @@ pub const UnifiedTextBuffer = struct { try self.setTextInternal(self.styled_text_mem_id.?, full_text); if (self.syntax_style) |style| { - var seen_link_ids: std.AutoHashMapUnmanaged(u32, void) = .{}; + var seen_link_ids: std.AutoHashMapUnmanaged(u32, void) = .empty; defer seen_link_ids.deinit(self.global_allocator); self.startHighlightsTransaction(); @@ -1187,21 +1189,24 @@ pub const UnifiedTextBuffer = struct { /// Load text from a file path (relative to cwd) /// The file content is allocated in the arena and will be freed when the buffer is destroyed pub fn loadFile(self: *Self, path: []const u8) TextBufferError!void { - const file = std.fs.cwd().openFile(path, .{}) catch |err| { + const file = std.Io.Dir.cwd().openFile(io, path, .{}) catch |err| { return switch (err) { error.FileNotFound => TextBufferError.InvalidIndex, error.AccessDenied => TextBufferError.InvalidIndex, else => TextBufferError.OutOfMemory, }; }; - defer file.close(); + defer file.close(io); - const file_size = file.getEndPos() catch return TextBufferError.OutOfMemory; + const stat = file.stat(io) catch return TextBufferError.OutOfMemory; + const file_size = std.math.cast(usize, stat.size) orelse return TextBufferError.OutOfMemory; self.clear(); const content = self.allocator.alloc(u8, file_size) catch return TextBufferError.OutOfMemory; - const bytes_read = file.readAll(content) catch return TextBufferError.OutOfMemory; + var read_buffer: [4096]u8 = undefined; + var reader = file.reader(io, &read_buffer); + const bytes_read = reader.interface.readSliceShort(content) catch return TextBufferError.OutOfMemory; const text = content[0..bytes_read]; const mem_id = try self.mem_registry.register(text, false); diff --git a/packages/core/src/zig/utf8.zig b/packages/core/src/zig/utf8.zig index 0346cc226e..1644e64e74 100644 --- a/packages/core/src/zig/utf8.zig +++ b/packages/core/src/zig/utf8.zig @@ -65,7 +65,7 @@ pub const LineBreakResult = struct { pub fn init(allocator: std.mem.Allocator) LineBreakResult { return .{ - .breaks = .{}, + .breaks = .empty, .allocator = allocator, }; } @@ -86,7 +86,7 @@ pub const TabStopResult = struct { pub fn init(allocator: std.mem.Allocator) TabStopResult { return .{ - .positions = .{}, + .positions = .empty, .allocator = allocator, }; } @@ -118,7 +118,7 @@ pub const WrapBreakResult = struct { pub fn init(allocator: std.mem.Allocator) WrapBreakResult { return .{ - .breaks = .{}, + .breaks = .empty, .allocator = allocator, }; } @@ -1641,15 +1641,17 @@ pub const GraphemeInfo = struct { pub const GraphemeInfoResult = struct { graphemes: std.ArrayList(GraphemeInfo), + allocator: std.mem.Allocator, pub fn init(allocator: std.mem.Allocator) GraphemeInfoResult { return .{ - .graphemes = std.ArrayList(GraphemeInfo).init(allocator), + .graphemes = .empty, + .allocator = allocator, }; } pub fn deinit(self: *GraphemeInfoResult) void { - self.graphemes.deinit(); + self.graphemes.deinit(self.allocator); self.* = undefined; } diff --git a/packages/core/src/zig/yoga.zig b/packages/core/src/zig/yoga.zig index 36383a9d6b..6e135cbb01 100644 --- a/packages/core/src/zig/yoga.zig +++ b/packages/core/src/zig/yoga.zig @@ -1,8 +1,7 @@ const std = @import("std"); - -const c = @cImport({ - @cInclude("yoga/Yoga.h"); -}); +const builtin = @import("builtin"); +const c = @import("yoga"); +const io = if (builtin.is_test) std.testing.io else @import("root").io; pub const YGNodeRef = c.YGNodeRef; pub const YGNodeConstRef = c.YGNodeConstRef; @@ -100,7 +99,7 @@ const JsMeasureCallback = *const fn (?*anyopaque, f32, u32, f32, u32) callconv(. const JsDirtiedCallback = *const fn (?*anyopaque) callconv(.c) void; pub const NativeMeasureCallback = *const fn (?*anyopaque, f32, u32, f32, u32) callconv(.c) ExternalYogaSize; var opentui_config: YGConfigRef = null; -var opentui_config_mutex: std.Thread.Mutex = .{}; +var opentui_config_mutex: std.Io.Mutex = .init; var global_measure_callback: ?*const anyopaque = null; var global_dirtied_callback: ?*const anyopaque = null; @@ -173,8 +172,8 @@ fn undefinedValue() c.YGValue { } fn getOpenTUIConfig() YGConfigRef { - opentui_config_mutex.lock(); - defer opentui_config_mutex.unlock(); + opentui_config_mutex.lockUncancelable(io); + defer opentui_config_mutex.unlock(io); if (opentui_config == null) { const config = c.YGConfigNew(); From 466771b1ac25d55ea9bf2347ec93268792c10937 Mon Sep 17 00:00:00 2001 From: Simon Klee Date: Wed, 29 Jul 2026 16:17:52 +0200 Subject: [PATCH 2/4] zig: migrate audio capture mutex calls --- packages/core/src/zig/audio.zig | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/core/src/zig/audio.zig b/packages/core/src/zig/audio.zig index 9fd85f7a44..fc4ff56815 100644 --- a/packages/core/src/zig/audio.zig +++ b/packages/core/src/zig/audio.zig @@ -1334,15 +1334,15 @@ pub fn clearPlaybackDeviceSelection(engine: *Engine) void { pub fn refreshCaptureDevices(engine: *Engine) i32 { const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); return refreshCaptureDevicesLocked(e); } pub fn getCaptureDeviceCount(engine: *Engine) u32 { const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); return @intCast(e.capture_devices.items.len); } @@ -1350,8 +1350,8 @@ pub fn getCaptureDeviceName(engine: *Engine, index: u32, out_ptr: ?[*]u8, max_le if (out_ptr == null) return 0; const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); const idx: usize = @intCast(index); if (idx >= e.capture_devices.items.len) return 0; @@ -1360,8 +1360,8 @@ pub fn getCaptureDeviceName(engine: *Engine, index: u32, out_ptr: ?[*]u8, max_le pub fn isCaptureDeviceDefault(engine: *Engine, index: u32) bool { const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); const idx: usize = @intCast(index); if (idx >= e.capture_devices.items.len) return false; @@ -1370,8 +1370,8 @@ pub fn isCaptureDeviceDefault(engine: *Engine, index: u32) bool { pub fn selectCaptureDevice(engine: *Engine, index: u32) i32 { const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); if (e.has_capture_device) return Status.err_invalid; if (e.capture_devices.items.len == 0) { @@ -1390,8 +1390,8 @@ pub fn selectCaptureDevice(engine: *Engine, index: u32) i32 { pub fn clearCaptureDeviceSelection(engine: *Engine) void { const e = engine; - e.lock.lock(); - defer e.lock.unlock(); + e.lock.lockUncancelable(io); + defer e.lock.unlock(io); e.selected_capture_index = null; e.has_selected_capture_device = false; } From e1f88e674c49a769d853b60dc000bd44f8254460 Mon Sep 17 00:00:00 2001 From: Simon Klee Date: Tue, 4 Aug 2026 23:07:10 +0200 Subject: [PATCH 3/4] fix(core): migrate new main code to Zig 0.16 --- packages/core/src/zig/bench.zig | 13 +- .../zig/bench/buffer-cell-drawing_bench.zig | 16 +- .../zig/bench/buffer-color-blending_bench.zig | 5 +- .../zig/bench/buffer-image-overlap_bench.zig | 9 +- .../src/zig/bench/image-operations_bench.zig | 33 ++- .../src/zig/bench/renderer-image_bench.zig | 40 +-- .../src/zig/bench/renderer-output_bench.zig | 10 +- .../src/zig/bench/renderer-overhead_bench.zig | 10 +- .../src/zig/bench/terminal-image_bench.zig | 208 +++++++------- packages/core/src/zig/buffer.zig | 2 +- packages/core/src/zig/build.zig | 6 +- packages/core/src/zig/image.zig | 34 +-- packages/core/src/zig/lib.zig | 8 +- packages/core/src/zig/renderer-output.zig | 2 +- packages/core/src/zig/renderer.zig | 43 +-- packages/core/src/zig/terminal-image.zig | 8 +- packages/core/src/zig/terminal.zig | 10 +- packages/core/src/zig/tests/buffer_test.zig | 2 +- packages/core/src/zig/tests/image_test.zig | 16 +- packages/core/src/zig/tests/renderer_test.zig | 208 +++++++------- .../src/zig/tests/terminal-image_test.zig | 263 +++++++++--------- packages/core/src/zig/tests/terminal_test.zig | 10 +- packages/core/src/zig/tests/test-renderer.zig | 6 +- 23 files changed, 494 insertions(+), 468 deletions(-) diff --git a/packages/core/src/zig/bench.zig b/packages/core/src/zig/bench.zig index b865b5e273..a91619e2d9 100644 --- a/packages/core/src/zig/bench.zig +++ b/packages/core/src/zig/bench.zig @@ -43,6 +43,9 @@ const std = @import("std"); const bench_utils = @import("bench-utils.zig"); const gp = @import("grapheme.zig"); +var io_threaded: std.Io.Threaded = .init_single_threaded; +pub const io = io_threaded.io(); + // Import all benchmark modules const text_buffer_view_bench = @import("bench/text-buffer-view_bench.zig"); const edit_buffer_bench = @import("bench/edit-buffer_bench.zig"); @@ -92,7 +95,7 @@ fn matchesFilter(bench_name: []const u8, filter: ?[]const u8) bool { } pub fn main(init: std.process.Init) !void { - const io = init.io; + const process_io = init.io; var gpa: std.heap.DebugAllocator(.{}) = .init; defer _ = gpa.deinit(); const allocator = gpa.allocator(); @@ -151,7 +154,7 @@ pub fn main(init: std.process.Init) !void { } } else if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) { var stdout_buffer: [4096]u8 = undefined; - var stdout_writer = std.Io.File.stdout().writerStreaming(io, &stdout_buffer); + var stdout_writer = std.Io.File.stdout().writerStreaming(process_io, &stdout_buffer); const stdout = &stdout_writer.interface; try stdout.print("Usage: bench [options]\n\n", .{}); try stdout.print("Options:\n", .{}); @@ -170,7 +173,7 @@ pub fn main(init: std.process.Init) !void { } var stdout_buffer: [4096]u8 = undefined; - var stdout_writer = std.Io.File.stdout().writerStreaming(io, &stdout_buffer); + var stdout_writer = std.Io.File.stdout().writerStreaming(process_io, &stdout_buffer); const stdout = &stdout_writer.interface; if (!json_output and filter != null) { @@ -189,8 +192,8 @@ pub fn main(init: std.process.Init) !void { var results_arena = std.heap.ArenaAllocator.init(allocator); defer results_arena.deinit(); - const timer = bench_utils.BenchTimer.start(io); - const results = try bench.run(io, results_arena.allocator(), show_mem, bench_filter); + const timer = bench_utils.BenchTimer.start(process_io); + const results = try bench.run(process_io, results_arena.allocator(), show_mem, bench_filter); const elapsed_ns = timer.read(); if (results.len == 0) continue; diff --git a/packages/core/src/zig/bench/buffer-cell-drawing_bench.zig b/packages/core/src/zig/bench/buffer-cell-drawing_bench.zig index 85b556df91..253a1a17e7 100644 --- a/packages/core/src/zig/bench/buffer-cell-drawing_bench.zig +++ b/packages/core/src/zig/bench/buffer-cell-drawing_bench.zig @@ -91,7 +91,7 @@ fn runWorkload(target: *buffer.OptimizedBuffer, scenario: Scenario, text: []cons } } -fn runScenario(allocator: std.mem.Allocator, pool: *gp.GraphemePool, scenario: Scenario) !bench_utils.BenchStats { +fn runScenario(io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, scenario: Scenario) !bench_utils.BenchStats { var link_pool = link.LinkPool.init(allocator); defer link_pool.deinit(); const target = try buffer.OptimizedBuffer.init(allocator, WIDTH, HEIGHT, .{ .pool = pool, .link_pool = &link_pool }); @@ -103,7 +103,7 @@ fn runScenario(allocator: std.mem.Allocator, pool: *gp.GraphemePool, scenario: S var elapsed: u64 = 0; for (0..BATCH_SIZE) |_| { target.clear(ansi.rgbColor(0, 0, 0, 255), null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); try runWorkload(target, scenario, text); elapsed += timer.read(); } @@ -112,7 +112,7 @@ fn runScenario(allocator: std.mem.Allocator, pool: *gp.GraphemePool, scenario: S return stats; } -fn runFrameBufferScenario(allocator: std.mem.Allocator, pool: *gp.GraphemePool) !bench_utils.BenchStats { +fn runFrameBufferScenario(io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool) !bench_utils.BenchStats { var link_pool = link.LinkPool.init(allocator); defer link_pool.deinit(); const source = try buffer.OptimizedBuffer.init(allocator, WIDTH, HEIGHT, .{ .pool = pool, .link_pool = &link_pool }); @@ -134,7 +134,7 @@ fn runFrameBufferScenario(allocator: std.mem.Allocator, pool: *gp.GraphemePool) for (0..WARMUP_SAMPLES + SAMPLES) |sample| { var elapsed: u64 = 0; for (0..BATCH_SIZE) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); target.drawFrameBuffer(0, 0, source, null, null, null, null); elapsed += timer.read(); target.clear(ansi.rgbColor(0, 0, 0, 255), null); @@ -144,7 +144,7 @@ fn runFrameBufferScenario(allocator: std.mem.Allocator, pool: *gp.GraphemePool) return stats; } -pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8) ![]bench_utils.BenchResult { +pub fn run(io: std.Io, allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8) ![]bench_utils.BenchResult { _ = show_mem; const pool = gp.initGlobalPool(allocator); defer gp.deinitGlobalPool(); @@ -161,10 +161,10 @@ pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const .{ .name = "1k translucent filled boxes no images", .kind = .translucent_boxes }, .{ .name = "1k half-clipped filled boxes no images", .kind = .half_clipped_boxes }, }; - var results: std.ArrayListUnmanaged(bench_utils.BenchResult) = .{}; + var results: std.ArrayListUnmanaged(bench_utils.BenchResult) = .empty; for (scenarios) |scenario| { if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; - const stats = try runScenario(allocator, pool, scenario.kind); + const stats = try runScenario(io, allocator, pool, scenario.kind); try results.append(allocator, .{ .name = scenario.name, .min_ns = stats.min_ns, @@ -179,7 +179,7 @@ pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const } const framebuffer_name = "drawFrameBuffer 10k cells no images"; if (bench_utils.matchesBenchFilter(framebuffer_name, bench_filter)) { - const stats = try runFrameBufferScenario(allocator, pool); + const stats = try runFrameBufferScenario(io, allocator, pool); try results.append(allocator, .{ .name = framebuffer_name, .min_ns = stats.min_ns, diff --git a/packages/core/src/zig/bench/buffer-color-blending_bench.zig b/packages/core/src/zig/bench/buffer-color-blending_bench.zig index 19376f059a..8649243e29 100644 --- a/packages/core/src/zig/bench/buffer-color-blending_bench.zig +++ b/packages/core/src/zig/bench/buffer-color-blending_bench.zig @@ -332,6 +332,7 @@ fn runTranslucentTextBuffers( } fn runTranslucentFillOverImageMarkers( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, iterations: usize, @@ -351,7 +352,7 @@ fn runTranslucentFillOverImageMarkers( buf.clear(CLEAR_BG, null); _ = try buf.drawImage(source, 1, 0, 0, BUFFER_WIDTH, BUFFER_HEIGHT, 0, 0, 0, 0, 1, 1, .auto); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); buf.fillRect(0, 0, BUFFER_WIDTH, BUFFER_HEIGHT, overlay); stats.record(timer.read()); } @@ -390,7 +391,7 @@ pub fn run( const text_buffers_results = try runTranslucentTextBuffers(io, allocator, pool, show_mem, iterations, bench_filter); try all_results.appendSlice(allocator, text_buffers_results); - const image_marker_results = try runTranslucentFillOverImageMarkers(allocator, pool, iterations, bench_filter); + const image_marker_results = try runTranslucentFillOverImageMarkers(io, allocator, pool, iterations, bench_filter); try all_results.appendSlice(allocator, image_marker_results); return all_results.toOwnedSlice(allocator); diff --git a/packages/core/src/zig/bench/buffer-image-overlap_bench.zig b/packages/core/src/zig/bench/buffer-image-overlap_bench.zig index a873dc7bef..921d7f76f7 100644 --- a/packages/core/src/zig/bench/buffer-image-overlap_bench.zig +++ b/packages/core/src/zig/bench/buffer-image-overlap_bench.zig @@ -121,6 +121,7 @@ fn runWorkload(target: *buffer.OptimizedBuffer, scenario: Scenario) !void { } fn runScenario( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, source: *image.Image, @@ -135,7 +136,7 @@ fn runScenario( for (0..WARMUP_SAMPLES + SAMPLES) |sample| { target.clear(ansi.rgbColor(0, 0, 0, 255), null); try addPlacements(target, source, scenario); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); try runWorkload(target, scenario); const elapsed = timer.read(); if (sample >= WARMUP_SAMPLES) stats.record(elapsed); @@ -143,7 +144,7 @@ fn runScenario( return stats; } -pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8) ![]bench_utils.BenchResult { +pub fn run(io: std.Io, allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8) ![]bench_utils.BenchResult { _ = show_mem; const pool = gp.initGlobalPool(allocator); defer gp.deinitGlobalPool(); @@ -161,10 +162,10 @@ pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const .{ .name = "1k transparent full fills with sparse placement", .kind = .transparent_fill_sparse }, .{ .name = "100 transparent full fills with dense placement", .kind = .transparent_fill_dense }, }; - var results: std.ArrayListUnmanaged(bench_utils.BenchResult) = .{}; + var results: std.ArrayListUnmanaged(bench_utils.BenchResult) = .empty; for (scenarios) |scenario| { if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; - const stats = try runScenario(allocator, pool, source, scenario.kind); + const stats = try runScenario(io, allocator, pool, source, scenario.kind); try results.append(allocator, .{ .name = scenario.name, .min_ns = stats.min_ns, diff --git a/packages/core/src/zig/bench/image-operations_bench.zig b/packages/core/src/zig/bench/image-operations_bench.zig index 527c334078..d59f53bdbf 100644 --- a/packages/core/src/zig/bench/image-operations_bench.zig +++ b/packages/core/src/zig/bench/image-operations_bench.zig @@ -37,6 +37,7 @@ fn appendResult( } fn runEncodedScenarios( + io: std.Io, allocator: std.mem.Allocator, work_allocator: std.mem.Allocator, results: *std.ArrayListUnmanaged(bench_utils.BenchResult), @@ -52,7 +53,7 @@ fn runEncodedScenarios( const run_decode = bench_utils.matchesBenchFilter(decode_name, bench_filter); if (!run_probe and !run_inspect and !run_decode) continue; - const encoded = try std.fs.cwd().readFileAlloc(work_allocator, scenario.path, 2 * 1024 * 1024); + const encoded = try std.Io.Dir.cwd().readFileAlloc(io, scenario.path, work_allocator, .limited(2 * 1024 * 1024)); defer work_allocator.free(encoded); if (run_probe) { @@ -69,7 +70,7 @@ fn runEncodedScenarios( var stats: bench_utils.BenchStats = .{}; var checksum: u64 = 0; for (0..30) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..batch_size) |_| { const status = image.probe(encoded, .{}, &info); if (status != .ok) return error.ImageProbeFailed; @@ -87,7 +88,7 @@ fn runEncodedScenarios( var stats: bench_utils.BenchStats = .{}; var checksum: u64 = 0; for (0..20) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); const status = image.inspect(work_allocator, encoded, .{}, &info); stats.record(timer.read()); if (status != .ok) return error.ImageInspectFailed; @@ -105,7 +106,7 @@ fn runEncodedScenarios( var stats: bench_utils.BenchStats = .{}; var checksum: u64 = 0; for (0..20) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); const decoded = try image.decode(work_allocator, encoded, .{}); stats.record(timer.read()); checksum +%= decoded.width() + decoded.height() + decoded.pixels.len; @@ -126,6 +127,7 @@ fn runEncodedScenarios( } fn runIccReuseScenarios( + io: std.Io, allocator: std.mem.Allocator, work_allocator: std.mem.Allocator, results: *std.ArrayListUnmanaged(bench_utils.BenchResult), @@ -156,7 +158,7 @@ fn runIccReuseScenarios( var stats: bench_utils.BenchStats = .{}; var checksum: u64 = 0; for (0..30) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); const decoded = try image.decode(work_allocator, png, .{}); if (scenario == 1) _ = try decoded.ensurePixels(); stats.record(timer.read()); @@ -181,6 +183,7 @@ fn makeImage(allocator: std.mem.Allocator, width: u32, height: u32, seed: u8) !* } fn runTransformScenarios( + io: std.Io, allocator: std.mem.Allocator, work_allocator: std.mem.Allocator, results: *std.ArrayListUnmanaged(bench_utils.BenchResult), @@ -210,7 +213,7 @@ fn runTransformScenarios( var stats: bench_utils.BenchStats = .{}; var checksum: u64 = 0; for (0..20) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); const output = try image.transform(work_allocator, base, .rotate_90); stats.record(timer.read()); checksum +%= output.width() + output.height() + output.pixels[0]; @@ -228,7 +231,7 @@ fn runTransformScenarios( var stats: bench_utils.BenchStats = .{}; var checksum: u64 = 0; for (0..20) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); const output = try image.extend(work_allocator, base, 16, 16, 16, 16, .{ 8, 16, 24, 128 }); stats.record(timer.read()); checksum +%= output.width() + output.height() + output.pixels[3]; @@ -250,7 +253,7 @@ fn runTransformScenarios( var stats: bench_utils.BenchStats = .{}; var checksum: u64 = 0; for (0..50) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); for (0..batch_size) |_| { const status = image.copyPixels(base, destination, base.width() * 4, scenario.bgra); if (status != .ok) return error.ImageCopyFailed; @@ -270,7 +273,7 @@ fn runTransformScenarios( var stats: bench_utils.BenchStats = .{}; var checksum: u64 = 0; for (0..20) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); const output = try image.composite(work_allocator, base, overlay, 0, 0, .source_over, 192); stats.record(timer.read()); checksum +%= output.pixels[0] + output.pixels[output.pixels.len - 1]; @@ -281,14 +284,14 @@ fn runTransformScenarios( } } -pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8) ![]bench_utils.BenchResult { - var results: std.ArrayListUnmanaged(bench_utils.BenchResult) = .{}; - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; +pub fn run(io: std.Io, allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8) ![]bench_utils.BenchResult { + var results: std.ArrayListUnmanaged(bench_utils.BenchResult) = .empty; + var gpa: std.heap.DebugAllocator(.{}) = .init; defer _ = gpa.deinit(); const work_allocator = gpa.allocator(); - try runEncodedScenarios(allocator, work_allocator, &results, show_mem, bench_filter); - try runIccReuseScenarios(allocator, work_allocator, &results, bench_filter); - try runTransformScenarios(allocator, work_allocator, &results, bench_filter); + try runEncodedScenarios(io, allocator, work_allocator, &results, show_mem, bench_filter); + try runIccReuseScenarios(io, allocator, work_allocator, &results, bench_filter); + try runTransformScenarios(io, allocator, work_allocator, &results, bench_filter); return results.toOwnedSlice(allocator); } diff --git a/packages/core/src/zig/bench/renderer-image_bench.zig b/packages/core/src/zig/bench/renderer-image_bench.zig index 0a57644413..9d19cd945b 100644 --- a/packages/core/src/zig/bench/renderer-image_bench.zig +++ b/packages/core/src/zig/bench/renderer-image_bench.zig @@ -59,6 +59,7 @@ const FrameCost = struct { }; fn runPlacementScenario( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, protocol: Protocol, @@ -114,7 +115,7 @@ fn runPlacementScenario( test_renderer.memory.bytes.clearRetainingCapacity(); test_renderer.memory.last_write_start = 0; test_renderer.memory.last_write_len = 0; - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); _ = test_renderer.renderer.render(false); cost.stats.record(timer.read()); // Skip the first frame: it pays the initial full paint for every scenario. @@ -128,7 +129,7 @@ fn runPlacementScenario( return cost; } -fn runLargeStillTransmit(allocator: std.mem.Allocator, pool: *gp.GraphemePool) !FrameCost { +fn runLargeStillTransmit(io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool) !FrameCost { var test_renderer = try test_renderer_mod.TestRenderer.create(allocator, TERM_WIDTH, TERM_HEIGHT, pool); defer test_renderer.deinit(); test_renderer.renderer.terminal.caps.kitty_graphics = true; @@ -166,7 +167,7 @@ fn runLargeStillTransmit(allocator: std.mem.Allocator, pool: *gp.GraphemePool) ! test_renderer.memory.bytes.clearRetainingCapacity(); test_renderer.memory.last_write_start = 0; test_renderer.memory.last_write_len = 0; - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); _ = test_renderer.renderer.render(false); cost.stats.record(timer.read()); // Match the other renderer scenarios by excluding initial full paint. @@ -180,7 +181,7 @@ fn runLargeStillTransmit(allocator: std.mem.Allocator, pool: *gp.GraphemePool) ! return cost; } -fn runDrawFrameBuffer(allocator: std.mem.Allocator, with_image: bool) !FrameCost { +fn runDrawFrameBuffer(io: std.Io, allocator: std.mem.Allocator, with_image: bool) !FrameCost { var pool = gp.GraphemePool.init(allocator); defer pool.deinit(); var link_pool = link.LinkPool.init(allocator); @@ -198,7 +199,7 @@ fn runDrawFrameBuffer(allocator: std.mem.Allocator, with_image: bool) !FrameCost var cost = FrameCost{}; var iteration: usize = 0; while (iteration < 400) : (iteration += 1) { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); target.drawFrameBuffer(0, 0, source, null, null, null, null); cost.stats.record(timer.read()); cost.frames += 1; @@ -216,6 +217,7 @@ fn drawStaticKittyPlacements(target: *buffer.OptimizedBuffer, value: *image.Imag } fn runStaticKittyPlacementCount( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, count: usize, @@ -245,7 +247,7 @@ fn runStaticKittyPlacementCount( test_renderer.memory.bytes.clearRetainingCapacity(); test_renderer.memory.last_write_start = 0; test_renderer.memory.last_write_len = 0; - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); if (test_renderer.renderer.render(false) == .failed) return error.RenderFailed; cost.stats.record(timer.read()); cost.total_bytes += test_renderer.memory.bytes.items.len; @@ -271,6 +273,7 @@ fn drawOverlappingSixelPlacements( } fn runDirtySixelOverlapCount( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, count: usize, @@ -337,7 +340,7 @@ fn runDirtySixelOverlapCount( test_renderer.memory.bytes.clearRetainingCapacity(); test_renderer.memory.last_write_start = 0; test_renderer.memory.last_write_len = 0; - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); if (test_renderer.renderer.render(false) == .failed) return error.RenderFailed; cost.stats.record(timer.read()); cost.total_bytes += test_renderer.memory.bytes.items.len; @@ -347,6 +350,7 @@ fn runDirtySixelOverlapCount( } fn runSplitImageCommit( + io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, protocol: Protocol, @@ -417,7 +421,7 @@ fn runSplitImageCommit( test_renderer.memory.bytes.clearRetainingCapacity(); test_renderer.memory.last_write_start = 0; test_renderer.memory.last_write_len = 0; - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); const result = test_renderer.renderer.commitSplitFooterSnapshotBatched( snapshot, snapshot.width, @@ -432,8 +436,8 @@ fn runSplitImageCommit( if (result.status == .failed) return error.RenderFailed; const output = test_renderer.memory.bytes.items; switch (protocol) { - .kitty => if (std.mem.indexOf(u8, output, "\x1b_Ga=t") == null) return error.MissingKittyImageOutput, - .sixel => if (std.mem.indexOf(u8, output, "\x1bP0;1;0q") == null) return error.MissingSixelImageOutput, + .kitty => if (std.mem.find(u8, output, "\x1b_Ga=t") == null) return error.MissingKittyImageOutput, + .sixel => if (std.mem.find(u8, output, "\x1bP0;1;0q") == null) return error.MissingSixelImageOutput, .blocks => if (snapshot.image_placements.items.len != 0) return error.MissingBlockImageFallback, } if (iteration < 10) continue; @@ -444,13 +448,13 @@ fn runSplitImageCommit( return cost; } -pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8) ![]bench_utils.BenchResult { +pub fn run(io: std.Io, allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8) ![]bench_utils.BenchResult { _ = show_mem; const pool = gp.initGlobalPool(allocator); defer gp.deinitGlobalPool(); defer link.deinitGlobalLinkPool(); - var results: std.ArrayListUnmanaged(bench_utils.BenchResult) = .{}; + var results: std.ArrayListUnmanaged(bench_utils.BenchResult) = .empty; const Scenario = struct { name: []const u8, @@ -468,7 +472,7 @@ pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const }; for (scenarios) |scenario| { if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; - const cost = try runPlacementScenario(allocator, pool, scenario.protocol, 320, 200, scenario.animate, scenario.text_change); + const cost = try runPlacementScenario(io, allocator, pool, scenario.protocol, 320, 200, scenario.animate, scenario.text_change); try results.append(allocator, .{ .name = try std.fmt.allocPrint(allocator, "{s} ({d} bytes/frame)", .{ scenario.name, cost.bytesPerFrame() }), .min_ns = cost.stats.min_ns, @@ -483,7 +487,7 @@ pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const } if (bench_utils.matchesBenchFilter("kitty large still transmit", bench_filter)) { - const cost = try runLargeStillTransmit(allocator, pool); + const cost = try runLargeStillTransmit(io, allocator, pool); try results.append(allocator, .{ .name = try std.fmt.allocPrint(allocator, "kitty large still transmit ({d} bytes/frame)", .{cost.bytesPerFrame()}), .min_ns = cost.stats.min_ns, @@ -503,7 +507,7 @@ pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const }; for (framebuffer_scenarios) |scenario| { if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; - const cost = try runDrawFrameBuffer(allocator, scenario.with_image); + const cost = try runDrawFrameBuffer(io, allocator, scenario.with_image); try results.append(allocator, .{ .name = scenario.name, .min_ns = cost.stats.min_ns, @@ -520,7 +524,7 @@ pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const for ([_]usize{ 8, 32, 128, 512, 2048, 4096 }) |count| { const name = try std.fmt.allocPrint(allocator, "kitty static {d} placements", .{count}); if (!bench_utils.matchesBenchFilter(name, bench_filter)) continue; - const cost = try runStaticKittyPlacementCount(allocator, pool, count); + const cost = try runStaticKittyPlacementCount(io, allocator, pool, count); try results.append(allocator, .{ .name = try std.fmt.allocPrint(allocator, "{s} ({d} bytes/frame)", .{ name, cost.bytesPerFrame() }), .min_ns = cost.stats.min_ns, @@ -537,7 +541,7 @@ pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const for ([_]usize{ 8, 32, 128, 512, 2048, 4096 }) |count| { const name = try std.fmt.allocPrint(allocator, "sixel dirty overlap {d} transparent placements", .{count}); if (!bench_utils.matchesBenchFilter(name, bench_filter)) continue; - const cost = try runDirtySixelOverlapCount(allocator, pool, count); + const cost = try runDirtySixelOverlapCount(io, allocator, pool, count); try results.append(allocator, .{ .name = try std.fmt.allocPrint(allocator, "{s} ({d} bytes/frame)", .{ name, cost.bytesPerFrame() }), .min_ns = cost.stats.min_ns, @@ -563,7 +567,7 @@ pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const }; for (split_scenarios) |scenario| { if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; - const cost = try runSplitImageCommit(allocator, pool, scenario.protocol, scenario.placements); + const cost = try runSplitImageCommit(io, allocator, pool, scenario.protocol, scenario.placements); try results.append(allocator, .{ .name = try std.fmt.allocPrint(allocator, "{s} ({d} bytes/frame)", .{ scenario.name, cost.bytesPerFrame() }), .min_ns = cost.stats.min_ns, diff --git a/packages/core/src/zig/bench/renderer-output_bench.zig b/packages/core/src/zig/bench/renderer-output_bench.zig index 74233d099b..2edcb132e7 100644 --- a/packages/core/src/zig/bench/renderer-output_bench.zig +++ b/packages/core/src/zig/bench/renderer-output_bench.zig @@ -8,6 +8,7 @@ pub const benchName = "Renderer Output"; const Operation = enum { atomic, backend }; fn runScenario( + io: std.Io, allocator: std.mem.Allocator, result_allocator: std.mem.Allocator, name: []const u8, @@ -34,7 +35,7 @@ fn runScenario( var stats: bench_utils.BenchStats = .{}; var checksum: u64 = 0; for (0..iterations + 5) |iteration| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); switch (operation) { .atomic => try feed.writeAtomic(bytes), .backend => { @@ -76,7 +77,7 @@ fn runScenario( }; } -pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8) ![]bench_utils.BenchResult { +pub fn run(io: std.Io, allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8) ![]bench_utils.BenchResult { const scenarios = [_]struct { name: []const u8, byte_count: usize, @@ -88,12 +89,13 @@ pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const .{ .name = "writeAtomic 4 MiB", .byte_count = 4 * 1024 * 1024, .iterations = 100, .operation = .atomic }, .{ .name = "FeedBackend frame 4 MiB", .byte_count = 4 * 1024 * 1024, .iterations = 100, .operation = .backend }, }; - var results: std.ArrayListUnmanaged(bench_utils.BenchResult) = .{}; - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; + var results: std.ArrayListUnmanaged(bench_utils.BenchResult) = .empty; + var gpa: std.heap.DebugAllocator(.{}) = .init; defer _ = gpa.deinit(); for (scenarios) |scenario| { if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; try results.append(allocator, try runScenario( + io, gpa.allocator(), allocator, scenario.name, diff --git a/packages/core/src/zig/bench/renderer-overhead_bench.zig b/packages/core/src/zig/bench/renderer-overhead_bench.zig index 6c36efc5e7..e415138fca 100644 --- a/packages/core/src/zig/bench/renderer-overhead_bench.zig +++ b/packages/core/src/zig/bench/renderer-overhead_bench.zig @@ -30,7 +30,7 @@ fn drawFrame(target: anytype, frame: usize, scenario: Scenario) void { } } -fn runScenario(allocator: std.mem.Allocator, pool: *gp.GraphemePool, scenario: Scenario) !bench_utils.BenchStats { +fn runScenario(io: std.Io, allocator: std.mem.Allocator, pool: *gp.GraphemePool, scenario: Scenario) !bench_utils.BenchStats { var test_renderer = try test_renderer_mod.TestRenderer.create(allocator, WIDTH, HEIGHT, pool); defer test_renderer.deinit(); drawFrame(test_renderer.renderer.getNextBuffer(), 0, .full_change); @@ -44,7 +44,7 @@ fn runScenario(allocator: std.mem.Allocator, pool: *gp.GraphemePool, scenario: S test_renderer.memory.bytes.clearRetainingCapacity(); test_renderer.memory.last_write_start = 0; test_renderer.memory.last_write_len = 0; - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); _ = test_renderer.renderer.render(false); elapsed += timer.read(); } @@ -53,7 +53,7 @@ fn runScenario(allocator: std.mem.Allocator, pool: *gp.GraphemePool, scenario: S return stats; } -pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8) ![]bench_utils.BenchResult { +pub fn run(io: std.Io, allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8) ![]bench_utils.BenchResult { _ = show_mem; const pool = gp.initGlobalPool(allocator); defer gp.deinitGlobalPool(); @@ -64,10 +64,10 @@ pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const .{ .name = "10k cells one change no images", .kind = .one_change }, .{ .name = "10k cells full change no images", .kind = .full_change }, }; - var results: std.ArrayListUnmanaged(bench_utils.BenchResult) = .{}; + var results: std.ArrayListUnmanaged(bench_utils.BenchResult) = .empty; for (scenarios) |scenario| { if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; - const stats = try runScenario(allocator, pool, scenario.kind); + const stats = try runScenario(io, allocator, pool, scenario.kind); try results.append(allocator, .{ .name = scenario.name, .min_ns = stats.min_ns, diff --git a/packages/core/src/zig/bench/terminal-image_bench.zig b/packages/core/src/zig/bench/terminal-image_bench.zig index b086607f61..902dbf9792 100644 --- a/packages/core/src/zig/bench/terminal-image_bench.zig +++ b/packages/core/src/zig/bench/terminal-image_bench.zig @@ -91,6 +91,7 @@ fn fillPixels(pixels: []u8, scenario: Scenario) void { } fn appendDragonGeometryBenchmarks( + io: std.Io, allocator: std.mem.Allocator, results: *std.ArrayListUnmanaged(bench_utils.BenchResult), show_mem: bool, @@ -105,10 +106,10 @@ fn appendDragonGeometryBenchmarks( var run_any = false; for (names) |name| run_any = run_any or bench_utils.matchesBenchFilter(name, bench_filter); if (!run_any) return; - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; + var gpa: std.heap.DebugAllocator(.{}) = .init; defer _ = gpa.deinit(); const work_allocator = gpa.allocator(); - const encoded = try std.fs.cwd().readFileAlloc(work_allocator, "../../../examples/src/assets/image-demo.gif", 2 * 1024 * 1024); + const encoded = try std.Io.Dir.cwd().readFileAlloc(io, "../../../examples/src/assets/image-demo.gif", work_allocator, .limited(2 * 1024 * 1024)); defer work_allocator.free(encoded); const decoded = try image.decode(work_allocator, encoded, .{}); defer decoded.deinit(); @@ -120,22 +121,22 @@ fn appendDragonGeometryBenchmarks( }; for (names, geometries) |name, geometry| { if (!bench_utils.matchesBenchFilter(name, bench_filter)) continue; - var output: std.ArrayList(u8) = .empty; - defer output.deinit(work_allocator); + var output: std.Io.Writer.Allocating = .init(work_allocator); + defer output.deinit(); var stats: bench_utils.BenchStats = .{}; for (0..10) |_| { output.clearRetainingCapacity(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); const cropped = try image.extract(work_allocator, decoded, geometry.x, geometry.y, geometry.width, geometry.height); defer cropped.deinit(); const resized = try image.resize(work_allocator, cropped, geometry.output_width, geometry.output_height, .area); defer resized.deinit(); - try writeScenario(work_allocator, output.writer(work_allocator), resized, geometry.colors); + try writeScenario(work_allocator, &output.writer, resized, geometry.colors); stats.record(timer.read()); } const mem_stats: ?[]const bench_utils.MemStat = if (show_mem) blk: { const values = try allocator.alloc(bench_utils.MemStat, 1); - values[0] = .{ .name = "Payload", .bytes = output.items.len }; + values[0] = .{ .name = "Payload", .bytes = output.written().len }; break :blk values; } else null; try results.append(allocator, .{ @@ -173,6 +174,7 @@ fn appendResult( } fn appendKittyBenchmarks( + io: std.Io, allocator: std.mem.Allocator, results: *std.ArrayListUnmanaged(bench_utils.BenchResult), show_mem: bool, @@ -193,10 +195,10 @@ fn appendKittyBenchmarks( for (names) |name| run_any = run_any or bench_utils.matchesBenchFilter(name, bench_filter); if (!run_any) return; - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; + var gpa: std.heap.DebugAllocator(.{}) = .init; defer _ = gpa.deinit(); const work_allocator = gpa.allocator(); - const encoded = try std.fs.cwd().readFileAlloc(work_allocator, "../../../examples/src/assets/image-demo.gif", 2 * 1024 * 1024); + const encoded = try std.Io.Dir.cwd().readFileAlloc(io, "../../../examples/src/assets/image-demo.gif", work_allocator, .limited(2 * 1024 * 1024)); defer work_allocator.free(encoded); const decoded = try image.decode(work_allocator, encoded, .{}); defer decoded.deinit(); @@ -210,19 +212,19 @@ fn appendKittyBenchmarks( .{ .name = names[1], .source = cover }, }) |scenario| { if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; - var output: std.ArrayList(u8) = .empty; - defer output.deinit(work_allocator); - try output.ensureTotalCapacity(work_allocator, scenario.source.pixels.len * 4 / 3 + 8192); + var output: std.Io.Writer.Allocating = .init(work_allocator); + defer output.deinit(); + try output.ensureTotalCapacity(scenario.source.pixels.len * 4 / 3 + 8192); var stats: bench_utils.BenchStats = .{}; for (0..20) |_| { output.clearRetainingCapacity(); - var timer = try std.time.Timer.start(); - try terminal_image.writeKittyTransmit(output.writer(work_allocator), scenario.source, 7, false); + const timer = bench_utils.BenchTimer.start(io); + try terminal_image.writeKittyTransmit(&output.writer, scenario.source, 7, false); stats.record(timer.read()); } const mem_stats: ?[]const bench_utils.MemStat = if (show_mem) blk: { const values = try allocator.alloc(bench_utils.MemStat, 1); - values[0] = .{ .name = "Payload", .bytes = output.items.len }; + values[0] = .{ .name = "Payload", .bytes = output.written().len }; break :blk values; } else null; try appendResult(allocator, results, scenario.name, stats, mem_stats); @@ -232,7 +234,7 @@ fn appendKittyBenchmarks( var stats: bench_utils.BenchStats = .{}; for (0..20) |_| { var counting: CountingWriter = .{}; - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); try terminal_image.writeKittyTransmit(&counting, cover, 7, false); stats.record(timer.read()); } @@ -240,18 +242,18 @@ fn appendKittyBenchmarks( } if (bench_utils.matchesBenchFilter(names[3], bench_filter)) { - var output: std.ArrayList(u8) = .empty; - defer output.deinit(work_allocator); + var output: std.Io.Writer.Allocating = .init(work_allocator); + defer output.deinit(); var stats: bench_utils.BenchStats = .{}; for (0..1000) |_| { output.clearRetainingCapacity(); - var timer = try std.time.Timer.start(); - try terminal_image.writeKittyPlacement(output.writer(work_allocator), 7, 8, 0, 0, 36, 29, 0, 0, 576, 1015, -1, false); + const timer = bench_utils.BenchTimer.start(io); + try terminal_image.writeKittyPlacement(&output.writer, 7, 8, 0, 0, 36, 29, 0, 0, 576, 1015, -1, false); stats.record(timer.read()); } const mem_stats: ?[]const bench_utils.MemStat = if (show_mem) blk: { const values = try allocator.alloc(bench_utils.MemStat, 1); - values[0] = .{ .name = "Payload", .bytes = output.items.len }; + values[0] = .{ .name = "Payload", .bytes = output.written().len }; break :blk values; } else null; try appendResult(allocator, results, names[3], stats, mem_stats); @@ -262,19 +264,19 @@ fn appendKittyBenchmarks( .{ .name = names[5], .format = .rgb }, }) |scenario| { if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; - var output: std.ArrayList(u8) = .empty; - defer output.deinit(work_allocator); - try output.ensureTotalCapacity(work_allocator, cover.pixels.len * 4 / 3 + 8192); + var output: std.Io.Writer.Allocating = .init(work_allocator); + defer output.deinit(); + try output.ensureTotalCapacity(cover.pixels.len * 4 / 3 + 8192); var stats: bench_utils.BenchStats = .{}; for (0..20) |_| { output.clearRetainingCapacity(); - var timer = try std.time.Timer.start(); - try terminal_image.writeKittyTransmitFormat(output.writer(work_allocator), cover, 7, false, scenario.format); + const timer = bench_utils.BenchTimer.start(io); + try terminal_image.writeKittyTransmitFormat(&output.writer, cover, 7, false, scenario.format); stats.record(timer.read()); } const mem_stats: ?[]const bench_utils.MemStat = if (show_mem) blk: { const values = try allocator.alloc(bench_utils.MemStat, 1); - values[0] = .{ .name = "Payload", .bytes = output.items.len }; + values[0] = .{ .name = "Payload", .bytes = output.written().len }; break :blk values; } else null; try appendResult(allocator, results, scenario.name, stats, mem_stats); @@ -288,21 +290,21 @@ fn appendKittyBenchmarks( .{ .name = names[7], .format = .rgba }, }) |scenario| { if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; - var output: std.ArrayList(u8) = .empty; - defer output.deinit(work_allocator); - try output.ensureTotalCapacity(work_allocator, photo_pixels.len * 4 / 3 + 8192); + var output: std.Io.Writer.Allocating = .init(work_allocator); + defer output.deinit(); + try output.ensureTotalCapacity(photo_pixels.len * 4 / 3 + 8192); var stats: bench_utils.BenchStats = .{}; for (0..20) |_| { output.clearRetainingCapacity(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); const value = try image.createFromRgba(work_allocator, photo_pixels, 576, 1015, 576 * 4); - try terminal_image.writeKittyTransmitFormat(output.writer(work_allocator), value, 7, false, scenario.format); + try terminal_image.writeKittyTransmitFormat(&output.writer, value, 7, false, scenario.format); value.deinit(); stats.record(timer.read()); } const mem_stats: ?[]const bench_utils.MemStat = if (show_mem) blk: { const values = try allocator.alloc(bench_utils.MemStat, 1); - values[0] = .{ .name = "Payload", .bytes = output.items.len }; + values[0] = .{ .name = "Payload", .bytes = output.written().len }; break :blk values; } else null; try appendResult(allocator, results, scenario.name, stats, mem_stats); @@ -312,7 +314,7 @@ fn appendKittyBenchmarks( var stats: bench_utils.BenchStats = .{}; var checksum: u64 = 0; for (0..100) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); const copy = try cover.clone(); copy.discardEncoded(); var index: usize = 3; @@ -329,6 +331,7 @@ fn appendKittyBenchmarks( } fn appendKittyPngBenchmarks( + io: std.Io, allocator: std.mem.Allocator, results: *std.ArrayListUnmanaged(bench_utils.BenchResult), show_mem: bool, @@ -339,10 +342,10 @@ fn appendKittyPngBenchmarks( for (names) |name| run_any = run_any or bench_utils.matchesBenchFilter(name, bench_filter); if (!run_any) return; - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; + var gpa: std.heap.DebugAllocator(.{}) = .init; defer _ = gpa.deinit(); const work_allocator = gpa.allocator(); - const encoded = try std.fs.cwd().readFileAlloc(work_allocator, "../../../examples/src/assets/image-demo.png", 2 * 1024 * 1024); + const encoded = try std.Io.Dir.cwd().readFileAlloc(io, "../../../examples/src/assets/image-demo.png", work_allocator, .limited(2 * 1024 * 1024)); defer work_allocator.free(encoded); const decoded = try image.decode(work_allocator, encoded, .{}); defer decoded.deinit(); @@ -352,23 +355,23 @@ fn appendKittyPngBenchmarks( .{ .name = names[1], .format = .rgba }, }) |scenario| { if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; - var output: std.ArrayList(u8) = .empty; - defer output.deinit(work_allocator); - try output.ensureTotalCapacity(work_allocator, @max(encoded.len, decoded.pixels.len) * 4 / 3 + 8192); - try terminal_image.writeKittyTransmitFormat(output.writer(work_allocator), decoded, 7, false, scenario.format); + var output: std.Io.Writer.Allocating = .init(work_allocator); + defer output.deinit(); + try output.ensureTotalCapacity(@max(encoded.len, decoded.pixels.len) * 4 / 3 + 8192); + try terminal_image.writeKittyTransmitFormat(&output.writer, decoded, 7, false, scenario.format); var stats: bench_utils.BenchStats = .{}; var checksum: usize = 0; for (0..50) |_| { output.clearRetainingCapacity(); - var timer = try std.time.Timer.start(); - try terminal_image.writeKittyTransmitFormat(output.writer(work_allocator), decoded, 7, false, scenario.format); + const timer = bench_utils.BenchTimer.start(io); + try terminal_image.writeKittyTransmitFormat(&output.writer, decoded, 7, false, scenario.format); stats.record(timer.read()); - checksum +%= output.items.len + output.items[output.items.len / 2]; + checksum +%= output.written().len + output.written()[output.written().len / 2]; } if (checksum == 0) return error.InvalidKittyPngBenchmark; const mem_stats: ?[]const bench_utils.MemStat = if (show_mem) blk: { const values = try allocator.alloc(bench_utils.MemStat, 1); - values[0] = .{ .name = "Payload", .bytes = output.items.len }; + values[0] = .{ .name = "Payload", .bytes = output.written().len }; break :blk values; } else null; try appendResult(allocator, results, scenario.name, stats, mem_stats); @@ -376,6 +379,7 @@ fn appendKittyPngBenchmarks( } fn appendImageSwitchBenchmarks( + io: std.Io, allocator: std.mem.Allocator, results: *std.ArrayListUnmanaged(bench_utils.BenchResult), show_mem: bool, @@ -403,10 +407,10 @@ fn appendImageSwitchBenchmarks( for (names) |name| run_any = run_any or bench_utils.matchesBenchFilter(name, bench_filter); if (!run_any) return; - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; + var gpa: std.heap.DebugAllocator(.{}) = .init; defer _ = gpa.deinit(); const work_allocator = gpa.allocator(); - const encoded = try std.fs.cwd().readFileAlloc(work_allocator, "../../../examples/src/assets/image-demo.gif", 2 * 1024 * 1024); + const encoded = try std.Io.Dir.cwd().readFileAlloc(io, "../../../examples/src/assets/image-demo.gif", work_allocator, .limited(2 * 1024 * 1024)); defer work_allocator.free(encoded); const decoded = try image.decode(work_allocator, encoded, .{}); defer decoded.deinit(); @@ -424,12 +428,12 @@ fn appendImageSwitchBenchmarks( defer fit_resized.deinit(); const cover_resized = try image.resize(work_allocator, cover_crop, cover.output_width, cover.output_height, .area); defer cover_resized.deinit(); - var fit_payload: std.ArrayList(u8) = .empty; - defer fit_payload.deinit(work_allocator); - try terminal_image.writeSixelPayload(work_allocator, fit_payload.writer(work_allocator), fit_resized); - var cover_payload: std.ArrayList(u8) = .empty; - defer cover_payload.deinit(work_allocator); - try terminal_image.writeSixelPayload(work_allocator, cover_payload.writer(work_allocator), cover_resized); + var fit_payload: std.Io.Writer.Allocating = .init(work_allocator); + defer fit_payload.deinit(); + try terminal_image.writeSixelPayload(work_allocator, &fit_payload.writer, fit_resized); + var cover_payload: std.Io.Writer.Allocating = .init(work_allocator); + defer cover_payload.deinit(); + try terminal_image.writeSixelPayload(work_allocator, &cover_payload.writer, cover_resized); for ([_]struct { name: []const u8, geometry: Geometry }{ .{ .name = names[0], .geometry = fit }, @@ -442,7 +446,7 @@ fn appendImageSwitchBenchmarks( extracted.deinit(); } for (0..100) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); const extracted = try image.extract(work_allocator, decoded, scenario.geometry.x, scenario.geometry.y, scenario.geometry.width, scenario.geometry.height); stats.record(timer.read()); extracted.deinit(); @@ -457,7 +461,7 @@ fn appendImageSwitchBenchmarks( if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; var stats: bench_utils.BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); const resized = try image.resize(work_allocator, scenario.source, scenario.geometry.output_width, scenario.geometry.output_height, .area); stats.record(timer.read()); resized.deinit(); @@ -472,7 +476,7 @@ fn appendImageSwitchBenchmarks( if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; var stats: bench_utils.BenchStats = .{}; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); const extracted = try image.extract(work_allocator, decoded, scenario.geometry.x, scenario.geometry.y, scenario.geometry.width, scenario.geometry.height); const resized = try image.resize(work_allocator, extracted, scenario.geometry.output_width, scenario.geometry.output_height, .area); stats.record(timer.read()); @@ -483,23 +487,23 @@ fn appendImageSwitchBenchmarks( } for ([_]struct { name: []const u8, source: *image.Image, payload_bytes: usize }{ - .{ .name = names[6], .source = fit_resized, .payload_bytes = fit_payload.items.len }, - .{ .name = names[7], .source = cover_resized, .payload_bytes = cover_payload.items.len }, + .{ .name = names[6], .source = fit_resized, .payload_bytes = fit_payload.written().len }, + .{ .name = names[7], .source = cover_resized, .payload_bytes = cover_payload.written().len }, }) |scenario| { if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; - var output: std.ArrayList(u8) = .empty; - defer output.deinit(work_allocator); - try output.ensureTotalCapacity(work_allocator, scenario.payload_bytes); + var output: std.Io.Writer.Allocating = .init(work_allocator); + defer output.deinit(); + try output.ensureTotalCapacity(scenario.payload_bytes); var stats: bench_utils.BenchStats = .{}; for (0..iterations) |_| { output.clearRetainingCapacity(); - var timer = try std.time.Timer.start(); - try terminal_image.writeSixelPayload(work_allocator, output.writer(work_allocator), scenario.source); + const timer = bench_utils.BenchTimer.start(io); + try terminal_image.writeSixelPayload(work_allocator, &output.writer, scenario.source); stats.record(timer.read()); } const mem_stats: ?[]const bench_utils.MemStat = if (show_mem) blk: { const values = try allocator.alloc(bench_utils.MemStat, 1); - values[0] = .{ .name = "Payload", .bytes = output.items.len }; + values[0] = .{ .name = "Payload", .bytes = output.written().len }; break :blk values; } else null; try appendResult(allocator, results, scenario.name, stats, mem_stats); @@ -510,67 +514,67 @@ fn appendImageSwitchBenchmarks( .{ .name = names[15], .colors = 64 }, }) |scenario| { if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; - var output: std.ArrayList(u8) = .empty; - defer output.deinit(work_allocator); - try writeScenario(work_allocator, output.writer(work_allocator), cover_resized, scenario.colors); + var output: std.Io.Writer.Allocating = .init(work_allocator); + defer output.deinit(); + try writeScenario(work_allocator, &output.writer, cover_resized, scenario.colors); var stats: bench_utils.BenchStats = .{}; for (0..iterations) |_| { output.clearRetainingCapacity(); - var timer = try std.time.Timer.start(); - try writeScenario(work_allocator, output.writer(work_allocator), cover_resized, scenario.colors); + const timer = bench_utils.BenchTimer.start(io); + try writeScenario(work_allocator, &output.writer, cover_resized, scenario.colors); stats.record(timer.read()); } const mem_stats: ?[]const bench_utils.MemStat = if (show_mem) blk: { const values = try allocator.alloc(bench_utils.MemStat, 1); - values[0] = .{ .name = "Payload", .bytes = output.items.len }; + values[0] = .{ .name = "Payload", .bytes = output.written().len }; break :blk values; } else null; try appendResult(allocator, results, scenario.name, stats, mem_stats); } for ([_]struct { name: []const u8, geometry: Geometry, payload_bytes: usize }{ - .{ .name = names[8], .geometry = fit, .payload_bytes = fit_payload.items.len }, - .{ .name = names[9], .geometry = cover, .payload_bytes = cover_payload.items.len }, + .{ .name = names[8], .geometry = fit, .payload_bytes = fit_payload.written().len }, + .{ .name = names[9], .geometry = cover, .payload_bytes = cover_payload.written().len }, }) |scenario| { if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; - var output: std.ArrayList(u8) = .empty; - defer output.deinit(work_allocator); - try output.ensureTotalCapacity(work_allocator, scenario.payload_bytes); + var output: std.Io.Writer.Allocating = .init(work_allocator); + defer output.deinit(); + try output.ensureTotalCapacity(scenario.payload_bytes); var stats: bench_utils.BenchStats = .{}; for (0..iterations) |_| { output.clearRetainingCapacity(); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); const extracted = try image.extract(work_allocator, decoded, scenario.geometry.x, scenario.geometry.y, scenario.geometry.width, scenario.geometry.height); const resized = try image.resize(work_allocator, extracted, scenario.geometry.output_width, scenario.geometry.output_height, .area); - try terminal_image.writeSixelPayload(work_allocator, output.writer(work_allocator), resized); + try terminal_image.writeSixelPayload(work_allocator, &output.writer, resized); stats.record(timer.read()); resized.deinit(); extracted.deinit(); } const mem_stats: ?[]const bench_utils.MemStat = if (show_mem) blk: { const values = try allocator.alloc(bench_utils.MemStat, 1); - values[0] = .{ .name = "Payload", .bytes = output.items.len }; + values[0] = .{ .name = "Payload", .bytes = output.written().len }; break :blk values; } else null; try appendResult(allocator, results, scenario.name, stats, mem_stats); } for ([_]struct { name: []const u8, payload: []const u8 }{ - .{ .name = names[10], .payload = fit_payload.items }, - .{ .name = names[11], .payload = cover_payload.items }, + .{ .name = names[10], .payload = fit_payload.written() }, + .{ .name = names[11], .payload = cover_payload.written() }, }) |scenario| { if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; var stats: bench_utils.BenchStats = .{}; - var output: std.ArrayList(u8) = .empty; - defer output.deinit(work_allocator); - try output.ensureTotalCapacity(work_allocator, scenario.payload.len + 32); + var output: std.Io.Writer.Allocating = .init(work_allocator); + defer output.deinit(); + try output.ensureTotalCapacity(scenario.payload.len + 32); var checksum: usize = 0; for (0..500) |_| { output.clearRetainingCapacity(); - var timer = try std.time.Timer.start(); - try terminal_image.writeSixelFramedPayload(output.writer(work_allocator), scenario.payload, false); + const timer = bench_utils.BenchTimer.start(io); + try terminal_image.writeSixelFramedPayload(&output.writer, scenario.payload, false); stats.record(timer.read()); - checksum +%= output.items.len + output.items[output.items.len / 2]; + checksum +%= output.written().len + output.written()[output.written().len / 2]; } if (checksum == 0) return error.InvalidWarmPayloadBenchmark; const mem_stats: ?[]const bench_utils.MemStat = if (show_mem) blk: { @@ -599,7 +603,7 @@ fn appendImageSwitchBenchmarks( var stats: bench_utils.BenchStats = .{}; for (0..100) |_| { draw_buffer.clear(ansi.rgbColor(0, 0, 0, 0), null); - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); if (!try draw_buffer.drawImage( decoded, 1, @@ -621,8 +625,8 @@ fn appendImageSwitchBenchmarks( } } -pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8) ![]bench_utils.BenchResult { - var results: std.ArrayListUnmanaged(bench_utils.BenchResult) = .{}; +pub fn run(io: std.Io, allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8) ![]bench_utils.BenchResult { + var results: std.ArrayListUnmanaged(bench_utils.BenchResult) = .empty; for (scenarios) |scenario| { const count_name = try std.fmt.allocPrint(allocator, "{s} count-only", .{scenario.name}); const quantize_name = "Sixel 160x240 original baseline quantize-only"; @@ -630,7 +634,7 @@ pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const const run_counted = bench_utils.matchesBenchFilter(count_name, bench_filter); const run_quantized = scenario.pattern == .baseline and scenario.colors == 255 and bench_utils.matchesBenchFilter(quantize_name, bench_filter); if (!run_materialized and !run_counted and !run_quantized) continue; - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; + var gpa: std.heap.DebugAllocator(.{}) = .init; defer _ = gpa.deinit(); const work_allocator = gpa.allocator(); const pixels = try work_allocator.alloc(u8, @as(usize, scenario.width) * scenario.height * 4); @@ -638,22 +642,22 @@ pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const fillPixels(pixels, scenario); const value = try image.createFromRgba(work_allocator, pixels, scenario.width, scenario.height, scenario.width * 4); defer value.deinit(); - var output: std.ArrayList(u8) = .empty; - defer output.deinit(work_allocator); + var output: std.Io.Writer.Allocating = .init(work_allocator); + defer output.deinit(); - try writeScenario(work_allocator, output.writer(work_allocator), value, scenario.colors); + try writeScenario(work_allocator, &output.writer, value, scenario.colors); const iterations: usize = if (scenario.width > 160) 20 else 50; if (run_materialized) { var stats: bench_utils.BenchStats = .{}; for (0..iterations) |_| { output.clearRetainingCapacity(); - var timer = try std.time.Timer.start(); - try writeScenario(work_allocator, output.writer(work_allocator), value, scenario.colors); + const timer = bench_utils.BenchTimer.start(io); + try writeScenario(work_allocator, &output.writer, value, scenario.colors); stats.record(timer.read()); } const mem_stats: ?[]const bench_utils.MemStat = if (show_mem) blk: { const values = try allocator.alloc(bench_utils.MemStat, 1); - values[0] = .{ .name = "Payload", .bytes = output.items.len }; + values[0] = .{ .name = "Payload", .bytes = output.written().len }; break :blk values; } else null; try results.append(allocator, .{ @@ -673,10 +677,10 @@ pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const var count_stats: bench_utils.BenchStats = .{}; for (0..iterations) |_| { var counting: CountingWriter = .{}; - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); try writeScenario(work_allocator, &counting, value, scenario.colors); count_stats.record(timer.read()); - if (counting.bytes != output.items.len) return error.IncorrectSixelByteCount; + if (counting.bytes != output.written().len) return error.IncorrectSixelByteCount; } try results.append(allocator, .{ .name = count_name, @@ -694,7 +698,7 @@ pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const var quantize_stats: bench_utils.BenchStats = .{}; var checksum: usize = 0; for (0..iterations) |_| { - var timer = try std.time.Timer.start(); + const timer = bench_utils.BenchTimer.start(io); var quantized = try terminal_image.quantizeSixel(work_allocator, value, scenario.colors); quantize_stats.record(timer.read()); checksum +%= quantized.palette_len + quantized.indices[0]; @@ -714,9 +718,9 @@ pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const }); } } - try appendKittyBenchmarks(allocator, &results, show_mem, bench_filter); - try appendKittyPngBenchmarks(allocator, &results, show_mem, bench_filter); - try appendDragonGeometryBenchmarks(allocator, &results, show_mem, bench_filter); - try appendImageSwitchBenchmarks(allocator, &results, show_mem, bench_filter); + try appendKittyBenchmarks(io, allocator, &results, show_mem, bench_filter); + try appendKittyPngBenchmarks(io, allocator, &results, show_mem, bench_filter); + try appendDragonGeometryBenchmarks(io, allocator, &results, show_mem, bench_filter); + try appendImageSwitchBenchmarks(io, allocator, &results, show_mem, bench_filter); return results.toOwnedSlice(allocator); } diff --git a/packages/core/src/zig/buffer.zig b/packages/core/src/zig/buffer.zig index 582c4927a1..72cc290a0e 100644 --- a/packages/core/src/zig/buffer.zig +++ b/packages/core/src/zig/buffer.zig @@ -267,7 +267,7 @@ pub const OptimizedBuffer = struct { .id = owned_id, .scissor_stack = scissor_stack, .opacity_stack = opacity_stack, - .image_placements = .{}, + .image_placements = .empty, }; @memset(self.buffer.char, 0); diff --git a/packages/core/src/zig/build.zig b/packages/core/src/zig/build.zig index 28c0fecdf7..66eb83b0ba 100644 --- a/packages/core/src/zig/build.zig +++ b/packages/core/src/zig/build.zig @@ -559,9 +559,9 @@ pub fn build(b: *std.Build) void { .name = "opentui-bench", .root_module = bench_mod, }); - bench_exe.linkLibC(); - addImageShim(b, bench_exe, native_target, macos_sdk_path); - if (native_target.result.os.tag == .macos) addMacOSSDKSearchPaths(b, bench_exe, macos_sdk_path.?); + bench_mod.link_libc = true; + addImageShim(b, bench_mod, native_target, macos_sdk_path); + if (native_target.result.os.tag == .macos) addMacOSSDKSearchPaths(b, bench_mod, macos_sdk_path.?); const run_bench = b.addRunArtifact(bench_exe); if (b.args) |args| { run_bench.addArgs(args); diff --git a/packages/core/src/zig/image.zig b/packages/core/src/zig/image.zig index dc68991fff..0db0457a4d 100644 --- a/packages/core/src/zig/image.zig +++ b/packages/core/src/zig/image.zig @@ -1,7 +1,9 @@ const std = @import("std"); +const builtin = @import("builtin"); +const io = if (builtin.is_test) std.testing.io else @import("root").io; const Allocator = std.mem.Allocator; -var icc_cache_mutex: std.Thread.Mutex = .{}; +var icc_cache_mutex: std.Io.Mutex = .init; var icc_cache_clients: u32 = 0; extern fn ot_image_png_probe(data: [*]const u8, data_len: u32, width: *u32, height: *u32) c_int; @@ -310,7 +312,7 @@ fn pngKeywordByteAllowed(byte: u8) bool { } fn parsePngIccp(payload: []const u8, payload_offset: usize) !struct { offset: usize, len: usize } { - const name_end = std.mem.indexOfScalar(u8, payload, 0) orelse return error.MalformedInput; + const name_end = std.mem.findScalar(u8, payload, 0) orelse return error.MalformedInput; if (name_end == 0 or name_end > 79 or name_end + 2 > payload.len) return error.MalformedInput; var previous_space = false; for (payload[0..name_end], 0..) |byte, index| { @@ -344,37 +346,37 @@ pub const IccCacheStats = struct { }; pub fn clearIccCache() void { - icc_cache_mutex.lock(); - defer icc_cache_mutex.unlock(); + icc_cache_mutex.lock(io) catch unreachable; + defer icc_cache_mutex.unlock(io); ot_image_icc_cache_clear(); } pub fn retainIccCache() void { - icc_cache_mutex.lock(); - defer icc_cache_mutex.unlock(); + icc_cache_mutex.lock(io) catch unreachable; + defer icc_cache_mutex.unlock(io); std.debug.assert(icc_cache_clients < std.math.maxInt(u32)); icc_cache_clients += 1; } pub fn releaseIccCache() void { - icc_cache_mutex.lock(); - defer icc_cache_mutex.unlock(); + icc_cache_mutex.lock(io) catch unreachable; + defer icc_cache_mutex.unlock(io); std.debug.assert(icc_cache_clients > 0); icc_cache_clients -= 1; if (icc_cache_clients == 0) ot_image_icc_cache_clear(); } pub fn getIccCacheStats() IccCacheStats { - icc_cache_mutex.lock(); - defer icc_cache_mutex.unlock(); + icc_cache_mutex.lock(io) catch unreachable; + defer icc_cache_mutex.unlock(io); var stats: IccCacheStats = undefined; ot_image_icc_cache_stats(&stats.hits, &stats.misses, &stats.entries); return stats; } pub fn testFailIccProfileCopyAllocationOnce() void { - icc_cache_mutex.lock(); - defer icc_cache_mutex.unlock(); + icc_cache_mutex.lock(io) catch unreachable; + defer icc_cache_mutex.unlock(io); ot_image_icc_test_fail_profile_copy_allocation_once(); } @@ -555,8 +557,8 @@ fn scanPng(data: []const u8) !PngMetadata { } fn transformPngIcc(compressed: []const u8, color_type: u8, pixels: []u8, width: u32, height: u32) !void { - icc_cache_mutex.lock(); - defer icc_cache_mutex.unlock(); + icc_cache_mutex.lock(io) catch unreachable; + defer icc_cache_mutex.unlock(io); const result = ot_image_icc_transform_rgba( compressed.ptr, @intCast(compressed.len), @@ -671,14 +673,14 @@ fn probeInternal( if (metadata.iccp_compressed_len > 0) { const compressed = data[metadata.iccp_compressed_offset..][0..metadata.iccp_compressed_len]; - icc_cache_mutex.lock(); + icc_cache_mutex.lock(io) catch unreachable; const icc_result = ot_image_icc_validate( compressed.ptr, @intCast(compressed.len), metadata.color_type, max_icc_profile_bytes, ); - icc_cache_mutex.unlock(); + icc_cache_mutex.unlock(io); const icc_status = statusFromIccResult(icc_result); if (icc_status != .ok) return icc_status; } diff --git a/packages/core/src/zig/lib.zig b/packages/core/src/zig/lib.zig index fd3cdc2c52..0b90d1c272 100644 --- a/packages/core/src/zig/lib.zig +++ b/packages/core/src/zig/lib.zig @@ -1367,7 +1367,7 @@ export fn bufferDrawImage( ) u8 { const buffer_ptr = acquireBuffer(buffer_handle) orelse return 0; const image_ptr = acquireImage(image_handle) orelse return 0; - const protocol = std.meta.intToEnum(native_image.RenderProtocol, options.protocol) catch return 0; + const protocol = std.enums.fromInt(native_image.RenderProtocol, options.protocol) orelse return 0; return @intFromBool(buffer_ptr.drawImage( image_ptr, image_handle, @@ -2972,7 +2972,7 @@ export fn imageResize(image_handle: NativeHandle, width: u32, height: u32, filte const image = acquireImage(image_handle) orelse return @intFromEnum(native_image.Status.invalid_handle); const output = out_handle orelse return @intFromEnum(native_image.Status.invalid_argument); output.* = INVALID_HANDLE; - const resize_filter = std.meta.intToEnum(native_image.ResizeFilter, filter) catch return @intFromEnum(native_image.Status.invalid_argument); + const resize_filter = std.enums.fromInt(native_image.ResizeFilter, filter) orelse return @intFromEnum(native_image.Status.invalid_argument); const resized = native_image.resize(globalAllocator, image, width, height, resize_filter) catch |err| { return @intFromEnum(native_image.statusFromError(err)); }; @@ -3019,7 +3019,7 @@ export fn imageTransform(image_handle: NativeHandle, operation: u32, out_handle: const image = acquireImage(image_handle) orelse return @intFromEnum(native_image.Status.invalid_handle); const output = out_handle orelse return @intFromEnum(native_image.Status.invalid_argument); output.* = INVALID_HANDLE; - const transform_operation = std.meta.intToEnum(native_image.Transform, operation) catch return @intFromEnum(native_image.Status.invalid_argument); + const transform_operation = std.enums.fromInt(native_image.Transform, operation) orelse return @intFromEnum(native_image.Status.invalid_argument); const transformed = native_image.transform(globalAllocator, image, transform_operation) catch |err| { return @intFromEnum(native_image.statusFromError(err)); }; @@ -3039,7 +3039,7 @@ export fn imageComposite( const overlay = acquireImage(overlay_handle) orelse return @intFromEnum(native_image.Status.invalid_handle); const output = out_handle orelse return @intFromEnum(native_image.Status.invalid_argument); output.* = INVALID_HANDLE; - const blend_mode = std.meta.intToEnum(native_image.Blend, blend) catch return @intFromEnum(native_image.Status.invalid_argument); + const blend_mode = std.enums.fromInt(native_image.Blend, blend) orelse return @intFromEnum(native_image.Status.invalid_argument); const composited = native_image.composite(globalAllocator, base, overlay, left, top, blend_mode, opacity) catch |err| { return @intFromEnum(native_image.statusFromError(err)); }; diff --git a/packages/core/src/zig/renderer-output.zig b/packages/core/src/zig/renderer-output.zig index 12159faf0b..7962cf969b 100644 --- a/packages/core/src/zig/renderer-output.zig +++ b/packages/core/src/zig/renderer-output.zig @@ -820,7 +820,7 @@ pub const BufferedBackend = struct { /// Zig tests that want to exercise the feed path should drain the feed directly. pub const FeedBackend = struct { feed: *NativeSpanFeed.Stream, - frameBytes: std.ArrayListUnmanaged(u8) = .{}, + frameBytes: std.ArrayListUnmanaged(u8) = .empty, /// Set when staging a frame fails. No bytes from a failed frame are /// published; the renderer forces a later full repaint. diff --git a/packages/core/src/zig/renderer.zig b/packages/core/src/zig/renderer.zig index 833b82f961..b50ed21238 100644 --- a/packages/core/src/zig/renderer.zig +++ b/packages/core/src/zig/renderer.zig @@ -190,9 +190,9 @@ pub const CliRenderer = struct { height: u32, currentRenderBuffer: *OptimizedBuffer, nextRenderBuffer: *OptimizedBuffer, - currentImages: std.ArrayListUnmanaged(CommittedImage) = .{}, - pendingImages: std.ArrayListUnmanaged(CommittedImage) = .{}, - imageDirty: std.ArrayListUnmanaged(ImageDirty) = .{}, + currentImages: std.ArrayListUnmanaged(CommittedImage) = .empty, + pendingImages: std.ArrayListUnmanaged(CommittedImage) = .empty, + imageDirty: std.ArrayListUnmanaged(ImageDirty) = .empty, imageIdSalt: u32, kittyHistoryNextImageId: ?u32, imageRenderFailed: bool = false, @@ -380,7 +380,8 @@ pub const CliRenderer = struct { }; errdefer backend.deinit(); - const image_id_salt = 1 + @as(u32, @truncate(@as(u128, @bitCast(std.time.nanoTimestamp())) ^ @as(u128, @intFromPtr(self)))) % (std.math.maxInt(u32) - gp.IMAGE_ID_MASK - 1); + const timestamp: u96 = @bitCast(std.Io.Clock.now(.real, io).nanoseconds); + const image_id_salt = 1 + @as(u32, @truncate(@as(u128, timestamp) ^ @as(u128, @intFromPtr(self)))) % (std.math.maxInt(u32) - gp.IMAGE_ID_MASK - 1); self.* = .{ .width = width, .height = height, @@ -543,15 +544,15 @@ pub const CliRenderer = struct { for (self.currentImages.items) |current| { if (current.protocol != .kitty) continue; var delete_buf: [128]u8 = undefined; - var delete_stream = std.io.fixedBufferStream(&delete_buf); + var delete_writer: std.Io.Writer = .fixed(&delete_buf); terminal_image.writeKittyDelete( - delete_stream.writer(), + &delete_writer, self.kittyImageId(current.placement_id), null, true, self.terminal.isInTmux(), ) catch {}; - self.backend.writeOut(delete_stream.getWritten()); + self.backend.writeOut(delete_writer.buffered()); } } self.currentImages.clearRetainingCapacity(); @@ -1357,17 +1358,17 @@ pub const CliRenderer = struct { var quantized = try terminal_image.quantizeSixel(self.allocator, prepared, 255); defer quantized.deinit(); if (quantized.palette_len == 0) return; - var payload: std.ArrayList(u8) = .empty; - defer payload.deinit(self.allocator); + var payload: std.Io.Writer.Allocating = .init(self.allocator); + defer payload.deinit(); try terminal_image.writeSixelIndexedPayload( self.allocator, - payload.writer(self.allocator), + &payload.writer, quantized.indices, quantized.palette[0..quantized.palette_len], prepared.width(), prepared.height(), ); - try terminal_image.writeSixelFramedPayload(writer, payload.items, self.terminal.isInTmux()); + try terminal_image.writeSixelFramedPayload(writer, payload.written(), self.terminal.isInTmux()); } fn writeSnapshotKittyImage( @@ -2160,19 +2161,19 @@ pub const CliRenderer = struct { } var quantized = try terminal_image.quantizeSixel(self.allocator, prepared, 255); defer quantized.deinit(); - var payload: std.ArrayList(u8) = .empty; - defer payload.deinit(self.allocator); + var payload: std.Io.Writer.Allocating = .init(self.allocator); + defer payload.deinit(); if (quantized.palette_len > 0) { try terminal_image.writeSixelIndexedPayload( self.allocator, - payload.writer(self.allocator), + &payload.writer, quantized.indices, quantized.palette[0..quantized.palette_len], prepared.width(), prepared.height(), ); try ansi.ANSI.moveToOutput(writer, @intCast(placement.x + 1), @intCast(placement.y + 1 + @as(i32, @intCast(self.renderOffset)))); - try terminal_image.writeSixelFramedPayload(writer, payload.items, self.terminal.isInTmux()); + try terminal_image.writeSixelFramedPayload(writer, payload.written(), self.terminal.isInTmux()); } self.cacheSixelPayload(cache_key, &payload); } @@ -2248,9 +2249,9 @@ pub const CliRenderer = struct { return hasher.final(); } - fn cacheSixelPayload(self: *CliRenderer, key: SixelCacheKey, payload: *std.ArrayList(u8)) void { - if (payload.items.len > SIXEL_CACHE_MAX_BYTES) return; - const owned = payload.toOwnedSlice(self.allocator) catch return; + fn cacheSixelPayload(self: *CliRenderer, key: SixelCacheKey, payload: *std.Io.Writer.Allocating) void { + if (payload.written().len > SIXEL_CACHE_MAX_BYTES) return; + const owned = payload.toOwnedSlice() catch return; self.sixelCache.ensureUnusedCapacity(self.allocator, 1) catch { self.allocator.free(owned); return; @@ -2758,15 +2759,15 @@ pub const CliRenderer = struct { for (self.currentImages.items) |current| { if (current.protocol != .kitty) continue; var delete_buf: [128]u8 = undefined; - var stream = std.io.fixedBufferStream(&delete_buf); + var delete_writer: std.Io.Writer = .fixed(&delete_buf); terminal_image.writeKittyDelete( - stream.writer(), + &delete_writer, self.kittyImageId(current.placement_id), null, true, self.terminal.isInTmux(), ) catch {}; - self.writeOut(stream.getWritten()); + self.writeOut(delete_writer.buffered()); } } self.writeOut(ansi.ANSI.clearAndHome); diff --git a/packages/core/src/zig/terminal-image.zig b/packages/core/src/zig/terminal-image.zig index 81665fe54c..d2d9908692 100644 --- a/packages/core/src/zig/terminal-image.zig +++ b/packages/core/src/zig/terminal-image.zig @@ -520,8 +520,8 @@ pub fn writeSixelFramedPayload(writer: anytype, payload: []const u8, tmux: bool) } pub fn writeSixel(allocator: std.mem.Allocator, writer: anytype, image: *native_image.Image, tmux: bool) !void { - var payload: std.ArrayList(u8) = .empty; - defer payload.deinit(allocator); - try writeSixelPayload(allocator, payload.writer(allocator), image); - try writeSixelFramedPayload(writer, payload.items, tmux); + var payload: std.Io.Writer.Allocating = .init(allocator); + defer payload.deinit(); + try writeSixelPayload(allocator, &payload.writer, image); + try writeSixelFramedPayload(writer, payload.written(), tmux); } diff --git a/packages/core/src/zig/terminal.zig b/packages/core/src/zig/terminal.zig index 852235fa61..b1f42dea53 100644 --- a/packages/core/src/zig/terminal.zig +++ b/packages/core/src/zig/terminal.zig @@ -1089,10 +1089,10 @@ pub fn restoreTerminalModes(self: *Terminal, tty: anytype) !void { fn parseKittyGraphicsResponse(self: *Terminal, response: []const u8) void { if (!self.graphics_enabled) return; var offset: usize = 0; - while (std.mem.indexOfPos(u8, response, offset, "\x1b_G")) |start| { - const end = std.mem.indexOfPos(u8, response, start + 3, "\x1b\\") orelse return; + while (std.mem.findPos(u8, response, offset, "\x1b_G")) |start| { + const end = std.mem.findPos(u8, response, start + 3, "\x1b\\") orelse return; const frame = response[start + 3 .. end]; - const control_end = std.mem.indexOfScalar(u8, frame, ';') orelse frame.len; + const control_end = std.mem.findScalar(u8, frame, ';') orelse frame.len; var fields = std.mem.splitScalar(u8, frame[0..control_end], ','); while (fields.next()) |field| { if (std.mem.eql(u8, field, "i=31337")) { @@ -1108,7 +1108,7 @@ fn parseKittyGraphicsResponse(self: *Terminal, response: []const u8) void { fn parseSixelDeviceAttributes(self: *Terminal, response: []const u8) void { if (!self.graphics_enabled) return; var offset: usize = 0; - while (std.mem.indexOfPos(u8, response, offset, "\x1b[?")) |start| { + while (std.mem.findPos(u8, response, offset, "\x1b[?")) |start| { var end = start + 3; while (end < response.len and (std.ascii.isDigit(response[end]) or response[end] == ';')) : (end += 1) {} if (end >= response.len) return; @@ -1129,7 +1129,7 @@ fn parseSixelDeviceAttributes(self: *Terminal, response: []const u8) void { } fn semanticVersionAtLeast(version: []const u8, required_major: u32, required_minor: u32) bool { - const suffix_start = std.mem.indexOfScalar(u8, version, '-'); + const suffix_start = std.mem.findScalar(u8, version, '-'); const core = if (suffix_start) |index| version[0..index] else version; var parts = std.mem.splitScalar(u8, core, '.'); const major_text = parts.next() orelse return false; diff --git a/packages/core/src/zig/tests/buffer_test.zig b/packages/core/src/zig/tests/buffer_test.zig index 4241fc0642..0307d8f629 100644 --- a/packages/core/src/zig/tests/buffer_test.zig +++ b/packages/core/src/zig/tests/buffer_test.zig @@ -75,7 +75,7 @@ test "OptimizedBuffer flattens image placements into owned block cells" { const cell = target.get(0, 0).?; try std.testing.expect(!gp.isImageChar(cell.char)); - try std.testing.expect(std.mem.indexOfScalar(u32, &buffer_mod.quadrantChars, cell.char) != null); + try std.testing.expect(std.mem.findScalar(u32, &buffer_mod.quadrantChars, cell.char) != null); try std.testing.expectEqual(@as(usize, 0), target.image_placements.items.len); try std.testing.expectEqual(@as(u32, 1), source.ref_count); } diff --git a/packages/core/src/zig/tests/image_test.zig b/packages/core/src/zig/tests/image_test.zig index 6308dfaae0..9d4eb92a86 100644 --- a/packages/core/src/zig/tests/image_test.zig +++ b/packages/core/src/zig/tests/image_test.zig @@ -310,7 +310,7 @@ test "JPEG decode rejects EOI before the first scan" { const encoded = "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAACAAMDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACP/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAVAQEBAAAAAAAAAAAAAAAAAAAHCf/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/ADoDFU3/2Q=="; const jpeg = try decodeBase64(encoded); defer std.testing.allocator.free(jpeg); - const sos = std.mem.indexOf(u8, jpeg, &[_]u8{ 0xFF, 0xDA }) orelse return error.TestUnexpectedResult; + const sos = std.mem.find(u8, jpeg, &[_]u8{ 0xFF, 0xDA }) orelse return error.TestUnexpectedResult; const malformed = try std.testing.allocator.alloc(u8, sos + 2); defer std.testing.allocator.free(malformed); @@ -324,7 +324,7 @@ test "JPEG decode rejects a scan without entropy data" { const encoded = "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAACAAMDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACP/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAVAQEBAAAAAAAAAAAAAAAAAAAHCf/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/ADoDFU3/2Q=="; const jpeg = try decodeBase64(encoded); defer std.testing.allocator.free(jpeg); - const sos = std.mem.indexOf(u8, jpeg, &[_]u8{ 0xFF, 0xDA }) orelse return error.TestUnexpectedResult; + const sos = std.mem.find(u8, jpeg, &[_]u8{ 0xFF, 0xDA }) orelse return error.TestUnexpectedResult; const scan_header_length = std.mem.readInt(u16, jpeg[sos + 2 ..][0..2], .big); const after_scan_header = sos + 2 + scan_header_length; @@ -340,7 +340,7 @@ test "JPEG decode rejects an incomplete entropy-coded scan" { const encoded = "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAACAAMDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACP/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAVAQEBAAAAAAAAAAAAAAAAAAAHCf/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/ADoDFU3/2Q=="; const jpeg = try decodeBase64(encoded); defer std.testing.allocator.free(jpeg); - const sos = std.mem.indexOf(u8, jpeg, &[_]u8{ 0xFF, 0xDA }) orelse return error.TestUnexpectedResult; + const sos = std.mem.find(u8, jpeg, &[_]u8{ 0xFF, 0xDA }) orelse return error.TestUnexpectedResult; const scan_header_length = std.mem.readInt(u16, jpeg[sos + 2 ..][0..2], .big); const after_scan_header = sos + 2 + scan_header_length; @@ -356,7 +356,7 @@ test "JPEG probe applies dimension limits before full scan validation" { const encoded = "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAACAAMDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACP/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAVAQEBAAAAAAAAAAAAAAAAAAAHCf/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/ADoDFU3/2Q=="; const jpeg = try decodeBase64(encoded); defer std.testing.allocator.free(jpeg); - const sos = std.mem.indexOf(u8, jpeg, &[_]u8{ 0xFF, 0xDA }) orelse return error.TestUnexpectedResult; + const sos = std.mem.find(u8, jpeg, &[_]u8{ 0xFF, 0xDA }) orelse return error.TestUnexpectedResult; const scan_header_length = std.mem.readInt(u16, jpeg[sos + 2 ..][0..2], .big); const after_scan_header = sos + 2 + scan_header_length; @@ -377,7 +377,7 @@ test "progressive JPEG decode rejects a final scan without entropy data" { var search_start: usize = 0; var final_sos: ?usize = null; - while (std.mem.indexOfPos(u8, jpeg, search_start, &[_]u8{ 0xFF, 0xDA })) |sos| { + while (std.mem.findPos(u8, jpeg, search_start, &[_]u8{ 0xFF, 0xDA })) |sos| { final_sos = sos; search_start = sos + 2; } @@ -659,7 +659,7 @@ fn injectJpegExifOrientation( } test "JPEG EXIF orientation swaps probe and decode dimensions" { - const jpeg = try std.fs.cwd().readFileAlloc(std.testing.allocator, "../tests/fixtures/images/orientation.jpg", 1 << 20); + const jpeg = try std.Io.Dir.cwd().readFileAlloc(std.testing.io, "../tests/fixtures/images/orientation.jpg", std.testing.allocator, .limited(1 << 20)); defer std.testing.allocator.free(jpeg); const plain = try image.decode(std.testing.allocator, jpeg, .{}); defer plain.deinit(); @@ -701,7 +701,7 @@ test "JPEG EXIF orientation swaps probe and decode dimensions" { } test "JPEG EXIF orientation 180 keeps dimensions" { - const jpeg = try std.fs.cwd().readFileAlloc(std.testing.allocator, "../tests/fixtures/images/orientation.jpg", 1 << 20); + const jpeg = try std.Io.Dir.cwd().readFileAlloc(std.testing.io, "../tests/fixtures/images/orientation.jpg", std.testing.allocator, .limited(1 << 20)); defer std.testing.allocator.free(jpeg); const plain = try image.decode(std.testing.allocator, jpeg, .{}); defer plain.deinit(); @@ -730,7 +730,7 @@ test "JPEG EXIF orientation 180 keeps dimensions" { } test "JPEG EXIF orientation ignores invalid values and uses the default" { - const jpeg = try std.fs.cwd().readFileAlloc(std.testing.allocator, "../tests/fixtures/images/orientation.jpg", 1 << 20); + const jpeg = try std.Io.Dir.cwd().readFileAlloc(std.testing.io, "../tests/fixtures/images/orientation.jpg", std.testing.allocator, .limited(1 << 20)); defer std.testing.allocator.free(jpeg); for ([_]u16{ 0, 9, 200 }) |invalid| { const bytes = try injectJpegExifOrientation(std.testing.allocator, jpeg, invalid, .little); diff --git a/packages/core/src/zig/tests/renderer_test.zig b/packages/core/src/zig/tests/renderer_test.zig index 68ff11e801..3cb2229794 100644 --- a/packages/core/src/zig/tests/renderer_test.zig +++ b/packages/core/src/zig/tests/renderer_test.zig @@ -22,8 +22,8 @@ const TestMemoryOutput = test_renderer_mod.TestMemoryOutput; const TestRenderer = test_renderer_mod.TestRenderer; fn lastSixelFrame(output: []const u8) ![]const u8 { - const start = std.mem.lastIndexOf(u8, output, "\x1bP0;1;0q") orelse return error.NoSixelPayload; - const end = std.mem.indexOfPos(u8, output, start, "\x1b\\") orelse return error.NoSixelPayload; + const start = std.mem.findLast(u8, output, "\x1bP0;1;0q") orelse return error.NoSixelPayload; + const end = std.mem.findPos(u8, output, start, "\x1b\\") orelse return error.NoSixelPayload; return output[start .. end + 2]; } @@ -102,8 +102,8 @@ test "renderer emits Kitty image once and leaves unchanged frame empty" { try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .auto)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1b_Ga=t,f=24,s=1,v=1,i=") != null); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "c=1,r=1,x=0,y=0,w=1,h=1,C=1,z=-1499999999") != null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "\x1b_Ga=t,f=24,s=1,v=1,i=") != null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "c=1,r=1,x=0,y=0,w=1,h=1,C=1,z=-1499999999") != null); try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .auto)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); @@ -128,11 +128,11 @@ test "renderer emits Sixel only with known pixel dimensions" { try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .auto)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); try std.testing.expectEqual(@as(u64, 1), test_renderer.renderer.sixelCacheMisses); try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .auto)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); try std.testing.expectEqual(@as(u64, 1), test_renderer.renderer.sixelCacheHits); try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 1, 0, 1, 1, 2, 2, 0, 0, 1, 1, .auto)); @@ -207,7 +207,7 @@ test "renderer does not copy identity Sixel geometry" { try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); try std.testing.expect(!source_allocator.has_induced_failure); try std.testing.expectEqual(source_allocations_before_render, source_allocator.allocations); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); } test "renderer repaints unchanged upper Sixel after an overlapping lower image changes" { @@ -404,12 +404,12 @@ test "renderer honors per-placement protocol overrides" { try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .kitty)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1b_Ga=t") != null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "\x1b_Ga=t") != null); test_renderer.renderer.terminal.caps.kitty_graphics = true; try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .blocks)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1b_Ga=t") == null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "\x1b_Ga=t") == null); } test "renderer honors global image protocol override for auto placements" { @@ -428,7 +428,7 @@ test "renderer honors global image protocol override for auto placements" { test_renderer.renderer.terminal.image_protocol = .kitty; try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .auto)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1b_Ga=t") != null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "\x1b_Ga=t") != null); } test "renderer keeps unresolved Sixel fallback frames as no-ops" { @@ -448,8 +448,8 @@ test "renderer keeps unresolved Sixel fallback frames as no-ops" { try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .auto)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); const first = test_renderer.memory.lastWrite(); - try std.testing.expect(std.mem.indexOf(u8, first, "\x1bP0;1;0q") == null); - try std.testing.expect(std.mem.indexOf(u8, first, "█") != null); + try std.testing.expect(std.mem.find(u8, first, "\x1bP0;1;0q") == null); + try std.testing.expect(std.mem.find(u8, first, "█") != null); try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .auto)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); try std.testing.expectEqual(@as(usize, 0), test_renderer.memory.lastWrite().len); @@ -472,8 +472,8 @@ test "renderer repaints a final blocks placement over Sixel" { try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .blocks)); _ = test_renderer.renderer.render(true); const output = test_renderer.memory.lastWrite(); - const sixel = std.mem.indexOf(u8, output, "\x1bP0;1;0q") orelse return error.TestUnexpectedResult; - const block = std.mem.lastIndexOf(u8, output, "█") orelse return error.TestUnexpectedResult; + const sixel = std.mem.find(u8, output, "\x1bP0;1;0q") orelse return error.TestUnexpectedResult; + const block = std.mem.findLast(u8, output, "█") orelse return error.TestUnexpectedResult; try std.testing.expect(block > sixel); } @@ -501,20 +501,20 @@ test "renderer does not retransmit Sixel when overlay text changes" { try std.testing.expect(try next.drawImage(value, image_handle, 0, 0, 2, 1, 2, 2, 0, 0, 1, 1, .sixel)); try next.drawText("1", 1, 0, fg, bg, 0); _ = test_renderer.renderer.render(false); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") == null); - try std.testing.expect(std.mem.indexOfScalar(u8, test_renderer.memory.lastWrite(), '1') != null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") == null); + try std.testing.expect(std.mem.findScalar(u8, test_renderer.memory.lastWrite(), '1') != null); next = test_renderer.renderer.getNextBuffer(); try std.testing.expect(try next.drawImage(value, image_handle, 0, 0, 2, 1, 2, 2, 0, 0, 1, 1, .sixel)); try next.drawText("2", 1, 0, fg, bg, 0); _ = test_renderer.renderer.render(false); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") == null); - try std.testing.expect(std.mem.indexOfScalar(u8, test_renderer.memory.lastWrite(), '2') != null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") == null); + try std.testing.expect(std.mem.findScalar(u8, test_renderer.memory.lastWrite(), '2') != null); next = test_renderer.renderer.getNextBuffer(); try std.testing.expect(try next.drawImage(value, image_handle, 0, 0, 2, 1, 2, 2, 0, 0, 1, 1, .sixel)); _ = test_renderer.renderer.render(false); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); } test "renderer replays a wide grapheme that starts before a Sixel placement" { @@ -537,8 +537,8 @@ test "renderer replays a wide grapheme that starts before a Sixel placement" { _ = test_renderer.renderer.render(true); const output = test_renderer.memory.lastWrite(); - const sixel = std.mem.indexOf(u8, output, "\x1bP0;1;0q") orelse return error.TestUnexpectedResult; - const overlay = std.mem.lastIndexOf(u8, output, "界") orelse return error.TestUnexpectedResult; + const sixel = std.mem.find(u8, output, "\x1bP0;1;0q") orelse return error.TestUnexpectedResult; + const overlay = std.mem.findLast(u8, output, "界") orelse return error.TestUnexpectedResult; try std.testing.expect(overlay > sixel); } @@ -569,13 +569,13 @@ test "renderer preserves terminal semantics when replaying cells over Sixel" { _ = test_renderer.renderer.render(true); const output = test_renderer.memory.lastWrite(); - const sixel = std.mem.lastIndexOf(u8, output, "\x1bP0;1;0q") orelse return error.TestUnexpectedResult; + const sixel = std.mem.findLast(u8, output, "\x1bP0;1;0q") orelse return error.TestUnexpectedResult; const replay = output[sixel..]; - try std.testing.expect(std.mem.indexOf(u8, replay, "\x1b]8;id=") != null); - try std.testing.expect(std.mem.indexOf(u8, replay, ";https://example.com/replayed\x1b\\") != null); - try std.testing.expect(std.mem.indexOf(u8, replay, "\x1b]8;;\x1b\\") != null); - try std.testing.expect(std.mem.indexOf(u8, replay, "\x1b]66;w=2;界\x1b\\") != null); - try std.testing.expect(std.mem.indexOf(u8, replay, "\x1b[0m\x1b[1;3H") != null); + try std.testing.expect(std.mem.find(u8, replay, "\x1b]8;id=") != null); + try std.testing.expect(std.mem.find(u8, replay, ";https://example.com/replayed\x1b\\") != null); + try std.testing.expect(std.mem.find(u8, replay, "\x1b]8;;\x1b\\") != null); + try std.testing.expect(std.mem.find(u8, replay, "\x1b]66;w=2;界\x1b\\") != null); + try std.testing.expect(std.mem.find(u8, replay, "\x1b[0m\x1b[1;3H") != null); } fn expectPlaneCoversImage(protocol: image.RenderProtocol) !void { @@ -606,10 +606,10 @@ fn expectPlaneCoversImage(protocol: image.RenderProtocol) !void { _ = test_renderer.renderer.render(false); const covered_output = test_renderer.memory.lastWrite(); try std.testing.expect(covered_output.len > 0); - try std.testing.expect(std.mem.indexOf(u8, covered_output, "\x1b_Ga=t") == null); - try std.testing.expect(std.mem.indexOf(u8, covered_output, "a=p") == null); - try std.testing.expect(std.mem.indexOf(u8, covered_output, "a=d") == null); - try std.testing.expect(std.mem.indexOf(u8, covered_output, "\x1bP0;1;0q") == null); + try std.testing.expect(std.mem.find(u8, covered_output, "\x1b_Ga=t") == null); + try std.testing.expect(std.mem.find(u8, covered_output, "a=p") == null); + try std.testing.expect(std.mem.find(u8, covered_output, "a=d") == null); + try std.testing.expect(std.mem.find(u8, covered_output, "\x1bP0;1;0q") == null); next = test_renderer.renderer.getNextBuffer(); try std.testing.expect(try next.drawImage(value, image_handle, 0, 0, 2, 1, 2, 2, 0, 0, 1, 1, protocol)); @@ -619,11 +619,11 @@ fn expectPlaneCoversImage(protocol: image.RenderProtocol) !void { switch (protocol) { .kitty => { try std.testing.expect(restored_output.len > 0); - try std.testing.expect(std.mem.indexOf(u8, restored_output, "\x1b_Ga=t") == null); - try std.testing.expect(std.mem.indexOf(u8, restored_output, "a=p") == null); + try std.testing.expect(std.mem.find(u8, restored_output, "\x1b_Ga=t") == null); + try std.testing.expect(std.mem.find(u8, restored_output, "a=p") == null); }, - .sixel => try std.testing.expect(std.mem.indexOf(u8, restored_output, "\x1bP0;1;0q") != null), - .blocks => try std.testing.expect(std.mem.indexOf(u8, restored_output, "█") != null), + .sixel => try std.testing.expect(std.mem.find(u8, restored_output, "\x1bP0;1;0q") != null), + .blocks => try std.testing.expect(std.mem.find(u8, restored_output, "█") != null), .auto => unreachable, } } @@ -658,9 +658,9 @@ test "renderer splits changed background runs around clean Kitty image cells" { try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); const output = test_renderer.memory.lastWrite(); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[1;1H") != null); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[0m\x1b[1;4H") != null); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b_G") == null); + try std.testing.expect(std.mem.find(u8, output, "\x1b[1;1H") != null); + try std.testing.expect(std.mem.find(u8, output, "\x1b[0m\x1b[1;4H") != null); + try std.testing.expect(std.mem.find(u8, output, "\x1b_G") == null); } test "renderer retransmits Sixel after removing an alpha-colored plane" { @@ -702,8 +702,8 @@ test "renderer clears old Sixel pixels when replacing an image" { try std.testing.expect(try next.drawImage(replacement, replacement_handle, 0, 0, 2, 1, 2, 2, 0, 0, 2, 1, .sixel)); _ = test_renderer.renderer.render(false); const output = test_renderer.memory.lastWrite(); - const sixel = std.mem.indexOf(u8, output, "\x1bP0;1;0q") orelse return error.TestUnexpectedResult; - try std.testing.expect(std.mem.indexOfScalar(u8, output[0..sixel], ' ') != null); + const sixel = std.mem.find(u8, output, "\x1bP0;1;0q") orelse return error.TestUnexpectedResult; + try std.testing.expect(std.mem.findScalar(u8, output[0..sixel], ' ') != null); } const PaintedSixelColor = struct { r: u8, g: u8, b: u8 }; @@ -711,8 +711,8 @@ const PaintedSixelColor = struct { r: u8, g: u8, b: u8 }; // Parses the last Sixel DCS in an output stream and returns the palette colors // that actually paint at least one pixel (RGB in the 0-100 Sixel scale). fn paintedSixelColors(output: []const u8, colors: *[8]PaintedSixelColor) !usize { - const start = std.mem.lastIndexOf(u8, output, "\x1bP0;1;0q") orelse return error.NoSixelPayload; - const end = std.mem.indexOfPos(u8, output, start, "\x1b\\") orelse return error.NoSixelPayload; + const start = std.mem.findLast(u8, output, "\x1bP0;1;0q") orelse return error.NoSixelPayload; + const end = std.mem.findPos(u8, output, start, "\x1b\\") orelse return error.NoSixelPayload; const payload = output[start + 8 .. end]; var palette = [_][3]u8{.{ 0, 0, 0 }} ** 256; @@ -863,7 +863,7 @@ test "renderer materializes lazy ICC PNGs before applying Sixel opacity" { try std.testing.expect(try next.drawImage(value, image_handle, 0, 0, 3, 1, 3, 1, 0, 0, 3, 1, .sixel)); next.popOpacity(); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); try std.testing.expectEqual(@as(usize, 0), value.pixels.len); } @@ -898,8 +898,8 @@ test "renderer keeps image alpha holes transparent while dimming by opacity" { try std.testing.expectEqual(@as(usize, 1), count); try std.testing.expect(colors[0].r >= 55 and colors[0].r <= 65); - const start = std.mem.lastIndexOf(u8, output, "\x1bP0;1;0q").?; - const end = std.mem.indexOfPos(u8, output, start, "\x1b\\").?; + const start = std.mem.findLast(u8, output, "\x1bP0;1;0q").?; + const end = std.mem.findPos(u8, output, start, "\x1b\\").?; const payload = output[start + 8 .. end]; // Exactly one column paints in the single 2x1 band: one '@' data char. try std.testing.expectEqual(@as(usize, 1), std.mem.count(u8, payload, "@")); @@ -2398,8 +2398,8 @@ test "renderer - pinned split scrollback repaints live native images after appen try std.testing.expectEqual(renderer.RenderStatus.rendered, result.status); const output = test_renderer.memory.lastWrite(); - const append_index = std.mem.indexOf(u8, output, "line") orelse return error.TestUnexpectedResult; - const placement_index = std.mem.indexOf(u8, output, "\x1b_Ga=p") orelse return error.TestUnexpectedResult; + const append_index = std.mem.find(u8, output, "line") orelse return error.TestUnexpectedResult; + const placement_index = std.mem.find(u8, output, "\x1b_Ga=p") orelse return error.TestUnexpectedResult; try std.testing.expect(placement_index > append_index); } @@ -2424,10 +2424,10 @@ test "renderer - split scrollback materializes image fallback cells" { _ = test_renderer.renderer.resetSplitScrollback(2, 2); _ = test_renderer.renderer.commitSplitFooterSnapshotBatched(snapshot, 2, false, true, 2, false, true, true); const output = test_renderer.memory.lastWrite(); - try std.testing.expect(std.mem.indexOfScalar(u8, output, 'X') != null); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b_G") == null); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1bP0;1;0q") == null); - try std.testing.expect(std.mem.indexOf(u8, output, "█") != null); + try std.testing.expect(std.mem.findScalar(u8, output, 'X') != null); + try std.testing.expect(std.mem.find(u8, output, "\x1b_G") == null); + try std.testing.expect(std.mem.find(u8, output, "\x1bP0;1;0q") == null); + try std.testing.expect(std.mem.find(u8, output, "█") != null); } test "renderer - split scrollback uses native Kitty when Kitty is selected" { @@ -2455,12 +2455,12 @@ test "renderer - split scrollback uses native Kitty when Kitty is selected" { try std.testing.expectEqual(renderer.RenderStatus.rendered, result.status); const output = test_renderer.memory.lastWrite(); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b_Ga=t") != null); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b_Ga=p") != null); - try std.testing.expect(std.mem.indexOf(u8, output, ",U=1,") == null); - try std.testing.expect(std.mem.indexOf(u8, output, "\u{10EEEE}") == null); - try std.testing.expect(std.mem.indexOf(u8, output, "█") == null); - try std.testing.expect(std.mem.indexOf(u8, output, "48;2;1;2;3") == null); + try std.testing.expect(std.mem.find(u8, output, "\x1b_Ga=t") != null); + try std.testing.expect(std.mem.find(u8, output, "\x1b_Ga=p") != null); + try std.testing.expect(std.mem.find(u8, output, ",U=1,") == null); + try std.testing.expect(std.mem.find(u8, output, "\u{10EEEE}") == null); + try std.testing.expect(std.mem.find(u8, output, "█") == null); + try std.testing.expect(std.mem.find(u8, output, "48;2;1;2;3") == null); } test "renderer - failed Kitty scrollback preparation does not publish the batch" { @@ -2503,7 +2503,7 @@ test "renderer - failed Kitty scrollback preparation does not publish the batch" const retry = test_renderer.renderer.commitSplitFooterSnapshotBatched(snapshot, 1, false, true, 2, false, true, true); try std.testing.expectEqual(renderer.RenderStatus.rendered, retry.status); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1b_Ga=t") != null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "\x1b_Ga=t") != null); } test "renderer - split scrollback emits native Sixel images when placement geometry is available" { @@ -2530,10 +2530,10 @@ test "renderer - split scrollback emits native Sixel images when placement geome try std.testing.expectEqual(renderer.RenderStatus.rendered, result.status); const output = test_renderer.memory.lastWrite(); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1bP0;1;0q") != null); - try std.testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.saveCursorState) != null); - try std.testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.restoreCursorState) != null); - try std.testing.expect(std.mem.indexOf(u8, output, "█") == null); + try std.testing.expect(std.mem.find(u8, output, "\x1bP0;1;0q") != null); + try std.testing.expect(std.mem.find(u8, output, ansi.ANSI.saveCursorState) != null); + try std.testing.expect(std.mem.find(u8, output, ansi.ANSI.restoreCursorState) != null); + try std.testing.expect(std.mem.find(u8, output, "█") == null); var covered = try OptimizedBuffer.init(std.testing.allocator, 1, 1, .{ .pool = pool, .link_pool = &local_link_pool }); defer covered.deinit(); @@ -2541,8 +2541,8 @@ test "renderer - split scrollback emits native Sixel images when placement geome try covered.drawText("X", 0, 0, .{ 255, 255, 255, 255 }, null, 0); const covered_result = test_renderer.renderer.commitSplitFooterSnapshotBatched(covered, 1, false, true, 2, false, true, true); try std.testing.expectEqual(renderer.RenderStatus.rendered, covered_result.status); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") == null); - try std.testing.expect(std.mem.indexOfScalar(u8, test_renderer.memory.lastWrite(), 'X') != null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") == null); + try std.testing.expect(std.mem.findScalar(u8, test_renderer.memory.lastWrite(), 'X') != null); } test "renderer - commitSplitFooterSnapshot settling phase moves footer downward" { @@ -3515,7 +3515,7 @@ test "FeedBackend - failed ordinary render retries split transition" { output_len += bytes.len; feed.markSpanConsumed(span); } - try std.testing.expect(std.mem.indexOf(u8, output[0..output_len], "\x1b[1T") != null); + try std.testing.expect(std.mem.find(u8, output[0..output_len], "\x1b[1T") != null); } test "FeedBackend - failed frame keeps the published hit grid" { @@ -3608,9 +3608,9 @@ test "FeedBackend - failed frame retries unsent terminal controls" { feed.markSpanConsumed(span); } - try std.testing.expect(std.mem.indexOf(u8, output[0..output_len], "\x1b]12;#123456\x07") != null); - try std.testing.expect(std.mem.indexOf(u8, output[0..output_len], ansi.ANSI.cursorLine) != null); - try std.testing.expect(std.mem.indexOf(u8, output[0..output_len], "\x1b]22;pointer\x07") != null); + try std.testing.expect(std.mem.find(u8, output[0..output_len], "\x1b]12;#123456\x07") != null); + try std.testing.expect(std.mem.find(u8, output[0..output_len], ansi.ANSI.cursorLine) != null); + try std.testing.expect(std.mem.find(u8, output[0..output_len], "\x1b]22;pointer\x07") != null); } test "FeedBackend - failed Sixel frame does not publish an unterminated DCS" { @@ -3954,9 +3954,9 @@ test "renderer scales kitty transmission alpha by placement opacity" { // Translucent placements transmit RGBA with the alpha channel scaled so the // terminal composites the fade; colors are left for kitty to blend. - try std.testing.expect(std.mem.indexOf(u8, output, "f=32") != null); - const transmit_start = std.mem.indexOf(u8, output, "\x1b_Ga=t").?; - const transmit_end = std.mem.indexOfPos(u8, output, transmit_start, "\x1b\\").? + 2; + try std.testing.expect(std.mem.find(u8, output, "f=32") != null); + const transmit_start = std.mem.find(u8, output, "\x1b_Ga=t").?; + const transmit_end = std.mem.findPos(u8, output, transmit_start, "\x1b\\").? + 2; const transmitted = try terminal_image_test.decodeKittyChunks(output[transmit_start..transmit_end]); defer std.testing.allocator.free(transmitted); try std.testing.expectEqual(@as(usize, 4), transmitted.len); @@ -3972,9 +3972,9 @@ test "renderer scales kitty transmission alpha by placement opacity" { next.popOpacity(); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); const second = test_renderer.memory.lastWrite(); - try std.testing.expect(std.mem.indexOf(u8, second, "a=d,d=I") != null); - const second_start = std.mem.indexOf(u8, second, "\x1b_Ga=t").?; - const second_end = std.mem.indexOfPos(u8, second, second_start, "\x1b\\").? + 2; + try std.testing.expect(std.mem.find(u8, second, "a=d,d=I") != null); + const second_start = std.mem.find(u8, second, "\x1b_Ga=t").?; + const second_end = std.mem.findPos(u8, second, second_start, "\x1b\\").? + 2; const retransmitted = try terminal_image_test.decodeKittyChunks(second[second_start..second_end]); defer std.testing.allocator.free(retransmitted); try std.testing.expect(@abs(@as(i16, retransmitted[3]) - 64) <= 1); @@ -4042,7 +4042,7 @@ test "renderer treats fully transparent sixel placements as empty without failin var next = test_renderer.renderer.getNextBuffer(); try std.testing.expect(try next.drawImage(value, value_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .auto)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0") == null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "\x1bP0") == null); try std.testing.expect(!test_renderer.renderer.imageRenderFailed); try std.testing.expect(!test_renderer.renderer.force_full_repaint); @@ -4051,7 +4051,7 @@ test "renderer treats fully transparent sixel placements as empty without failin try std.testing.expect(try next.drawImage(value, value_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .auto)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); try std.testing.expectEqual(@as(u64, 1), test_renderer.renderer.sixelCacheHits); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0") == null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "\x1bP0") == null); try std.testing.expect(!test_renderer.renderer.imageRenderFailed); } @@ -4073,7 +4073,7 @@ test "renderer retransmits sixel placements on a forced full repaint" { var next = test_renderer.renderer.getNextBuffer(); try std.testing.expect(try next.drawImage(value, value_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .auto)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); // A full repaint (failed-frame recovery, palette change) rewrites every // cell, so the unchanged placement's pixels must be transmitted again. @@ -4081,7 +4081,7 @@ test "renderer retransmits sixel placements on a forced full repaint" { next = test_renderer.renderer.getNextBuffer(); try std.testing.expect(try next.drawImage(value, value_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .auto)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); } test "renderer leaves clean text alone when graphics content changes" { @@ -4105,7 +4105,7 @@ test "renderer leaves clean text alone when graphics content changes" { try next.drawText("HELLO", 0, 3, .{ 255, 255, 255, 255 }, null, 0); try std.testing.expect(try next.drawImage(first, first_handle, 0, 0, 2, 2, 0, 0, 0, 0, 1, 1, .auto)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "HELLO") != null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "HELLO") != null); // Replace the image while preserving text and placement geometry. // Kitty swaps the image server side; unchanged text must not be re-emitted @@ -4115,9 +4115,9 @@ test "renderer leaves clean text alone when graphics content changes" { try std.testing.expect(try next.drawImage(second, second_handle, 0, 0, 2, 2, 0, 0, 0, 0, 1, 1, .auto)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); const output = test_renderer.memory.lastWrite(); - try std.testing.expect(std.mem.indexOf(u8, output, "\x1b_Ga=t") != null); - try std.testing.expect(std.mem.indexOf(u8, output, "HELLO") == null); - try std.testing.expect(std.mem.indexOf(u8, output, " ") == null); + try std.testing.expect(std.mem.find(u8, output, "\x1b_Ga=t") != null); + try std.testing.expect(std.mem.find(u8, output, "HELLO") == null); + try std.testing.expect(std.mem.find(u8, output, " ") == null); } test "renderer clears dirty sixel cells as one batched space run" { @@ -4145,10 +4145,10 @@ test "renderer clears dirty sixel cells as one batched space run" { try std.testing.expect(try next.drawImage(second, second_handle, 0, 0, 6, 1, 12, 2, 0, 0, 1, 1, .auto)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); const output = test_renderer.memory.lastWrite(); - const sixel = std.mem.indexOf(u8, output, "\x1bP0;1;0q") orelse return error.TestUnexpectedResult; + const sixel = std.mem.find(u8, output, "\x1bP0;1;0q") orelse return error.TestUnexpectedResult; // The six reserved cells clear with a single cursor move and six spaces; // the only other CUP before the payload positions the Sixel itself. - try std.testing.expect(std.mem.indexOf(u8, output[0..sixel], "\x1b[1;1H ") != null); + try std.testing.expect(std.mem.find(u8, output[0..sixel], "\x1b[1;1H ") != null); try std.testing.expectEqual(@as(usize, 2), std.mem.count(u8, output[0..sixel], "\x1b[1;1H")); } @@ -4183,10 +4183,10 @@ test "renderer downscales large kitty stills to their placement pixel size" { // The transmission carries the downscaled pixels and the placement crops // the full downscaled image. - try std.testing.expect(std.mem.indexOf(u8, output, "a=t,f=24,s=16,v=16") != null); - try std.testing.expect(std.mem.indexOf(u8, output, "x=0,y=0,w=16,h=16,C=1") != null); - const transmit_start = std.mem.indexOf(u8, output, "\x1b_Ga=t").?; - const transmit_end = std.mem.indexOfPos(u8, output, transmit_start, "\x1b[").?; + try std.testing.expect(std.mem.find(u8, output, "a=t,f=24,s=16,v=16") != null); + try std.testing.expect(std.mem.find(u8, output, "x=0,y=0,w=16,h=16,C=1") != null); + const transmit_start = std.mem.find(u8, output, "\x1b_Ga=t").?; + const transmit_end = std.mem.findPos(u8, output, transmit_start, "\x1b[").?; const transmitted = try terminal_image_test.decodeKittyChunks(output[transmit_start..transmit_end]); defer std.testing.allocator.free(transmitted); try std.testing.expectEqual(@as(usize, 16 * 16 * 3), transmitted.len); @@ -4198,8 +4198,8 @@ test "renderer downscales large kitty stills to their placement pixel size" { try std.testing.expect(try next.drawImage(value, value_handle, 0, 0, 2, 2, 20, 20, 0, 0, 64, 64, .auto)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); const second = test_renderer.memory.lastWrite(); - try std.testing.expect(std.mem.indexOf(u8, second, "a=d,d=I") != null); - try std.testing.expect(std.mem.indexOf(u8, second, "a=t,f=24,s=20,v=20") != null); + try std.testing.expect(std.mem.find(u8, second, "a=d,d=I") != null); + try std.testing.expect(std.mem.find(u8, second, "a=t,f=24,s=20,v=20") != null); } test "renderer transmits small kitty images at native size" { @@ -4223,8 +4223,8 @@ test "renderer transmits small kitty images at native size" { try std.testing.expect(try next.drawImage(value, value_handle, 0, 0, 2, 2, 16, 16, 0, 0, 8, 8, .auto)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); const output = test_renderer.memory.lastWrite(); - try std.testing.expect(std.mem.indexOf(u8, output, "a=t,f=24,s=8,v=8") != null); - try std.testing.expect(std.mem.indexOf(u8, output, "x=0,y=0,w=8,h=8,C=1") != null); + try std.testing.expect(std.mem.find(u8, output, "a=t,f=24,s=8,v=8") != null); + try std.testing.expect(std.mem.find(u8, output, "x=0,y=0,w=8,h=8,C=1") != null); } test "renderer transmits only cropped kitty source pixels" { @@ -4255,10 +4255,10 @@ test "renderer transmits only cropped kitty source pixels" { try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); const output = test_renderer.memory.lastWrite(); - try std.testing.expect(std.mem.indexOf(u8, output, "a=t,f=24,s=8,v=8") != null); - try std.testing.expect(std.mem.indexOf(u8, output, "x=0,y=0,w=8,h=8,C=1") != null); - const transmit_start = std.mem.indexOf(u8, output, "\x1b_Ga=t").?; - const transmit_end = std.mem.indexOfPos(u8, output, transmit_start, "\x1b[").?; + try std.testing.expect(std.mem.find(u8, output, "a=t,f=24,s=8,v=8") != null); + try std.testing.expect(std.mem.find(u8, output, "x=0,y=0,w=8,h=8,C=1") != null); + const transmit_start = std.mem.find(u8, output, "\x1b_Ga=t").?; + const transmit_end = std.mem.findPos(u8, output, transmit_start, "\x1b[").?; const transmitted = try terminal_image_test.decodeKittyChunks(output[transmit_start..transmit_end]); defer std.testing.allocator.free(transmitted); try std.testing.expectEqual(@as(usize, 8 * 8 * 3), transmitted.len); @@ -4277,10 +4277,10 @@ test "renderer transmits only cropped kitty source pixels" { try std.testing.expect(try changed.drawImage(value, value_handle, 0, 0, 2, 2, 16, 16, 16, 12, 8, 8, .auto)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); const changed_output = test_renderer.memory.lastWrite(); - try std.testing.expect(std.mem.indexOf(u8, changed_output, "a=d,d=I") != null); - try std.testing.expect(std.mem.indexOf(u8, changed_output, "a=t,f=24,s=8,v=8") != null); - const changed_start = std.mem.indexOf(u8, changed_output, "\x1b_Ga=t").?; - const changed_end = std.mem.indexOfPos(u8, changed_output, changed_start, "\x1b[").?; + try std.testing.expect(std.mem.find(u8, changed_output, "a=d,d=I") != null); + try std.testing.expect(std.mem.find(u8, changed_output, "a=t,f=24,s=8,v=8") != null); + const changed_start = std.mem.find(u8, changed_output, "\x1b_Ga=t").?; + const changed_end = std.mem.findPos(u8, changed_output, changed_start, "\x1b[").?; const changed_transmitted = try terminal_image_test.decodeKittyChunks(changed_output[changed_start..changed_end]); defer std.testing.allocator.free(changed_transmitted); for (0..8) |y| { @@ -4325,7 +4325,7 @@ test "renderer does not publish a frame when image dirty preparation fails" { const retry = test_renderer.renderer.getNextBuffer(); try std.testing.expect(try retry.drawImage(value, value_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .sixel)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); try std.testing.expectEqual(@as(usize, 1), test_renderer.renderer.currentImages.items.len); } @@ -4362,7 +4362,7 @@ test "renderer does not publish a frame when Sixel preparation fails" { const retry = test_renderer.renderer.getNextBuffer(); try std.testing.expect(try retry.drawImage(value, value_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .sixel)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); try std.testing.expectEqual(@as(usize, 1), test_renderer.renderer.currentImages.items.len); } @@ -4398,7 +4398,7 @@ test "renderer does not publish Kitty output when image state staging fails" { const retry = test_renderer.renderer.getNextBuffer(); try std.testing.expect(try retry.drawImage(value, value_handle, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .kitty)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1b_Ga=t") != null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "\x1b_Ga=t") != null); try std.testing.expectEqual(@as(usize, 1), test_renderer.renderer.currentImages.items.len); } @@ -4429,5 +4429,5 @@ test "renderer clears an upper sixel hole when its lower image moves away" { try std.testing.expect(try next.drawImage(lower, lower_handle, 1, 0, 1, 1, 2, 2, 0, 0, 1, 1, .sixel)); try std.testing.expect(try next.drawImage(upper, upper_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .sixel)); try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); - try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1b[1;1H") != null); + try std.testing.expect(std.mem.find(u8, test_renderer.memory.lastWrite(), "\x1b[1;1H") != null); } diff --git a/packages/core/src/zig/tests/terminal-image_test.zig b/packages/core/src/zig/tests/terminal-image_test.zig index 04961d56d7..3e56f0daf4 100644 --- a/packages/core/src/zig/tests/terminal-image_test.zig +++ b/packages/core/src/zig/tests/terminal-image_test.zig @@ -25,9 +25,12 @@ fn quantizeWithAllocator(allocator: std.mem.Allocator) !void { fn writeIndexedWithAllocator(allocator: std.mem.Allocator) !void { const indices = [_]u8{ 0, 1, 1, 0 }; const palette = [_][3]u8{ .{ 255, 0, 0 }, .{ 0, 255, 0 } }; - var output: std.ArrayList(u8) = .empty; - defer output.deinit(allocator); - try terminal_image.writeSixelIndexedPayload(allocator, output.writer(allocator), &indices, &palette, 2, 2); + var output: std.Io.Writer.Allocating = .init(allocator); + defer output.deinit(); + terminal_image.writeSixelIndexedPayload(allocator, &output.writer, &indices, &palette, 2, 2) catch |err| { + if (err == error.WriteFailed) return error.OutOfMemory; + return err; + }; } test "Sixel preparation releases partial allocations on OOM" { @@ -46,7 +49,7 @@ fn parseUnsigned(bytes: []const u8, position: *usize) !usize { } fn decodeSixelIndices(payload: []const u8) !DecodedSixel { - const quote = std.mem.indexOfScalar(u8, payload, '"') orelse return error.InvalidSixel; + const quote = std.mem.findScalar(u8, payload, '"') orelse return error.InvalidSixel; var position = quote + 1; _ = try parseUnsigned(payload, &position); if (position >= payload.len or payload[position] != ';') return error.InvalidSixel; @@ -122,9 +125,9 @@ pub fn decodeKittyChunks(payload: []const u8) ![]u8 { var decoded: std.ArrayList(u8) = .empty; errdefer decoded.deinit(std.testing.allocator); var offset: usize = 0; - while (std.mem.indexOfPos(u8, payload, offset, "\x1b_G")) |start| { - const separator = std.mem.indexOfScalarPos(u8, payload, start + 3, ';') orelse return error.InvalidKittyPayload; - const end = std.mem.indexOfPos(u8, payload, separator + 1, "\x1b\\") orelse return error.InvalidKittyPayload; + while (std.mem.findPos(u8, payload, offset, "\x1b_G")) |start| { + const separator = std.mem.findScalarPos(u8, payload, start + 3, ';') orelse return error.InvalidKittyPayload; + const end = std.mem.findPos(u8, payload, separator + 1, "\x1b\\") orelse return error.InvalidKittyPayload; const encoded = payload[separator + 1 .. end]; const decoded_len = try std.base64.standard.Decoder.calcSizeForSlice(encoded); const destination = try decoded.addManyAsSlice(std.testing.allocator, decoded_len); @@ -143,31 +146,31 @@ test "kitty transmission chunks RGBA payloads and places without cursor movement .pixels = pixels, .metadata = .{ .width = 1025, .height = 1, .has_alpha = 1 }, }; - var output: std.ArrayList(u8) = .empty; - defer output.deinit(std.testing.allocator); - try terminal_image.writeKittyTransmit(output.writer(std.testing.allocator), &value, 7, false); - try terminal_image.writeKittyPlacement(output.writer(std.testing.allocator), 7, 8, 2, 3, 4, 5, 0, 0, 1, 1, -99, false); - try std.testing.expect(std.mem.indexOf(u8, output.items, "i=7,m=1,q=2;") != null); - try std.testing.expect(std.mem.indexOf(u8, output.items, "\x1b_Gm=0,q=2;") != null); - try std.testing.expect(std.mem.indexOf(u8, output.items, "a=p,i=7,p=8,c=4,r=5,x=0,y=0,w=1,h=1,C=1,z=-99") != null); + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); + try terminal_image.writeKittyTransmit(&output.writer, &value, 7, false); + try terminal_image.writeKittyPlacement(&output.writer, 7, 8, 2, 3, 4, 5, 0, 0, 1, 1, -99, false); + try std.testing.expect(std.mem.find(u8, output.written(), "i=7,m=1,q=2;") != null); + try std.testing.expect(std.mem.find(u8, output.written(), "\x1b_Gm=0,q=2;") != null); + try std.testing.expect(std.mem.find(u8, output.written(), "a=p,i=7,p=8,c=4,r=5,x=0,y=0,w=1,h=1,C=1,z=-99") != null); } test "kitty transmission uses RGB only when every pixel is opaque" { const opaque_image = try image.createFromRgba(std.testing.allocator, &[_]u8{ 1, 2, 3, 255 }, 1, 1, 4); defer opaque_image.deinit(); - var rgb: std.ArrayList(u8) = .empty; - defer rgb.deinit(std.testing.allocator); - try terminal_image.writeKittyTransmit(rgb.writer(std.testing.allocator), opaque_image, 1, false); - try std.testing.expect(std.mem.indexOf(u8, rgb.items, "f=24") != null); - try std.testing.expect(std.mem.indexOf(u8, rgb.items, ";AQID\x1b\\") != null); + var rgb: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer rgb.deinit(); + try terminal_image.writeKittyTransmit(&rgb.writer, opaque_image, 1, false); + try std.testing.expect(std.mem.find(u8, rgb.written(), "f=24") != null); + try std.testing.expect(std.mem.find(u8, rgb.written(), ";AQID\x1b\\") != null); const transparent = try image.createFromRgba(std.testing.allocator, &[_]u8{ 1, 2, 3, 4 }, 1, 1, 4); defer transparent.deinit(); - var rgba: std.ArrayList(u8) = .empty; - defer rgba.deinit(std.testing.allocator); - try terminal_image.writeKittyTransmit(rgba.writer(std.testing.allocator), transparent, 1, false); - try std.testing.expect(std.mem.indexOf(u8, rgba.items, "f=32") != null); - try std.testing.expect(std.mem.indexOf(u8, rgba.items, ";AQIDBA==\x1b\\") != null); + var rgba: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer rgba.deinit(); + try terminal_image.writeKittyTransmit(&rgba.writer, transparent, 1, false); + try std.testing.expect(std.mem.find(u8, rgba.written(), "f=32") != null); + try std.testing.expect(std.mem.find(u8, rgba.written(), ";AQIDBA==\x1b\\") != null); } test "kitty transmission sends retained PNG bytes as f=100" { @@ -178,12 +181,12 @@ test "kitty transmission sends retained PNG bytes as f=100" { try std.base64.standard.Decoder.decode(png, encoded); const value = try image.decode(std.testing.allocator, png, .{}); defer value.deinit(); - var output: std.ArrayList(u8) = .empty; - defer output.deinit(std.testing.allocator); + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); - try terminal_image.writeKittyTransmit(output.writer(std.testing.allocator), value, 9, false); - try std.testing.expect(std.mem.indexOf(u8, output.items, "a=t,f=100,i=9") != null); - const decoded = try decodeKittyChunks(output.items); + try terminal_image.writeKittyTransmit(&output.writer, value, 9, false); + try std.testing.expect(std.mem.find(u8, output.written(), "a=t,f=100,i=9") != null); + const decoded = try decodeKittyChunks(output.written()); defer std.testing.allocator.free(decoded); try std.testing.expectEqualSlices(u8, png, decoded); } @@ -198,11 +201,11 @@ test "kitty transmission passes ICC PNG bytes without materializing pixels" { defer value.deinit(); try std.testing.expectEqual(@as(usize, 0), value.pixels.len); - var output: std.ArrayList(u8) = .empty; - defer output.deinit(std.testing.allocator); - try terminal_image.writeKittyTransmit(output.writer(std.testing.allocator), value, 11, false); + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); + try terminal_image.writeKittyTransmit(&output.writer, value, 11, false); - const decoded = try decodeKittyChunks(output.items); + const decoded = try decodeKittyChunks(output.written()); defer std.testing.allocator.free(decoded); try std.testing.expectEqualSlices(u8, png, decoded); try std.testing.expectEqual(@as(usize, 0), value.pixels.len); @@ -218,12 +221,12 @@ test "kitty transmission preserves retained PNG bytes through clone" { defer value.deinit(); const cloned = try value.clone(); defer cloned.deinit(); - var output: std.ArrayList(u8) = .empty; - defer output.deinit(std.testing.allocator); + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); - try terminal_image.writeKittyTransmit(output.writer(std.testing.allocator), cloned, 10, false); - try std.testing.expect(std.mem.indexOf(u8, output.items, "a=t,f=100,i=10") != null); - const decoded = try decodeKittyChunks(output.items); + try terminal_image.writeKittyTransmit(&output.writer, cloned, 10, false); + try std.testing.expect(std.mem.find(u8, output.written(), "a=t,f=100,i=10") != null); + const decoded = try decodeKittyChunks(output.written()); defer std.testing.allocator.free(decoded); try std.testing.expectEqualSlices(u8, png, decoded); } @@ -235,10 +238,10 @@ test "kitty transmission rejects truncated image storage before writing" { .pixels = &pixels, .metadata = .{ .width = 1, .height = 1 }, }; - var output: std.ArrayList(u8) = .empty; - defer output.deinit(std.testing.allocator); - try std.testing.expectError(error.InvalidImageData, terminal_image.writeKittyTransmit(output.writer(std.testing.allocator), &value, 1, false)); - try std.testing.expectEqual(@as(usize, 0), output.items.len); + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); + try std.testing.expectError(error.InvalidImageData, terminal_image.writeKittyTransmit(&output.writer, &value, 1, false)); + try std.testing.expectEqual(@as(usize, 0), output.written().len); } test "kitty RGB transmission preserves pixels across chunk boundaries" { @@ -257,10 +260,10 @@ test "kitty RGB transmission preserves pixels across chunk boundaries" { } const value = try image.createFromRgba(std.testing.allocator, pixels, width, height, width * 4); defer value.deinit(); - var output: std.ArrayList(u8) = .empty; - defer output.deinit(std.testing.allocator); - try terminal_image.writeKittyTransmit(output.writer(std.testing.allocator), value, 1, false); - const decoded = try decodeKittyChunks(output.items); + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); + try terminal_image.writeKittyTransmit(&output.writer, value, 1, false); + const decoded = try decodeKittyChunks(output.written()); defer std.testing.allocator.free(decoded); try std.testing.expectEqualSlices(u8, expected, decoded); } @@ -268,12 +271,12 @@ test "kitty RGB transmission preserves pixels across chunk boundaries" { test "sixel encoding writes palette raster and terminator" { const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); defer value.deinit(); - var output: std.ArrayList(u8) = .empty; - defer output.deinit(std.testing.allocator); - try terminal_image.writeSixel(std.testing.allocator, output.writer(std.testing.allocator), value, false); - try std.testing.expect(std.mem.startsWith(u8, output.items, "\x1bP0;1;0q\"1;1;1;1")); - try std.testing.expect(std.mem.endsWith(u8, output.items, "\x1b\\")); - try std.testing.expect(std.mem.indexOf(u8, output.items, ";2;100;0;0") != null); + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); + try terminal_image.writeSixel(std.testing.allocator, &output.writer, value, false); + try std.testing.expect(std.mem.startsWith(u8, output.written(), "\x1bP0;1;0q\"1;1;1;1")); + try std.testing.expect(std.mem.endsWith(u8, output.written(), "\x1b\\")); + try std.testing.expect(std.mem.find(u8, output.written(), ";2;100;0;0") != null); } test "sixel encoding does not open a DCS when payload generation fails" { @@ -284,20 +287,20 @@ test "sixel encoding does not open a DCS when payload generation fails" { .pixels = pixels, .metadata = .{ .width = 1, .height = 1 }, }; - var output: std.ArrayList(u8) = .empty; - defer output.deinit(std.testing.allocator); - try std.testing.expectError(error.InvalidImageData, terminal_image.writeSixel(std.testing.allocator, output.writer(std.testing.allocator), &value, false)); - try std.testing.expectEqual(@as(usize, 0), output.items.len); + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); + try std.testing.expectError(error.InvalidImageData, terminal_image.writeSixel(std.testing.allocator, &output.writer, &value, false)); + try std.testing.expectEqual(@as(usize, 0), output.written().len); } test "kitty tmux passthrough doubles inner escape bytes" { const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 1, 2, 3, 4 }, 1, 1, 4); defer value.deinit(); - var output: std.ArrayList(u8) = .empty; - defer output.deinit(std.testing.allocator); - try terminal_image.writeKittyTransmit(output.writer(std.testing.allocator), value, 11, true); - try std.testing.expect(std.mem.startsWith(u8, output.items, "\x1bPtmux;\x1b\x1b_G")); - try std.testing.expect(std.mem.endsWith(u8, output.items, "\x1b\x1b\\\x1b\\")); + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); + try terminal_image.writeKittyTransmit(&output.writer, value, 11, true); + try std.testing.expect(std.mem.startsWith(u8, output.written(), "\x1bPtmux;\x1b\x1b_G")); + try std.testing.expect(std.mem.endsWith(u8, output.written(), "\x1b\x1b\\\x1b\\")); } test "sixel encoding uses RLE and omits transparent pixels" { @@ -307,10 +310,10 @@ test "sixel encoding uses RLE and omits transparent pixels" { }; const value = try image.createFromRgba(std.testing.allocator, &pixels, 5, 1, 20); defer value.deinit(); - var output: std.ArrayList(u8) = .empty; - defer output.deinit(std.testing.allocator); - try terminal_image.writeSixel(std.testing.allocator, output.writer(std.testing.allocator), value, false); - try std.testing.expect(std.mem.indexOf(u8, output.items, "!4@") != null); + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); + try terminal_image.writeSixel(std.testing.allocator, &output.writer, value, false); + try std.testing.expect(std.mem.find(u8, output.written(), "!4@") != null); } test "sixel adaptive palette caps at 255 colors deterministically" { @@ -326,14 +329,14 @@ test "sixel adaptive palette caps at 255 colors deterministically" { } const value = try image.createFromRgba(std.testing.allocator, pixels, width, 1, width * 4); defer value.deinit(); - var first: std.ArrayList(u8) = .empty; - defer first.deinit(std.testing.allocator); - var second: std.ArrayList(u8) = .empty; - defer second.deinit(std.testing.allocator); - try terminal_image.writeSixel(std.testing.allocator, first.writer(std.testing.allocator), value, false); - try terminal_image.writeSixel(std.testing.allocator, second.writer(std.testing.allocator), value, false); - try std.testing.expectEqualSlices(u8, first.items, second.items); - try std.testing.expectEqual(@as(usize, 255), std.mem.count(u8, first.items, ";2;")); + var first: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer first.deinit(); + var second: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer second.deinit(); + try terminal_image.writeSixel(std.testing.allocator, &first.writer, value, false); + try terminal_image.writeSixel(std.testing.allocator, &second.writer, value, false); + try std.testing.expectEqualSlices(u8, first.written(), second.written()); + try std.testing.expectEqual(@as(usize, 255), std.mem.count(u8, first.written(), ";2;")); } test "sixel adaptive palette assigns shorter indices to common colors" { @@ -356,25 +359,25 @@ test "sixel adaptive palette assigns shorter indices to common colors" { test "sixel indexed encoding preserves the supplied palette" { const indices = [_]u8{ 0, 1, 0, 1 }; const palette = [_][3]u8{ .{ 12, 34, 56 }, .{ 210, 180, 90 } }; - var output: std.ArrayList(u8) = .empty; - defer output.deinit(std.testing.allocator); - try terminal_image.writeSixelIndexedPayload(std.testing.allocator, output.writer(std.testing.allocator), &indices, &palette, 4, 1); - try std.testing.expect(std.mem.indexOf(u8, output.items, "#0;2;5;13;22") != null); - try std.testing.expect(std.mem.indexOf(u8, output.items, "#1;2;82;71;35") != null); - const decoded = try decodeSixelIndices(output.items); + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); + try terminal_image.writeSixelIndexedPayload(std.testing.allocator, &output.writer, &indices, &palette, 4, 1); + try std.testing.expect(std.mem.find(u8, output.written(), "#0;2;5;13;22") != null); + try std.testing.expect(std.mem.find(u8, output.written(), "#1;2;82;71;35") != null); + const decoded = try decodeSixelIndices(output.written()); defer decoded.deinit(); try std.testing.expectEqualSlices(u8, &indices, decoded.indices); } test "sixel indexed encoding reserves index 255 for transparency" { const palette = [_][3]u8{.{ 0, 0, 0 }} ** 256; - var output: std.ArrayList(u8) = .empty; - defer output.deinit(std.testing.allocator); + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); try std.testing.expectError( error.InvalidImageData, - terminal_image.writeSixelIndexedPayload(std.testing.allocator, output.writer(std.testing.allocator), &[_]u8{255}, &palette, 1, 1), + terminal_image.writeSixelIndexedPayload(std.testing.allocator, &output.writer, &[_]u8{255}, &palette, 1, 1), ); - try std.testing.expectEqual(@as(usize, 0), output.items.len); + try std.testing.expectEqual(@as(usize, 0), output.written().len); } test "sixel indexed scheduling preserves cursor resets bands and transparency" { @@ -392,16 +395,16 @@ test "sixel indexed scheduling preserves cursor resets bands and transparency" { indices[12 * width] = 1; indices[12 * width + 64] = 0; - var output: std.ArrayList(u8) = .empty; - defer output.deinit(std.testing.allocator); - try terminal_image.writeSixelIndexedPayload(std.testing.allocator, output.writer(std.testing.allocator), indices, &palette, width, height); - const decoded = try decodeSixelIndices(output.items); + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); + try terminal_image.writeSixelIndexedPayload(std.testing.allocator, &output.writer, indices, &palette, width, height); + const decoded = try decodeSixelIndices(output.written()); defer decoded.deinit(); try std.testing.expectEqual(width, decoded.width); try std.testing.expectEqual(height, decoded.height); try std.testing.expectEqualSlices(u8, indices, decoded.indices); - try std.testing.expectEqual(@as(usize, 2), std.mem.count(u8, output.items, "-")); - try std.testing.expect(std.mem.indexOfScalar(u8, output.items, '$') != null); + try std.testing.expectEqual(@as(usize, 2), std.mem.count(u8, output.written(), "-")); + try std.testing.expect(std.mem.findScalar(u8, output.written(), '$') != null); } test "sixel indexed scheduling handles cover-sized geometry" { @@ -418,13 +421,13 @@ test "sixel indexed scheduling handles cover-sized geometry" { indices[(height - 1) * width] = 1; indices[height * width - 1] = 0; - var output: std.ArrayList(u8) = .empty; - defer output.deinit(std.testing.allocator); - try terminal_image.writeSixelIndexedPayload(std.testing.allocator, output.writer(std.testing.allocator), indices, &palette, width, height); - const decoded = try decodeSixelIndices(output.items); + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); + try terminal_image.writeSixelIndexedPayload(std.testing.allocator, &output.writer, indices, &palette, width, height); + const decoded = try decodeSixelIndices(output.written()); defer decoded.deinit(); try std.testing.expectEqualSlices(u8, indices, decoded.indices); - try std.testing.expect(output.items.len < 10_000); + try std.testing.expect(output.written().len < 10_000); } test "sixel indexed scheduling handles width and band boundaries" { @@ -443,20 +446,20 @@ test "sixel indexed scheduling handles width and band boundaries" { indices[64] = 0; } - var output: std.ArrayList(u8) = .empty; - defer output.deinit(std.testing.allocator); + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); try terminal_image.writeSixelIndexedPayload( std.testing.allocator, - output.writer(std.testing.allocator), + &output.writer, indices, &palette, @intCast(width), @intCast(height), ); - const decoded = try decodeSixelIndices(output.items); + const decoded = try decodeSixelIndices(output.written()); defer decoded.deinit(); try std.testing.expectEqualSlices(u8, indices, decoded.indices); - try std.testing.expect(std.mem.indexOf(u8, output.items, "$-") == null); + try std.testing.expect(std.mem.find(u8, output.written(), "$-") == null); } } } @@ -532,19 +535,19 @@ test "adaptive Sixel palette quality by color limit" { } test "kitty transmits decoded JPEG images as raw RGB pixels" { - const jpeg = try std.fs.cwd().readFileAlloc(std.testing.allocator, "../tests/fixtures/images/halves.jpg", 1 << 20); + const jpeg = try std.Io.Dir.cwd().readFileAlloc(std.testing.io, "../tests/fixtures/images/halves.jpg", std.testing.allocator, .limited(1 << 20)); defer std.testing.allocator.free(jpeg); const decoded = try image.decode(std.testing.allocator, jpeg, .{}); defer decoded.deinit(); try std.testing.expect(decoded.encoded_png == null); try std.testing.expectEqual(@as(u32, 0), decoded.metadata.has_alpha); - var output: std.ArrayList(u8) = .empty; - defer output.deinit(std.testing.allocator); - try terminal_image.writeKittyTransmit(output.writer(std.testing.allocator), decoded, 21, false); - try std.testing.expect(std.mem.indexOf(u8, output.items, "a=t,f=24,s=16,v=8,i=21") != null); + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); + try terminal_image.writeKittyTransmit(&output.writer, decoded, 21, false); + try std.testing.expect(std.mem.find(u8, output.written(), "a=t,f=24,s=16,v=8,i=21") != null); - const transmitted = try decodeKittyChunks(output.items); + const transmitted = try decodeKittyChunks(output.written()); defer std.testing.allocator.free(transmitted); try std.testing.expectEqual(@as(usize, 16 * 8 * 3), transmitted.len); for (0..16 * 8) |pixel| { @@ -555,51 +558,51 @@ test "kitty transmits decoded JPEG images as raw RGB pixels" { } test "kitty transmits decoded WebP alpha images as raw RGBA pixels" { - const webp = try std.fs.cwd().readFileAlloc(std.testing.allocator, "../tests/fixtures/images/alpha.webp", 1 << 20); + const webp = try std.Io.Dir.cwd().readFileAlloc(std.testing.io, "../tests/fixtures/images/alpha.webp", std.testing.allocator, .limited(1 << 20)); defer std.testing.allocator.free(webp); const decoded = try image.decode(std.testing.allocator, webp, .{}); defer decoded.deinit(); try std.testing.expect(decoded.encoded_png == null); try std.testing.expectEqual(@as(u32, 1), decoded.metadata.has_alpha); - var output: std.ArrayList(u8) = .empty; - defer output.deinit(std.testing.allocator); - try terminal_image.writeKittyTransmit(output.writer(std.testing.allocator), decoded, 22, false); - try std.testing.expect(std.mem.indexOf(u8, output.items, "f=32") != null); + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); + try terminal_image.writeKittyTransmit(&output.writer, decoded, 22, false); + try std.testing.expect(std.mem.find(u8, output.written(), "f=32") != null); - const transmitted = try decodeKittyChunks(output.items); + const transmitted = try decodeKittyChunks(output.written()); defer std.testing.allocator.free(transmitted); try std.testing.expectEqualSlices(u8, decoded.pixels, transmitted); } test "kitty tmux passthrough wraps placement and delete frames" { - var output: std.ArrayList(u8) = .empty; - defer output.deinit(std.testing.allocator); - try terminal_image.writeKittyPlacement(output.writer(std.testing.allocator), 5, 6, 1, 2, 3, 4, 0, 0, 3, 4, -7, true); + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); + try terminal_image.writeKittyPlacement(&output.writer, 5, 6, 1, 2, 3, 4, 0, 0, 3, 4, -7, true); // The cursor move stays outside the passthrough; the graphics frame is wrapped. - try std.testing.expect(std.mem.startsWith(u8, output.items, "\x1b[3;2H\x1bPtmux;\x1b\x1b_G")); - try std.testing.expect(std.mem.endsWith(u8, output.items, "\x1b\x1b\\\x1b\\")); - try std.testing.expect(std.mem.indexOf(u8, output.items, "a=p,i=5,p=6") != null); + try std.testing.expect(std.mem.startsWith(u8, output.written(), "\x1b[3;2H\x1bPtmux;\x1b\x1b_G")); + try std.testing.expect(std.mem.endsWith(u8, output.written(), "\x1b\x1b\\\x1b\\")); + try std.testing.expect(std.mem.find(u8, output.written(), "a=p,i=5,p=6") != null); output.clearRetainingCapacity(); - try terminal_image.writeKittyDelete(output.writer(std.testing.allocator), 5, 6, true, true); - try std.testing.expect(std.mem.startsWith(u8, output.items, "\x1bPtmux;\x1b\x1b_G")); - try std.testing.expect(std.mem.endsWith(u8, output.items, "\x1b\x1b\\\x1b\\")); - try std.testing.expect(std.mem.indexOf(u8, output.items, "a=d,d=I,i=5,p=6") != null); + try terminal_image.writeKittyDelete(&output.writer, 5, 6, true, true); + try std.testing.expect(std.mem.startsWith(u8, output.written(), "\x1bPtmux;\x1b\x1b_G")); + try std.testing.expect(std.mem.endsWith(u8, output.written(), "\x1b\x1b\\\x1b\\")); + try std.testing.expect(std.mem.find(u8, output.written(), "a=d,d=I,i=5,p=6") != null); output.clearRetainingCapacity(); - try terminal_image.writeKittyDelete(output.writer(std.testing.allocator), 9, null, false, true); - try std.testing.expect(std.mem.indexOf(u8, output.items, "a=d,d=i,i=9") != null); - try std.testing.expect(std.mem.indexOf(u8, output.items, "p=") == null); + try terminal_image.writeKittyDelete(&output.writer, 9, null, false, true); + try std.testing.expect(std.mem.find(u8, output.written(), "a=d,d=i,i=9") != null); + try std.testing.expect(std.mem.find(u8, output.written(), "p=") == null); } test "sixel tmux passthrough wraps the framed payload" { - var output: std.ArrayList(u8) = .empty; - defer output.deinit(std.testing.allocator); - try terminal_image.writeSixelFramedPayload(output.writer(std.testing.allocator), "0;1;0qPAYLOAD", true); - try std.testing.expectEqualStrings("\x1bPtmux;\x1b\x1bP0;1;0qPAYLOAD\x1b\x1b\\\x1b\\", output.items); + var output: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer output.deinit(); + try terminal_image.writeSixelFramedPayload(&output.writer, "0;1;0qPAYLOAD", true); + try std.testing.expectEqualStrings("\x1bPtmux;\x1b\x1bP0;1;0qPAYLOAD\x1b\x1b\\\x1b\\", output.written()); output.clearRetainingCapacity(); - try terminal_image.writeSixelFramedPayload(output.writer(std.testing.allocator), "0;1;0qPAYLOAD", false); - try std.testing.expectEqualStrings("\x1bP0;1;0qPAYLOAD\x1b\\", output.items); + try terminal_image.writeSixelFramedPayload(&output.writer, "0;1;0qPAYLOAD", false); + try std.testing.expectEqualStrings("\x1bP0;1;0qPAYLOAD\x1b\\", output.written()); } diff --git a/packages/core/src/zig/tests/terminal_test.zig b/packages/core/src/zig/tests/terminal_test.zig index c802499a9e..166fe93fa3 100644 --- a/packages/core/src/zig/tests/terminal_test.zig +++ b/packages/core/src/zig/tests/terminal_test.zig @@ -217,7 +217,7 @@ test "graphics identity - a new identity replaces only identity-derived capabili } test "graphics identity - malformed XTVERSION cannot reuse environment version" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("TERM_PROGRAM", "foot"); try env.put("TERM_PROGRAM_VERSION", "1.20.2"); @@ -228,7 +228,7 @@ test "graphics identity - malformed XTVERSION cannot reuse environment version" } test "graphics identity - environment name alone is not authoritative" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("TERM_PROGRAM", "kitty"); try env.put("TERM_PROGRAM_VERSION", "0.47.2"); @@ -239,7 +239,7 @@ test "graphics identity - environment name alone is not authoritative" { } test "graphics identity - explicit graphics disable blocks identity and queries" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("OPENTUI_GRAPHICS", "0"); @@ -262,7 +262,7 @@ test "image protocol override - parses every forced protocol" { .{ .value = "blocks", .expected = .blocks }, }; for (cases) |case| { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("OPENTUI_IMAGE_PROTOCOL", case.value); const term = Terminal.init(.{ .env_map = &env }); @@ -271,7 +271,7 @@ test "image protocol override - parses every forced protocol" { } test "image protocol override - ignores invalid value" { - var env = std.process.EnvMap.init(testing.allocator); + var env = std.process.Environ.Map.init(testing.allocator); defer env.deinit(); try env.put("OPENTUI_IMAGE_PROTOCOL", "invalid"); const term = Terminal.init(.{ .env_map = &env }); diff --git a/packages/core/src/zig/tests/test-renderer.zig b/packages/core/src/zig/tests/test-renderer.zig index 8f61831b8f..9610dffe1e 100644 --- a/packages/core/src/zig/tests/test-renderer.zig +++ b/packages/core/src/zig/tests/test-renderer.zig @@ -1,4 +1,6 @@ const std = @import("std"); +const builtin = @import("builtin"); +const io = if (builtin.is_test) std.testing.io else @import("root").io; const renderer = @import("../renderer.zig"); const renderer_output = @import("../renderer-output.zig"); const gp = @import("../grapheme.zig"); @@ -25,8 +27,8 @@ pub const TestMemoryOutput = struct { fn write(ctx: *anyopaque, data: []const u8) void { const self: *TestMemoryOutput = @ptrCast(@alignCast(ctx)); - if (self.thread_safe) self.mutex.lockUncancelable(std.testing.io); - defer if (self.thread_safe) self.mutex.unlock(std.testing.io); + if (self.thread_safe) self.mutex.lockUncancelable(io); + defer if (self.thread_safe) self.mutex.unlock(io); const start = self.bytes.items.len; self.bytes.appendSlice(self.allocator, data) catch @panic("memory output write failed"); From a0644c928db5d37acd6337f64e2a48977594d742 Mon Sep 17 00:00:00 2001 From: Simon Klee Date: Wed, 5 Aug 2026 16:38:56 +0200 Subject: [PATCH 4/4] fix(core): update Ghostty for Zig 0.16 --- packages/core/src/zig/build.zig | 2 +- packages/core/src/zig/build.zig.zon | 4 ++-- packages/core/src/zig/tests/ghostty_vt_test.zig | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/src/zig/build.zig b/packages/core/src/zig/build.zig index 66eb83b0ba..cb67b13091 100644 --- a/packages/core/src/zig/build.zig +++ b/packages/core/src/zig/build.zig @@ -33,7 +33,7 @@ const DEFAULT_MACOS_SDK_PATH = "/Library/Developer/CommandLineTools/SDKs/MacOSX. const LIB_NAME = "opentui"; const ROOT_SOURCE_FILE = "lib.zig"; -const GHOSTTY_VT_VERSION = "0.1.0-dev+74d0c72f"; +const GHOSTTY_VT_VERSION = "0.1.0-dev+b988efcf"; const YOGA_CXX_FLAGS = [_][]const u8{ "-std=c++20", diff --git a/packages/core/src/zig/build.zig.zon b/packages/core/src/zig/build.zig.zon index 25f0da18b9..b3f8f43899 100644 --- a/packages/core/src/zig/build.zig.zon +++ b/packages/core/src/zig/build.zig.zon @@ -9,8 +9,8 @@ .hash = "uucode-0.2.0-ZZjBPuS4VABmt0lQ-jDiKB4afmuZVvZpbX09FP5JrR8N", }, .ghostty = .{ - .url = "https://github.com/ghostty-org/ghostty/archive/74d0c72fd9318ad3ab95bfb56f6c2d995e267e2e.tar.gz", - .hash = "ghostty-1.3.2-dev-5UdBC48HIgU0iYKZzcmjpJ76Ulg3fI9cIH1hPM1JA6NC", + .url = "https://github.com/ghostty-org/ghostty/archive/b988efcfe584e88a3d0330e2c17c386ffa419d72.tar.gz", + .hash = "ghostty-1.3.2-dev-5UdBC9WdJAU1k1nuTdAexwmdzbmW1VTy6KhL_EfHjoTt", .lazy = true, }, .yoga = .{ diff --git a/packages/core/src/zig/tests/ghostty_vt_test.zig b/packages/core/src/zig/tests/ghostty_vt_test.zig index e2256df407..a8c17bd350 100644 --- a/packages/core/src/zig/tests/ghostty_vt_test.zig +++ b/packages/core/src/zig/tests/ghostty_vt_test.zig @@ -3,7 +3,7 @@ const ghostty_vt = @import("../ghostty-vt.zig"); test "Ghostty VT dependency parses and formats terminal output" { const alloc = std.testing.allocator; - var terminal: ghostty_vt.vt.Terminal = try .init(alloc, .{ + var terminal: ghostty_vt.vt.Terminal = try .init(std.testing.io, alloc, .{ .cols = 16, .rows = 4, });