diff --git a/src/terminal/apc/glyph/Glossary.zig b/src/terminal/apc/glyph/Glossary.zig index f2b102d8b..316d36ae6 100644 --- a/src/terminal/apc/glyph/Glossary.zig +++ b/src/terminal/apc/glyph/Glossary.zig @@ -24,6 +24,9 @@ pub const empty: Glossary = .{ .entries = .empty }; /// Errors that can occur while registering a glossary entry. pub const RegisterError = Allocator.Error || error{OutOfNamespace}; +/// Errors that can occur while clearing glossary entries. +pub const ClearError = error{OutOfNamespace}; + /// The set of entries in the glossary keyed by the codepoint. /// /// The array hash map preserves insertion order and has O(N) @@ -87,7 +90,7 @@ pub fn delete( self: *Glossary, alloc: Allocator, cp: u21, -) error{OutOfNamespace}!void { +) ClearError!void { if (!isPrivateUse(cp)) return error.OutOfNamespace; const kv = self.entries.fetchOrderedRemove(cp) orelse return; var entry = kv.value; diff --git a/src/terminal/apc/glyph/execute.zig b/src/terminal/apc/glyph/execute.zig index 6a695383b..14f30686e 100644 --- a/src/terminal/apc/glyph/execute.zig +++ b/src/terminal/apc/glyph/execute.zig @@ -32,7 +32,8 @@ pub fn execute( return switch (req.*) { .support => .{ .support = .{ .fmt = supported_formats } }, .register => |reg| register(alloc, glossary, reg), - .query, .clear => @panic("TODO"), + .clear => |clr| clear(alloc, glossary, clr), + .query => @panic("TODO"), }; } @@ -82,6 +83,25 @@ fn registerFallible( 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 { + glossary.clearAndFree(alloc); + } + + return .{ .clear = .{} }; +} + fn testParse(alloc: Allocator, data: []const u8) !Request { var parser = request.CommandParser.init(alloc, 1024 * 1024); defer parser.deinit(); @@ -172,3 +192,67 @@ test "execute register reports malformed payload" { }, execute(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); + _ = execute(alloc, &glossary, ®1); + + var reg2 = try testParse(alloc, "r;cp=e0a1;AAAAAAAAAAAAAA=="); + defer reg2.deinit(alloc); + _ = execute(alloc, &glossary, ®2); + + var req = try testParse(alloc, "c"); + defer req.deinit(alloc); + + try testing.expectEqual(Response{ .clear = .{} }, execute(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); + _ = execute(alloc, &glossary, ®1); + + var reg2 = try testParse(alloc, "r;cp=e0a1;AAAAAAAAAAAAAA=="); + defer reg2.deinit(alloc); + _ = execute(alloc, &glossary, ®2); + + var req = try testParse(alloc, "c;cp=e0a0"); + defer req.deinit(alloc); + + try testing.expectEqual(Response{ .clear = .{} }, execute(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", + }, + }, execute(alloc, &glossary, &req).?); +}