mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
kitty images: add delete by range operations (#5957)
Fixes #5937 Implement [deleting Kitty image ranges](https://sw.kovidgoyal.net/kitty/graphics-protocol/#deleting-images).
This commit is contained in:
@ -801,6 +801,13 @@ pub const Delete = union(enum) {
|
|||||||
z: i32 = 0, // z
|
z: i32 = 0, // z
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// r/R
|
||||||
|
range: struct {
|
||||||
|
delete: bool = false, // uppercase
|
||||||
|
first: u32 = 0, // x
|
||||||
|
last: u32 = 0, // y
|
||||||
|
},
|
||||||
|
|
||||||
// x/X
|
// x/X
|
||||||
column: struct {
|
column: struct {
|
||||||
delete: bool = false, // uppercase
|
delete: bool = false, // uppercase
|
||||||
@ -885,6 +892,19 @@ pub const Delete = union(enum) {
|
|||||||
break :blk result;
|
break :blk result;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'r', 'R' => blk: {
|
||||||
|
const x = kv.get('x') orelse return error.InvalidFormat;
|
||||||
|
const y = kv.get('y') orelse return error.InvalidFormat;
|
||||||
|
if (x > y) return error.InvalidFormat;
|
||||||
|
break :blk .{
|
||||||
|
.range = .{
|
||||||
|
.delete = what == 'R',
|
||||||
|
.first = x,
|
||||||
|
.last = y,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
'x', 'X' => blk: {
|
'x', 'X' => blk: {
|
||||||
var result: Delete = .{ .column = .{ .delete = what == 'X' } };
|
var result: Delete = .{ .column = .{ .delete = what == 'X' } };
|
||||||
if (kv.get('x')) |v| {
|
if (kv.get('x')) |v| {
|
||||||
@ -1197,3 +1217,76 @@ test "response: encode with image ID and number" {
|
|||||||
try r.encode(fbs.writer());
|
try r.encode(fbs.writer());
|
||||||
try testing.expectEqualStrings("\x1b_Gi=12,I=4;OK\x1b\\", fbs.getWritten());
|
try testing.expectEqualStrings("\x1b_Gi=12,I=4;OK\x1b\\", fbs.getWritten());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "delete range command 1" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var p = Parser.init(alloc);
|
||||||
|
defer p.deinit();
|
||||||
|
|
||||||
|
const input = "a=d,d=r,x=3,y=4";
|
||||||
|
for (input) |c| try p.feed(c);
|
||||||
|
const command = try p.complete();
|
||||||
|
defer command.deinit(alloc);
|
||||||
|
|
||||||
|
try testing.expect(command.control == .delete);
|
||||||
|
const v = command.control.delete;
|
||||||
|
try testing.expect(v == .range);
|
||||||
|
const range = v.range;
|
||||||
|
try testing.expect(!range.delete);
|
||||||
|
try testing.expectEqual(@as(u32, 3), range.first);
|
||||||
|
try testing.expectEqual(@as(u32, 4), range.last);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "delete range command 2" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var p = Parser.init(alloc);
|
||||||
|
defer p.deinit();
|
||||||
|
|
||||||
|
const input = "a=d,d=R,x=5,y=11";
|
||||||
|
for (input) |c| try p.feed(c);
|
||||||
|
const command = try p.complete();
|
||||||
|
defer command.deinit(alloc);
|
||||||
|
|
||||||
|
try testing.expect(command.control == .delete);
|
||||||
|
const v = command.control.delete;
|
||||||
|
try testing.expect(v == .range);
|
||||||
|
const range = v.range;
|
||||||
|
try testing.expect(range.delete);
|
||||||
|
try testing.expectEqual(@as(u32, 5), range.first);
|
||||||
|
try testing.expectEqual(@as(u32, 11), range.last);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "delete range command 3" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var p = Parser.init(alloc);
|
||||||
|
defer p.deinit();
|
||||||
|
|
||||||
|
const input = "a=d,d=R,x=5,y=4";
|
||||||
|
for (input) |c| try p.feed(c);
|
||||||
|
try testing.expectError(error.InvalidFormat, p.complete());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "delete range command 4" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var p = Parser.init(alloc);
|
||||||
|
defer p.deinit();
|
||||||
|
|
||||||
|
const input = "a=d,d=R,x=5";
|
||||||
|
for (input) |c| try p.feed(c);
|
||||||
|
try testing.expectError(error.InvalidFormat, p.complete());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "delete range command 5" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var p = Parser.init(alloc);
|
||||||
|
defer p.deinit();
|
||||||
|
|
||||||
|
const input = "a=d,d=R,y=5";
|
||||||
|
for (input) |c| try p.feed(c);
|
||||||
|
try testing.expectError(error.InvalidFormat, p.complete());
|
||||||
|
}
|
||||||
|
@ -397,6 +397,31 @@ pub const ImageStorage = struct {
|
|||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
.range => |v| range: {
|
||||||
|
if (v.first <= 0 or v.last <= 0) {
|
||||||
|
log.warn("delete range values must be greater than zero", .{});
|
||||||
|
break :range;
|
||||||
|
}
|
||||||
|
if (v.first > v.last) {
|
||||||
|
log.warn("delete range 'x' ({}) must be less than or equal to 'y' ({})", .{ v.first, v.last });
|
||||||
|
break :range;
|
||||||
|
}
|
||||||
|
|
||||||
|
var it = self.placements.iterator();
|
||||||
|
while (it.next()) |entry| {
|
||||||
|
if (entry.key_ptr.image_id >= v.first or entry.key_ptr.image_id <= v.last) {
|
||||||
|
const image_id = entry.key_ptr.image_id;
|
||||||
|
log.warn("delete range: {}", .{image_id});
|
||||||
|
entry.value_ptr.deinit(&t.screen);
|
||||||
|
self.placements.removeByPtr(entry.key_ptr);
|
||||||
|
if (v.delete) self.deleteIfUnused(alloc, image_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark dirty to force redraw
|
||||||
|
self.dirty = true;
|
||||||
|
},
|
||||||
|
|
||||||
// We don't support animation frames yet so they are successfully
|
// We don't support animation frames yet so they are successfully
|
||||||
// deleted!
|
// deleted!
|
||||||
.animation_frames => {},
|
.animation_frames => {},
|
||||||
@ -1111,3 +1136,103 @@ test "storage: delete by row 1x1" {
|
|||||||
.placement_id = .{ .tag = .external, .id = 3 },
|
.placement_id = .{ .tag = .external, .id = 3 },
|
||||||
}) != null);
|
}) != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "storage: delete images by range 1" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var t = try terminal.Terminal.init(alloc, .{ .rows = 3, .cols = 3 });
|
||||||
|
defer t.deinit(alloc);
|
||||||
|
const tracked = t.screen.pages.countTrackedPins();
|
||||||
|
|
||||||
|
var s: ImageStorage = .{};
|
||||||
|
defer s.deinit(alloc, &t.screen);
|
||||||
|
try s.addImage(alloc, .{ .id = 1 });
|
||||||
|
try s.addImage(alloc, .{ .id = 2 });
|
||||||
|
try s.addImage(alloc, .{ .id = 3 });
|
||||||
|
try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } });
|
||||||
|
try s.addPlacement(alloc, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } });
|
||||||
|
try testing.expectEqual(@as(usize, 3), s.images.count());
|
||||||
|
try testing.expectEqual(@as(usize, 2), s.placements.count());
|
||||||
|
|
||||||
|
s.dirty = false;
|
||||||
|
s.delete(alloc, &t, .{ .range = .{ .delete = false, .first = 1, .last = 2 } });
|
||||||
|
try testing.expect(s.dirty);
|
||||||
|
try testing.expectEqual(@as(usize, 3), s.images.count());
|
||||||
|
try testing.expectEqual(@as(usize, 0), s.placements.count());
|
||||||
|
try testing.expectEqual(tracked, t.screen.pages.countTrackedPins());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "storage: delete images by range 2" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var t = try terminal.Terminal.init(alloc, .{ .rows = 3, .cols = 3 });
|
||||||
|
defer t.deinit(alloc);
|
||||||
|
const tracked = t.screen.pages.countTrackedPins();
|
||||||
|
|
||||||
|
var s: ImageStorage = .{};
|
||||||
|
defer s.deinit(alloc, &t.screen);
|
||||||
|
try s.addImage(alloc, .{ .id = 1 });
|
||||||
|
try s.addImage(alloc, .{ .id = 2 });
|
||||||
|
try s.addImage(alloc, .{ .id = 3 });
|
||||||
|
try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } });
|
||||||
|
try s.addPlacement(alloc, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } });
|
||||||
|
try testing.expectEqual(@as(usize, 3), s.images.count());
|
||||||
|
try testing.expectEqual(@as(usize, 2), s.placements.count());
|
||||||
|
|
||||||
|
s.dirty = false;
|
||||||
|
s.delete(alloc, &t, .{ .range = .{ .delete = true, .first = 1, .last = 2 } });
|
||||||
|
try testing.expect(s.dirty);
|
||||||
|
try testing.expectEqual(@as(usize, 1), s.images.count());
|
||||||
|
try testing.expectEqual(@as(usize, 0), s.placements.count());
|
||||||
|
try testing.expectEqual(tracked, t.screen.pages.countTrackedPins());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "storage: delete images by range 3" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var t = try terminal.Terminal.init(alloc, .{ .rows = 3, .cols = 3 });
|
||||||
|
defer t.deinit(alloc);
|
||||||
|
const tracked = t.screen.pages.countTrackedPins();
|
||||||
|
|
||||||
|
var s: ImageStorage = .{};
|
||||||
|
defer s.deinit(alloc, &t.screen);
|
||||||
|
try s.addImage(alloc, .{ .id = 1 });
|
||||||
|
try s.addImage(alloc, .{ .id = 2 });
|
||||||
|
try s.addImage(alloc, .{ .id = 3 });
|
||||||
|
try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } });
|
||||||
|
try s.addPlacement(alloc, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } });
|
||||||
|
try testing.expectEqual(@as(usize, 3), s.images.count());
|
||||||
|
try testing.expectEqual(@as(usize, 2), s.placements.count());
|
||||||
|
|
||||||
|
s.dirty = false;
|
||||||
|
s.delete(alloc, &t, .{ .range = .{ .delete = false, .first = 1, .last = 1 } });
|
||||||
|
try testing.expect(s.dirty);
|
||||||
|
try testing.expectEqual(@as(usize, 3), s.images.count());
|
||||||
|
try testing.expectEqual(@as(usize, 0), s.placements.count());
|
||||||
|
try testing.expectEqual(tracked, t.screen.pages.countTrackedPins());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "storage: delete images by range 4" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var t = try terminal.Terminal.init(alloc, .{ .rows = 3, .cols = 3 });
|
||||||
|
defer t.deinit(alloc);
|
||||||
|
const tracked = t.screen.pages.countTrackedPins();
|
||||||
|
|
||||||
|
var s: ImageStorage = .{};
|
||||||
|
defer s.deinit(alloc, &t.screen);
|
||||||
|
try s.addImage(alloc, .{ .id = 1 });
|
||||||
|
try s.addImage(alloc, .{ .id = 2 });
|
||||||
|
try s.addImage(alloc, .{ .id = 3 });
|
||||||
|
try s.addPlacement(alloc, 1, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } });
|
||||||
|
try s.addPlacement(alloc, 2, 1, .{ .location = .{ .pin = try trackPin(&t, .{ .x = 1, .y = 1 }) } });
|
||||||
|
try testing.expectEqual(@as(usize, 3), s.images.count());
|
||||||
|
try testing.expectEqual(@as(usize, 2), s.placements.count());
|
||||||
|
|
||||||
|
s.dirty = false;
|
||||||
|
s.delete(alloc, &t, .{ .range = .{ .delete = true, .first = 1, .last = 1 } });
|
||||||
|
try testing.expect(s.dirty);
|
||||||
|
try testing.expectEqual(@as(usize, 1), s.images.count());
|
||||||
|
try testing.expectEqual(@as(usize, 0), s.placements.count());
|
||||||
|
try testing.expectEqual(tracked, t.screen.pages.countTrackedPins());
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user