diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-16 23:28:05 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-16 23:29:28 +0300 |
| commit | 4a3302d19a2baa3a33e910e0088c24e780905b90 (patch) | |
| tree | dcaf76fae1c82102d933c9225b17ca7ac5d3a847 /vendor/libghostty-vt/src/terminal/apc/glyph/execute.zig | |
| parent | 550e9f8cc085f428429f5cf2da0bb2dba897e8b8 (diff) | |
chore: update vendored libghostty-vt
refs #276
Diffstat (limited to 'vendor/libghostty-vt/src/terminal/apc/glyph/execute.zig')
| -rw-r--r-- | vendor/libghostty-vt/src/terminal/apc/glyph/execute.zig | 367 |
1 files changed, 367 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/terminal/apc/glyph/execute.zig b/vendor/libghostty-vt/src/terminal/apc/glyph/execute.zig new file mode 100644 index 00000000..059029a6 --- /dev/null +++ b/vendor/libghostty-vt/src/terminal/apc/glyph/execute.zig @@ -0,0 +1,367 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const request = @import("request.zig"); +const response = @import("response.zig"); +const Glossary = @import("Glossary.zig"); +const Request = request.Request; +const Response = response.Response; + +const log = std.log.scoped(.glyph); + +/// Payload formats we support. Hardcoded because the support is +/// fixed. +pub const supported_formats: response.Response.Support.Formats = .{ + .glyf = true, +}; + +/// Execute a Glyph protocol request against the given state. +/// +/// This will never fail, but the response may indiciate an error and +/// the terminal state may not be updated to reflect the command. This will +/// never put the terminal in a corrupt or non-recoverable state. +/// +/// For example, allocation errors can happen, but they're wrapped up in +/// an out of memory response. +/// +/// Query responses only report glossary coverage. Callers that can determine +/// system font coverage must update the returned query response before sending +/// it to the client. +pub fn execute( + alloc: Allocator, + glossary: *Glossary, + req: *const Request, +) ?Response { + log.debug("executing glyph protocol request: {t}", .{req.*}); + return switch (req.*) { + .support => .{ .support = .{ .fmt = supported_formats } }, + .query => |qry| query(glossary, qry), + .register => |reg| register(alloc, glossary, reg), + .clear => |clr| clear(alloc, glossary, clr), + }; +} + +fn query( + glossary: *Glossary, + qry: Request.Query, +) ?Response { + const cp = qry.get(.cp) orelse return null; + return .{ .query = .{ + .cp = cp, + .status = .{ + .glossary = glossary.contains(cp), + }, + } }; +} + +fn register( + alloc: Allocator, + glossary: *Glossary, + reg: Request.Register, +) ?Response { + const reply = reg.get(.reply) orelse .all; + const cp = registerFallible(alloc, glossary, reg) catch |err| return switch (reply) { + .none => null, + .all, .failures => .{ .register = .{ + .cp = reg.get(.cp) orelse 0, + .status = .err, + .reason = switch (err) { + error.OutOfMemory => .{ .other = "out_of_memory" }, + error.OutOfNamespace => .out_of_namespace, + error.PayloadTooLarge => .payload_too_large, + error.MalformedPayload => .malformed_payload, + error.CompositeUnsupported => .composite_unsupported, + error.HintingUnsupported => .hinting_unsupported, + error.InvalidOptions, + error.UnsupportedFormat, + => .malformed_payload, + }, + } }, + }; + + return switch (reply) { + .none, .failures => null, + .all => .{ .register = .{ .cp = cp } }, + }; +} + +fn registerFallible( + alloc: Allocator, + glossary: *Glossary, + reg: Request.Register, +) (Glossary.Entry.InitError || Glossary.RegisterError)!u21 { + const cp = reg.get(.cp) orelse + return error.MalformedPayload; + + var entry = try Glossary.Entry.init(alloc, reg); + errdefer entry.deinit(alloc); + + try glossary.register(alloc, cp, entry); + return cp; +} + +fn clear( + alloc: Allocator, + glossary: *Glossary, + clr: Request.Clear, +) ?Response { + if (clr.get(.cp)) |cp| { + glossary.delete(alloc, cp) catch |err| return .{ .clear = .{ + .status = .err, + .reason = switch (err) { + error.OutOfNamespace => "out_of_namespace", + }, + } }; + } else if (clr.has(.cp)) { + return .{ .clear = .{ + .status = .err, + .reason = "malformed_payload", + } }; + } else { + glossary.clearAndFree(alloc); + } + + return .{ .clear = .{} }; +} + +fn testParse(alloc: Allocator, data: []const u8) !Request { + var parser = request.CommandParser.init(alloc, 1024 * 1024); + defer parser.deinit(); + for (data) |byte| try parser.feed(byte); + return try parser.complete(alloc); +} + +fn testExecute(alloc: Allocator, glossary: *Glossary, req: *const Request) ?Response { + return execute(alloc, glossary, req); +} + +test "execute register stores glyph and returns success" { + const testing = std.testing; + const alloc = testing.allocator; + + var glossary: Glossary = .empty; + defer glossary.deinit(alloc); + + var req = try testParse(alloc, "r;cp=e0a0;AAAAAAAAAAAAAA=="); + defer req.deinit(alloc); + + try testing.expectEqual(Response{ + .register = .{ .cp = 0xE0A0 }, + }, testExecute(alloc, &glossary, &req).?); + try testing.expect(glossary.contains(0xE0A0)); +} + +test "execute register reply failures suppresses success" { + const testing = std.testing; + const alloc = testing.allocator; + + var glossary: Glossary = .empty; + defer glossary.deinit(alloc); + + var req = try testParse(alloc, "r;cp=e0a0;reply=2;AAAAAAAAAAAAAA=="); + defer req.deinit(alloc); + + try testing.expect(testExecute(alloc, &glossary, &req) == null); + try testing.expect(glossary.contains(0xE0A0)); +} + +test "execute register reply none suppresses failure" { + const testing = std.testing; + const alloc = testing.allocator; + + var glossary: Glossary = .empty; + defer glossary.deinit(alloc); + + var req = try testParse(alloc, "r;cp=41;reply=0;%%%not-base64%%%"); + defer req.deinit(alloc); + + try testing.expect(testExecute(alloc, &glossary, &req) == null); + try testing.expect(!glossary.contains('A')); +} + +test "execute register rejects non-PUA" { + const testing = std.testing; + const alloc = testing.allocator; + + var glossary: Glossary = .empty; + defer glossary.deinit(alloc); + + var req = try testParse(alloc, "r;cp=41;AAAAAAAAAAAAAA=="); + defer req.deinit(alloc); + + try testing.expectEqual(Response{ + .register = .{ + .cp = 'A', + .status = .err, + .reason = .out_of_namespace, + }, + }, testExecute(alloc, &glossary, &req).?); + try testing.expect(!glossary.contains('A')); +} + +test "execute register reports malformed payload" { + const testing = std.testing; + const alloc = testing.allocator; + + var glossary: Glossary = .empty; + defer glossary.deinit(alloc); + + var req = try testParse(alloc, "r;cp=e0a0;%%%not-base64%%%"); + defer req.deinit(alloc); + + try testing.expectEqual(Response{ + .register = .{ + .cp = 0xE0A0, + .status = .err, + .reason = .malformed_payload, + }, + }, testExecute(alloc, &glossary, &req).?); + try testing.expect(!glossary.contains(0xE0A0)); +} + +test "execute clear removes all glyphs" { + const testing = std.testing; + const alloc = testing.allocator; + + var glossary: Glossary = .empty; + defer glossary.deinit(alloc); + + var reg1 = try testParse(alloc, "r;cp=e0a0;AAAAAAAAAAAAAA=="); + defer reg1.deinit(alloc); + _ = testExecute(alloc, &glossary, ®1); + + var reg2 = try testParse(alloc, "r;cp=e0a1;AAAAAAAAAAAAAA=="); + defer reg2.deinit(alloc); + _ = testExecute(alloc, &glossary, ®2); + + var req = try testParse(alloc, "c"); + defer req.deinit(alloc); + + try testing.expectEqual(Response{ .clear = .{} }, testExecute(alloc, &glossary, &req).?); + try testing.expect(!glossary.contains(0xE0A0)); + try testing.expect(!glossary.contains(0xE0A1)); +} + +test "execute clear removes one glyph" { + const testing = std.testing; + const alloc = testing.allocator; + + var glossary: Glossary = .empty; + defer glossary.deinit(alloc); + + var reg1 = try testParse(alloc, "r;cp=e0a0;AAAAAAAAAAAAAA=="); + defer reg1.deinit(alloc); + _ = testExecute(alloc, &glossary, ®1); + + var reg2 = try testParse(alloc, "r;cp=e0a1;AAAAAAAAAAAAAA=="); + defer reg2.deinit(alloc); + _ = testExecute(alloc, &glossary, ®2); + + var req = try testParse(alloc, "c;cp=e0a0"); + defer req.deinit(alloc); + + try testing.expectEqual(Response{ .clear = .{} }, testExecute(alloc, &glossary, &req).?); + try testing.expect(!glossary.contains(0xE0A0)); + try testing.expect(glossary.contains(0xE0A1)); +} + +test "execute clear rejects non-PUA" { + const testing = std.testing; + const alloc = testing.allocator; + + var glossary: Glossary = .empty; + defer glossary.deinit(alloc); + + var req = try testParse(alloc, "c;cp=41"); + defer req.deinit(alloc); + + try testing.expectEqual(Response{ + .clear = .{ + .status = .err, + .reason = "out_of_namespace", + }, + }, testExecute(alloc, &glossary, &req).?); +} + +test "execute clear rejects malformed cp without clearing glossary" { + const testing = std.testing; + const alloc = testing.allocator; + + var glossary: Glossary = .empty; + defer glossary.deinit(alloc); + + var reg1 = try testParse(alloc, "r;cp=e0a0;AAAAAAAAAAAAAA=="); + defer reg1.deinit(alloc); + _ = testExecute(alloc, &glossary, ®1); + + var reg2 = try testParse(alloc, "r;cp=e0a1;AAAAAAAAAAAAAA=="); + defer reg2.deinit(alloc); + _ = testExecute(alloc, &glossary, ®2); + + for ([_][]const u8{ "c;cp=zz", "c;cp=", "c;cp=200000" }) |data| { + var req = try testParse(alloc, data); + defer req.deinit(alloc); + + try testing.expectEqual(Response{ + .clear = .{ + .status = .err, + .reason = "malformed_payload", + }, + }, testExecute(alloc, &glossary, &req).?); + try testing.expect(glossary.contains(0xE0A0)); + try testing.expect(glossary.contains(0xE0A1)); + } +} + +test "execute query reports no coverage" { + const testing = std.testing; + const alloc = testing.allocator; + + var glossary: Glossary = .empty; + defer glossary.deinit(alloc); + + var req = try testParse(alloc, "q;cp=e0a0"); + defer req.deinit(alloc); + + try testing.expectEqual(Response{ + .query = .{ + .cp = 0xE0A0, + .status = .{}, + }, + }, testExecute(alloc, &glossary, &req).?); +} + +test "execute query reports glossary coverage" { + const testing = std.testing; + const alloc = testing.allocator; + + var glossary: Glossary = .empty; + defer glossary.deinit(alloc); + + var reg = try testParse(alloc, "r;cp=e0a0;AAAAAAAAAAAAAA=="); + defer reg.deinit(alloc); + _ = testExecute(alloc, &glossary, ®); + + var req = try testParse(alloc, "q;cp=e0a0"); + defer req.deinit(alloc); + + try testing.expectEqual(Response{ + .query = .{ + .cp = 0xE0A0, + .status = .{ .glossary = true }, + }, + }, testExecute(alloc, &glossary, &req).?); +} + +test "execute query without cp returns no response" { + const testing = std.testing; + const alloc = testing.allocator; + + var glossary: Glossary = .empty; + defer glossary.deinit(alloc); + + var req = try testParse(alloc, "q;foo=bar"); + defer req.deinit(alloc); + + try testing.expect(testExecute(alloc, &glossary, &req) == null); +} |
