diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-09-10 21:51:57 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-10 21:51:57 +0300 |
| commit | 425c86179791cd32e2d4ce0ae7f269940b4b0663 (patch) | |
| tree | e52c4383a6d69216cbe2a32638bf90841d7e2189 /vendor/libghostty-vt/src/lib/compat | |
| parent | 90e947a6e26e02f10ec7f1694c4eb5b78a86406b (diff) | |
chore: upgrade libghostty and preserve terminal behavior (#3906)
Diffstat (limited to 'vendor/libghostty-vt/src/lib/compat')
| -rw-r--r-- | vendor/libghostty-vt/src/lib/compat/README.md | 37 | ||||
| -rw-r--r-- | vendor/libghostty-vt/src/lib/compat/fd.zig | 83 | ||||
| -rw-r--r-- | vendor/libghostty-vt/src/lib/compat/file.zig | 56 | ||||
| -rw-r--r-- | vendor/libghostty-vt/src/lib/compat/reader.zig | 64 | ||||
| -rw-r--r-- | vendor/libghostty-vt/src/lib/compat/testing.zig | 19 | ||||
| -rw-r--r-- | vendor/libghostty-vt/src/lib/compat/thread.zig | 75 |
6 files changed, 334 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/lib/compat/README.md b/vendor/libghostty-vt/src/lib/compat/README.md new file mode 100644 index 00000000..22f58c10 --- /dev/null +++ b/vendor/libghostty-vt/src/lib/compat/README.md @@ -0,0 +1,37 @@ +# Zig Compatibility Libraries + +The following directory contains code that either used to exist in the Zig +standard library, or currently exists in Zig master that has been deemed +useful to have ahead of a full release. + +Code located here should likely be eventually phased out, or moved to a more +logical location if it's decided it would be useful to keep. + +## Licenses + +Zig code taken from the standard library is used under the terms of the MIT +license. The license body follows. + +``` +The MIT License (Expat) + +Copyright (c) Zig contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +``` diff --git a/vendor/libghostty-vt/src/lib/compat/fd.zig b/vendor/libghostty-vt/src/lib/compat/fd.zig new file mode 100644 index 00000000..3bef7414 --- /dev/null +++ b/vendor/libghostty-vt/src/lib/compat/fd.zig @@ -0,0 +1,83 @@ +//! Code taken from 0.15.2 and `std.fs.file`. See README.md for license and +//! details. +const std = @import("std"); + +pub const PipeError = error{ + SystemFdQuotaExceeded, + ProcessFdQuotaExceeded, +} || std.posix.UnexpectedError; + +pub fn close(fd: std.posix.fd_t) void { + switch (std.posix.errno(std.posix.system.close(fd))) { + .BADF => unreachable, // Always a race condition + else => {}, // Includes EINTR, see std.posix source in 0.15.2 + } +} + +pub fn pipe() PipeError![2]std.posix.fd_t { + var fds: [2]std.posix.fd_t = undefined; + switch (std.posix.errno(std.posix.system.pipe(&fds))) { + .SUCCESS => return fds, + .INVAL => unreachable, // Invalid parameters to pipe() + .FAULT => unreachable, // Invalid fds pointer + .NFILE => return error.SystemFdQuotaExceeded, + .MFILE => return error.ProcessFdQuotaExceeded, + else => |err| return std.posix.unexpectedErrno(err), + } +} + +pub fn pipe2(flags: std.posix.O) PipeError![2]std.posix.fd_t { + if (@TypeOf(std.posix.system.pipe2) != void) { + var fds: [2]std.posix.fd_t = undefined; + switch (std.posix.errno(std.posix.system.pipe2(&fds, flags))) { + .SUCCESS => return fds, + .INVAL => unreachable, // Invalid flags + .FAULT => unreachable, // Invalid fds pointer + .NFILE => return error.SystemFdQuotaExceeded, + .MFILE => return error.ProcessFdQuotaExceeded, + else => |err| return std.posix.unexpectedErrno(err), + } + } + + const fds: [2]std.posix.fd_t = try pipe(); + errdefer { + close(fds[0]); + close(fds[1]); + } + + // https://github.com/ziglang/zig/issues/18882 + if (@as(u32, @bitCast(flags)) == 0) + return fds; + + // CLOEXEC is special, it's a file descriptor flag and must be set using + // F.SETFD. + if (flags.CLOEXEC) { + for (fds) |fd| { + switch (std.posix.errno(std.posix.system.fcntl(fd, std.posix.F.SETFD, @as(u32, std.posix.FD_CLOEXEC)))) { + .SUCCESS => {}, + .INVAL => unreachable, // Invalid flags + .BADF => unreachable, // Always a race condition + else => |err| return std.posix.unexpectedErrno(err), + } + } + } + + const new_flags: u32 = f: { + var new_flags = flags; + new_flags.CLOEXEC = false; + break :f @bitCast(new_flags); + }; + // Set every other flag affecting the file status using F.SETFL. + if (new_flags != 0) { + for (fds) |fd| { + switch (std.posix.errno(std.posix.system.fcntl(fd, std.posix.F.SETFL, new_flags))) { + .SUCCESS => {}, + .INVAL => unreachable, // Invalid flags + .BADF => unreachable, // Always a race condition + else => |err| return std.posix.unexpectedErrno(err), + } + } + } + + return fds; +} diff --git a/vendor/libghostty-vt/src/lib/compat/file.zig b/vendor/libghostty-vt/src/lib/compat/file.zig new file mode 100644 index 00000000..efb31a52 --- /dev/null +++ b/vendor/libghostty-vt/src/lib/compat/file.zig @@ -0,0 +1,56 @@ +//! Code taken from 0.15.2 and `std.fs.file`. See README.md for license and +//! details. +const builtin = @import("builtin"); +const std = @import("std"); +const global = @import("../../global.zig"); + +pub const ReadToEndAllocError = error{FileTooBig} || + std.Io.File.ReadStreamingError || + std.mem.Allocator.Error; + +/// Read the file from its current position through end-of-stream, returning +/// `error.FileTooBig` if the result exceeds `max_bytes`. +/// +/// Caller owns the memory. +pub fn readToEndAlloc(file: std.Io.File, alloc: std.mem.Allocator, max_bytes: usize) ReadToEndAllocError![]u8 { + var read_buf: [4096]u8 = undefined; + var result: std.ArrayList(u8) = .empty; + defer result.deinit(alloc); + + while (true) { + const n = file.readStreaming(global.io(), &.{&read_buf}) catch |err| switch (err) { + error.EndOfStream => break, + else => |e| return e, + }; + if (n == 0) continue; + if (n > max_bytes - result.items.len) return error.FileTooBig; + try result.appendSlice(alloc, read_buf[0..n]); + } + + return result.toOwnedSlice(alloc); +} + +test "readToEndAlloc reads through EOF and permits exact limit" { + const testing = std.testing; + const contents = "hello, world"; + + var tmp_dir = testing.tmpDir(.{}); + defer tmp_dir.cleanup(); + try tmp_dir.dir.writeFile(testing.io, .{ + .sub_path = "data", + .data = contents, + }); + + const file = try tmp_dir.dir.openFile(testing.io, "data", .{}); + defer file.close(testing.io); + const result = try readToEndAlloc(file, testing.allocator, contents.len); + defer testing.allocator.free(result); + try testing.expectEqualStrings(contents, result); + + const too_big_file = try tmp_dir.dir.openFile(testing.io, "data", .{}); + defer too_big_file.close(testing.io); + try testing.expectError( + error.FileTooBig, + readToEndAlloc(too_big_file, testing.allocator, contents.len - 1), + ); +} diff --git a/vendor/libghostty-vt/src/lib/compat/reader.zig b/vendor/libghostty-vt/src/lib/compat/reader.zig new file mode 100644 index 00000000..28813440 --- /dev/null +++ b/vendor/libghostty-vt/src/lib/compat/reader.zig @@ -0,0 +1,64 @@ +//! Code taken from older reader implementations, still may be useful to keep +//! around in general. See README.md for license and details. +const std = @import("std"); + +/// Pulled from old stdlib as this function has been removed. +pub fn readSkipUntilDelimiterOrEof(reader: *std.Io.Reader, delimiter: u8) std.Io.Reader.Error!void { + while (true) { + const byte = readByte(reader) catch |err| switch (err) { + error.EndOfStream => return, + else => |e| return e, + }; + if (byte == delimiter) return; + } +} + +/// Pulled from old stdlib as this function has been removed. +pub fn readStructEndian( + reader: *std.Io.Reader, + comptime T: type, + endian: std.builtin.Endian, +) std.Io.Reader.Error!T { + var result: [1]T = undefined; + try reader.readSliceEndian(T, &result, endian); + return result[0]; +} + +/// Pulled from old stdlib as this function has been removed. +pub fn readerInt(reader: *std.Io.Reader, comptime T: type, endian: std.builtin.Endian) std.Io.Reader.Error!T { + const bytes = try readBytesNoEof(reader, @divExact(@typeInfo(T).int.bits, 8)); + return std.mem.readInt(T, &bytes, endian); +} + +/// Pulled from old stdlib as this function has been removed. +pub fn readByteSigned(reader: *std.Io.Reader) std.Io.Reader.Error!i8 { + return @as(i8, @bitCast(try readByte(reader))); +} + +/// Pulled from old stdlib as this function has been removed. +pub fn readByte(reader: *std.Io.Reader) std.Io.Reader.Error!u8 { + var result: [1]u8 = undefined; + try reader.readSliceAll(&result); + return result[0]; +} + +/// Pulled from old stdlib as this function has been removed. +pub fn readSkipBytes(reader: *std.Io.Reader, num_bytes: u64) std.Io.Reader.Error!void { + const buf_size = 512; + + var buf: [buf_size]u8 = undefined; + var remaining = num_bytes; + + while (remaining > 0) { + const amt = @min(remaining, buf_size); + try reader.readSliceAll(buf[0..amt]); + remaining -= amt; + } +} + +/// Pulled from old stdlib as this function has been removed. +fn readBytesNoEof(reader: *std.Io.Reader, comptime num_bytes: usize) std.Io.Reader.Error![num_bytes]u8 { + var bytes: [num_bytes]u8 = undefined; + try reader.readSliceAll(&bytes); + return bytes; +} diff --git a/vendor/libghostty-vt/src/lib/compat/testing.zig b/vendor/libghostty-vt/src/lib/compat/testing.zig new file mode 100644 index 00000000..e826eb40 --- /dev/null +++ b/vendor/libghostty-vt/src/lib/compat/testing.zig @@ -0,0 +1,19 @@ +//! Code taken from 0.15.2 `std.testing`. See README.md for license and +//! details. +const builtin = @import("builtin"); +const std = @import("std"); + +/// Given a type, recursively references all the declarations inside, so that the semantic analyzer sees them. +/// For deep types, you may use `@setEvalBranchQuota`. +pub fn refAllDeclsRecursive(comptime T: type) void { + if (!builtin.is_test) return; + inline for (comptime std.meta.declarations(T)) |decl| { + if (@TypeOf(@field(T, decl.name)) == type) { + switch (@typeInfo(@field(T, decl.name))) { + .@"struct", .@"enum", .@"union", .@"opaque" => refAllDeclsRecursive(@field(T, decl.name)), + else => {}, + } + } + _ = &@field(T, decl.name); + } +} diff --git a/vendor/libghostty-vt/src/lib/compat/thread.zig b/vendor/libghostty-vt/src/lib/compat/thread.zig new file mode 100644 index 00000000..890c02f0 --- /dev/null +++ b/vendor/libghostty-vt/src/lib/compat/thread.zig @@ -0,0 +1,75 @@ +//! Code taken from 0.17.0 `std.Io.Condition`. See README.md for license and +//! details. +//! +//! FIXME: Should be able to remove after 0.17.0, see: +//! https://codeberg.org/ziglang/zig/pulls/31278 +const builtin = @import("builtin"); +const std = @import("std"); +const assert = @import("../../quirks.zig").inlineAssert; + +pub const WaitTimeoutError = std.Io.Cancelable || std.Io.Timeout.Error; + +/// Blocks until the condition is signaled, canceled, or the provided +/// timeout expires. +/// +/// See also: +/// * `wait` +/// * `waitUncancelable` +pub fn waitTimeout( + cond: *std.Io.Condition, + io: std.Io, + mutex: *std.Io.Mutex, + timeout: std.Io.Timeout, +) WaitTimeoutError!void { + const deadline = timeout.toDeadline(io); + + var epoch = cond.epoch.load(.acquire); // `.acquire` to ensure ordered before state load + + { + const prev_state = cond.state.fetchAdd(.{ .waiters = 1, .signals = 0 }, .monotonic); + assert(prev_state.waiters < std.math.maxInt(u16)); // overflow caused by too many waiters + } + + mutex.unlock(io); + defer mutex.lockUncancelable(io); + + while (true) { + const result = io.futexWaitTimeout(u32, &cond.epoch.raw, epoch, deadline); + + epoch = cond.epoch.load(.acquire); // `.acquire` to ensure ordered before `state` load + + // Even on error, try to consume a pending signal first. Otherwise a race might + // cause a signal to get stuck in the state with no corresponding waiter. + { + var prev_state = cond.state.load(.monotonic); + while (prev_state.signals > 0) { + prev_state = cond.state.cmpxchgWeak(prev_state, .{ + .waiters = prev_state.waiters - 1, + .signals = prev_state.signals - 1, + }, .acquire, .monotonic) orelse { + // We successfully consumed a signal. + return; + }; + } + } + + // There are no more signals available; this was a spurious wakeup or an error. If it + // was an error, we will remove ourselves as a waiter and return that error. If a + // timeout was specified and the deadline has passed, we remove ourselves as a waiter + // and return `error.Timeout`. Otherwise, we'll loop back to the futex wait. + result catch |err| { + const prev_state = cond.state.fetchSub(.{ .waiters = 1, .signals = 0 }, .monotonic); + assert(prev_state.waiters > 0); // underflow caused by illegal state + return err; + }; + switch (deadline) { + .none => {}, + .deadline => |d| if (d.untilNow(io).raw.nanoseconds >= 0) { + const prev_state = cond.state.fetchSub(.{ .waiters = 1, .signals = 0 }, .monotonic); + assert(prev_state.waiters > 0); // underflow caused by illegal state + return error.Timeout; + }, + .duration => unreachable, + } + } +} |
