diff --git a/TODO.md b/TODO.md index a6cfb4c8b..8e7d95824 100644 --- a/TODO.md +++ b/TODO.md @@ -20,8 +20,3 @@ Major Features: - Bell - Sixels: https://saitoha.github.io/libsixel/ - -paged-terminal branch: - -- tests and logic for overflowing page capacities: - - graphemes diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index 571fd1541..9e7f99e65 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -1033,6 +1033,48 @@ pub fn manualStyleUpdate(self: *Screen) !void { self.cursor.style_ref = &md.ref; } +/// Append a grapheme to the given cell within the current cursor row. +pub fn appendGrapheme(self: *Screen, cell: *Cell, cp: u21) !void { + self.cursor.page_pin.page.data.appendGrapheme( + self.cursor.page_row, + cell, + cp, + ) catch |err| switch (err) { + error.OutOfMemory => { + // We need to determine the actual cell index of the cell so + // that after we adjust the capacity we can reload the cell. + const cell_idx: usize = cell_idx: { + const cells: [*]Cell = @ptrCast(self.cursor.page_cell); + const zero: [*]Cell = cells - self.cursor.x; + const target: [*]Cell = @ptrCast(cell); + const cell_idx = (@intFromPtr(target) - @intFromPtr(zero)) / @sizeOf(Cell); + break :cell_idx cell_idx; + }; + + // Adjust our capacity. This will update our cursor page pin and + // force us to reload. + const original_node = self.cursor.page_pin.page; + const new_bytes = original_node.data.capacity.grapheme_bytes * 2; + _ = try self.pages.adjustCapacity(original_node, .{ .grapheme_bytes = new_bytes }); + self.cursorReload(); + + // The cell pointer is now invalid, so we need to get it from + // the reloaded cursor pointers. + const reloaded_cell: *Cell = switch (std.math.order(cell_idx, self.cursor.x)) { + .eq => self.cursor.page_cell, + .lt => self.cursorCellLeft(@intCast(self.cursor.x - cell_idx)), + .gt => self.cursorCellRight(@intCast(cell_idx - self.cursor.x)), + }; + + try self.cursor.page_pin.page.data.appendGrapheme( + self.cursor.page_row, + reloaded_cell, + cp, + ); + }, + }; +} + /// Set the selection to the given selection. If this is a tracked selection /// then the screen will take overnship of the selection. If this is untracked /// then the screen will convert it to tracked internally. This will automatically diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index f7e2b333f..ece11342f 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -351,11 +351,7 @@ pub fn print(self: *Terminal, c: u21) !void { } log.debug("c={x} grapheme attach to left={}", .{ c, prev.left }); - try self.screen.cursor.page_pin.page.data.appendGrapheme( - self.screen.cursor.page_row, - prev.cell, - c, - ); + try self.screen.appendGrapheme(prev.cell, c); return; } } @@ -408,11 +404,7 @@ pub fn print(self: *Terminal, c: u21) !void { if (!emoji) return; } - try self.screen.cursor.page_pin.page.data.appendGrapheme( - self.screen.cursor.page_row, - prev, - c, - ); + try self.screen.appendGrapheme(prev, c); return; } @@ -2299,6 +2291,17 @@ test "Terminal: input unique style per cell" { } } +test "Terminal: input glitch text" { + const glitch = @embedFile("res/glitch.txt"); + const alloc = testing.allocator; + var t = try init(alloc, .{ .cols = 30, .rows = 30 }); + defer t.deinit(alloc); + + for (0..100) |_| { + try t.printString(glitch); + } +} + test "Terminal: zero-width character at start" { var t = try init(testing.allocator, .{ .cols = 80, .rows = 80 }); defer t.deinit(testing.allocator); diff --git a/src/terminal/page.zig b/src/terminal/page.zig index 591a903df..7424d1863 100644 --- a/src/terminal/page.zig +++ b/src/terminal/page.zig @@ -373,7 +373,7 @@ pub const Page = struct { } /// Append a codepoint to the given cell as a grapheme. - pub fn appendGrapheme(self: *Page, row: *Row, cell: *Cell, cp: u21) !void { + pub fn appendGrapheme(self: *Page, row: *Row, cell: *Cell, cp: u21) Allocator.Error!void { if (comptime std.debug.runtime_safety) assert(cell.hasText()); const cell_offset = getOffset(Cell, self.memory, cell); diff --git a/src/terminal/res/glitch.txt b/src/terminal/res/glitch.txt new file mode 100644 index 000000000..f401c0880 --- /dev/null +++ b/src/terminal/res/glitch.txt @@ -0,0 +1 @@ +Ḡ̴͎͍̜͎͔͕̩̗͕͖̟̜͑̊̌̇̑͒͋͑̄̈́͐̈́́̽͌͂̎̀̔͋̓́̅̌̇͘̕͜͝͝h̶̡̞̫͉̳̬̜̱̥͕͑̾͛̒̆̒̉̒̑͂̄͘ͅǫ̷̨̥͔͔͖̭͚͙̯̟̭̘͇̫̰͚̺̳̙̳̟͚̫̱̹̱͒̂̑͒͜͠ͅş̴̖̰̜̱̹͙̅͒̀̏͆̐̋͂̓͋̃̈̔̂̈͛̐̿̔́̔̄͑̇͑̋̈́͌͋̾̃̽̈́̕͘̚͘͘͘͠͠t̵̢̜̱̦͇͉̬̮̼͖̳̗̥̝̬͇͕̥̜͕̳̱̥̮͉̮̩̘̰̪̤͉͎̲͈͍̳̟̠͈̝̫͋̊̀͐̍̅̀̄̃̈́̔̇̈́̄̃̽̂̌̅̄̋͒̃̈́̍̀̍̇̽̐͊̾̆̅̈̿̓͒̄̾͌̚͝͝͝͝͝t̴̥̼̳̗̬̬͔͎̯͉͇̮̰͖͇̝͔̳̳̗̰͇͎͉̬͇̝̺̯͎͖͔̍͆͒̊̒̔̊̈́̿̊̅͂̐͋̿͂̈̒̄͜͠͠ÿ̴̢̗̜̥͇͖̰͎̝̹̗̪̙̞̣̳͎̯̹͚̲̝̗̳̳̗̖͎̗̬͈͙̝̟͍̥̤͖͇̰͈̺͛̒̂͌̌̏̈̾̓̈́̿͐̂̓̔̓̂̈́͑͛͊͋̔̿̊͑͌̊̏͘͘̕͘͠͝