diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig
index 6fd50b54e..93194393b 100644
--- a/src/terminal/Screen.zig
+++ b/src/terminal/Screen.zig
@@ -498,6 +498,27 @@ pub const Row = struct {
}
}
+ /// Copy a single cell from column x in src to column x in this row.
+ pub fn copyCell(self: Row, src: Row, x: usize) !void {
+ const dst_cell = self.getCellPtr(x);
+ const src_cell = src.getCellPtr(x);
+
+ // If our destination has graphemes, we have to clear them.
+ if (dst_cell.attrs.grapheme) self.clearGraphemes(x);
+ dst_cell.* = src_cell.*;
+
+ // If the source doesn't have any graphemes, then we can just copy.
+ if (!src_cell.attrs.grapheme) return;
+
+ // Source cell has graphemes. Copy them.
+ const src_key = src.getId() + x + 1;
+ const src_data = src.screen.graphemes.get(src_key) orelse return;
+ const dst_key = self.getId() + x + 1;
+ const dst_gop = try self.screen.graphemes.getOrPut(self.screen.alloc, dst_key);
+ dst_gop.value_ptr.* = try src_data.copy(self.screen.alloc);
+ self.storage[0].header.flags.grapheme = true;
+ }
+
/// Copy the row src into this row. The row can be from another screen.
pub fn copyRow(self: Row, src: Row) !void {
// If we have graphemes, clear first to unset them.
diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig
index 941b93dec..9312a1623 100644
--- a/src/terminal/Terminal.zig
+++ b/src/terminal/Terminal.zig
@@ -1564,8 +1564,14 @@ pub fn insertLines(self: *Terminal, count: usize) !void {
// Rare, but happens
if (count == 0) return;
+ // If the cursor is outside the scroll region we do nothing.
+ if (self.screen.cursor.y < self.scrolling_region.top or
+ self.screen.cursor.y > self.scrolling_region.bottom or
+ self.screen.cursor.x < self.scrolling_region.left or
+ self.screen.cursor.x > self.scrolling_region.right) return;
+
// Move the cursor to the left margin
- self.screen.cursor.x = 0;
+ self.screen.cursor.x = self.scrolling_region.left;
self.screen.cursor.pending_wrap = false;
// Remaining rows from our cursor
@@ -1582,14 +1588,21 @@ pub fn insertLines(self: *Terminal, count: usize) !void {
// Ensure we have the lines populated to the end
while (y > top) : (y -= 1) {
- try self.screen.copyRow(.{ .active = y }, .{ .active = y - adjusted_count });
+ const src = self.screen.getRow(.{ .active = y - adjusted_count });
+ const dst = self.screen.getRow(.{ .active = y });
+ for (self.scrolling_region.left..self.scrolling_region.right + 1) |x| {
+ try dst.copyCell(src, x);
+ }
}
// Insert count blank lines
y = self.screen.cursor.y;
while (y < self.screen.cursor.y + adjusted_count) : (y += 1) {
const row = self.screen.getRow(.{ .active = y });
- row.clear(self.screen.cursor.pen);
+ row.fillSlice(.{
+ .bg = self.screen.cursor.pen.bg,
+ .attrs = .{ .has_bg = self.screen.cursor.pen.attrs.has_bg },
+ }, self.scrolling_region.left, self.scrolling_region.right + 1);
}
}
@@ -1613,23 +1626,58 @@ pub fn deleteLines(self: *Terminal, count: usize) !void {
const tracy = trace(@src());
defer tracy.end();
- // If our cursor is outside of the scroll region, do nothing.
+ // If the cursor is outside the scroll region we do nothing.
if (self.screen.cursor.y < self.scrolling_region.top or
- self.screen.cursor.y > self.scrolling_region.bottom)
+ self.screen.cursor.y > self.scrolling_region.bottom or
+ self.screen.cursor.x < self.scrolling_region.left or
+ self.screen.cursor.x > self.scrolling_region.right) return;
+
+ // Move the cursor to the left margin
+ self.screen.cursor.x = self.scrolling_region.left;
+ self.screen.cursor.pending_wrap = false;
+
+ // If this is a full line margin then we can do a faster scroll.
+ if (self.scrolling_region.left == 0 and
+ self.scrolling_region.right == self.cols - 1)
{
+ self.screen.scrollRegionUp(
+ .{ .active = self.screen.cursor.y },
+ .{ .active = self.scrolling_region.bottom },
+ @min(count, self.scrolling_region.bottom - self.screen.cursor.y),
+ );
return;
}
- // Move the cursor to the left margin
- self.screen.cursor.x = 0;
- self.screen.cursor.pending_wrap = false;
+ // Left/right margin is set, we need to do a slower scroll.
+ // Remaining rows from our cursor in the region, 1-indexed.
+ const rem = self.scrolling_region.bottom - self.screen.cursor.y + 1;
- // Perform the scroll
- self.screen.scrollRegionUp(
- .{ .active = self.screen.cursor.y },
- .{ .active = self.scrolling_region.bottom },
- @min(count, self.scrolling_region.bottom - self.screen.cursor.y),
- );
+ // If our count is greater than the remaining amount, we can just
+ // clear the region using insertLines.
+ if (count >= rem) {
+ try self.insertLines(count);
+ return;
+ }
+
+ // The amount of lines we need to scroll up.
+ const scroll_amount = rem - count;
+ const scroll_top = self.scrolling_region.bottom - scroll_amount;
+ for (self.screen.cursor.y..scroll_top + 1) |y| {
+ const src = self.screen.getRow(.{ .active = y + count });
+ const dst = self.screen.getRow(.{ .active = y });
+ for (self.scrolling_region.left..self.scrolling_region.right + 1) |x| {
+ try dst.copyCell(src, x);
+ }
+ }
+
+ // Insert blank lines
+ for (scroll_top + 1..self.scrolling_region.bottom + 1) |y| {
+ const row = self.screen.getRow(.{ .active = y });
+ row.fillSlice(.{
+ .bg = self.screen.cursor.pen.bg,
+ .attrs = .{ .has_bg = self.screen.cursor.pen.attrs.has_bg },
+ }, self.scrolling_region.left, self.scrolling_region.right + 1);
+ }
}
/// Scroll the text down by one row.
@@ -2648,6 +2696,171 @@ test "Terminal: deleteLines resets wrap" {
}
}
+test "Terminal: deleteLines simple" {
+ const alloc = testing.allocator;
+ var t = try init(alloc, 5, 5);
+ defer t.deinit(alloc);
+
+ try t.printString("ABC");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("DEF");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("GHI");
+ t.setCursorPos(2, 2);
+ try t.deleteLines(1);
+
+ {
+ var str = try t.plainString(testing.allocator);
+ defer testing.allocator.free(str);
+ try testing.expectEqualStrings("ABC\nGHI", str);
+ }
+}
+
+test "Terminal: deleteLines left/right scroll region" {
+ const alloc = testing.allocator;
+ var t = try init(alloc, 10, 10);
+ defer t.deinit(alloc);
+
+ try t.printString("ABC123");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("DEF456");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("GHI789");
+ t.scrolling_region.left = 1;
+ t.scrolling_region.right = 3;
+ t.setCursorPos(2, 2);
+ try t.deleteLines(1);
+
+ {
+ var str = try t.plainString(testing.allocator);
+ defer testing.allocator.free(str);
+ try testing.expectEqualStrings("ABC123\nDHI756\nG 89", str);
+ }
+}
+
+test "Terminal: deleteLines left/right scroll region high count" {
+ const alloc = testing.allocator;
+ var t = try init(alloc, 10, 10);
+ defer t.deinit(alloc);
+
+ try t.printString("ABC123");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("DEF456");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("GHI789");
+ t.scrolling_region.left = 1;
+ t.scrolling_region.right = 3;
+ t.setCursorPos(2, 2);
+ try t.deleteLines(100);
+
+ {
+ var str = try t.plainString(testing.allocator);
+ defer testing.allocator.free(str);
+ try testing.expectEqualStrings("ABC123\nD 56\nG 89", str);
+ }
+}
+
+test "Terminal: insertLines simple" {
+ const alloc = testing.allocator;
+ var t = try init(alloc, 5, 5);
+ defer t.deinit(alloc);
+
+ try t.printString("ABC");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("DEF");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("GHI");
+ t.setCursorPos(2, 2);
+ try t.insertLines(1);
+
+ {
+ var str = try t.plainString(testing.allocator);
+ defer testing.allocator.free(str);
+ try testing.expectEqualStrings("ABC\n\nDEF\nGHI", str);
+ }
+}
+
+test "Terminal: insertLines outside of scroll region" {
+ const alloc = testing.allocator;
+ var t = try init(alloc, 5, 5);
+ defer t.deinit(alloc);
+
+ try t.printString("ABC");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("DEF");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("GHI");
+ t.setScrollingRegion(3, 4);
+ t.setCursorPos(2, 2);
+ try t.insertLines(1);
+
+ {
+ var str = try t.plainString(testing.allocator);
+ defer testing.allocator.free(str);
+ try testing.expectEqualStrings("ABC\nDEF\nGHI", str);
+ }
+}
+
+test "Terminal: insertLines top/bottom scroll region" {
+ const alloc = testing.allocator;
+ var t = try init(alloc, 5, 5);
+ defer t.deinit(alloc);
+
+ try t.printString("ABC");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("DEF");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("GHI");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("123");
+ t.setScrollingRegion(1, 3);
+ t.setCursorPos(2, 2);
+ try t.insertLines(1);
+
+ {
+ var str = try t.plainString(testing.allocator);
+ defer testing.allocator.free(str);
+ try testing.expectEqualStrings("ABC\n\nDEF\n123", str);
+ }
+}
+
+test "Terminal: insertLines left/right scroll region" {
+ const alloc = testing.allocator;
+ var t = try init(alloc, 10, 10);
+ defer t.deinit(alloc);
+
+ try t.printString("ABC123");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("DEF456");
+ t.carriageReturn();
+ try t.linefeed();
+ try t.printString("GHI789");
+ t.scrolling_region.left = 1;
+ t.scrolling_region.right = 3;
+ t.setCursorPos(2, 2);
+ try t.insertLines(1);
+
+ {
+ var str = try t.plainString(testing.allocator);
+ defer testing.allocator.free(str);
+ try testing.expectEqualStrings("ABC123\nD 56\nGEF489\n HI7", str);
+ }
+}
+
test "Terminal: insertLines" {
const alloc = testing.allocator;
var t = try init(alloc, 2, 5);
@@ -2964,6 +3177,32 @@ test "Terminal: index bottom of primary screen" {
}
}
+test "Terminal: index bottom of primary screen background sgr" {
+ const alloc = testing.allocator;
+ var t = try init(alloc, 5, 5);
+ defer t.deinit(alloc);
+
+ const pen: Screen.Cell = .{
+ .bg = .{ .r = 0xFF, .g = 0x00, .b = 0x00 },
+ .attrs = .{ .has_bg = true },
+ };
+
+ t.setCursorPos(5, 1);
+ try t.print('A');
+ t.screen.cursor.pen = pen;
+ try t.index();
+
+ {
+ var str = try t.plainString(testing.allocator);
+ defer testing.allocator.free(str);
+ try testing.expectEqualStrings("\n\n\nA", str);
+ for (0..5) |x| {
+ const cell = t.screen.getCell(.active, 4, x);
+ try testing.expectEqual(pen, cell);
+ }
+ }
+}
+
test "Terminal: index inside scroll region" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 5);
diff --git a/website/app/vt/dl/page.mdx b/website/app/vt/dl/page.mdx
new file mode 100644
index 000000000..00c3e6288
--- /dev/null
+++ b/website/app/vt/dl/page.mdx
@@ -0,0 +1,113 @@
+import VTSequence from "@/components/VTSequence";
+
+# Delete Line (DL)
+
+
+
+Deletes `n` lines at the current cursor position and shifts existing
+lines up.
+
+The parameter `n` must be an integer greater than or equal to 1. If `n` is less than
+or equal to 0, adjust `n` to be 1. If `n` is omitted, `n` defaults to 1.
+
+If the current cursor position is outside of the current scroll region,
+this sequence does nothing. The cursor is outside of the current scroll
+region if it is above the [top margin](#TODO), below the [bottom margin](#TODO),
+left of the [left margin](#TODO), or right of the [right margin](#TODO).
+
+This sequence unsets the pending wrap state.
+
+This sequence moves the cursor column to the left margin.
+
+Remove the top `n` lines of the current scroll region, and shift existing
+lines up. The space created at the bottom of the scroll region should be
+blank with the background color set according to the current SGR state.
+
+If a [left margin](#TODO) or [right margin](#TODO) is set, only the cells
+within and including the margins are deleted or shifted.
+Other existing contents to the left of the left margin or right of the
+right margin remains untouched.
+
+If a multi-cell character would be split, erase the full multi-cell
+character. For example, if "橋" is printed to the left of the left margin
+and shifting the line down as a result of DL would split the character,
+the cell should be erased.
+
+## Validation
+
+### DL V-1: Simple Delete Line
+
+```bash
+printf "\033[1;1H" # move to top-left
+printf "\033[0J" # clear screen
+printf "ABC\n"
+printf "DEF\n"
+printf "GHI\n"
+printf "\033[2;2H"
+printf "\033[M"
+```
+
+```
+|ABC_____|
+|GHI_____|
+```
+
+### DL V-2: Cursor Outside of Scroll Region
+
+```bash
+printf "\033[1;1H" # move to top-left
+printf "\033[0J" # clear screen
+printf "ABC\n"
+printf "DEF\n"
+printf "GHI\n"
+printf "\033[3;4r" # scroll region top/bottom
+printf "\033[2;2H"
+printf "\033[M"
+```
+
+```
+|ABC_____|
+|DEF_____|
+|GHI_____|
+```
+
+### DL V-3: Top/Bottom Scroll Regions
+
+```bash
+printf "\033[1;1H" # move to top-left
+printf "\033[0J" # clear screen
+printf "ABC\n"
+printf "DEF\n"
+printf "GHI\n"
+printf "123\n"
+printf "\033[1;3r" # scroll region top/bottom
+printf "\033[2;2H"
+printf "\033[M"
+```
+
+```
+|ABC_____|
+|GHI_____|
+|________|
+|123_____|
+```
+
+### DL V-4: Left/Right Scroll Regions
+
+```bash
+printf "\033[1;1H" # move to top-left
+printf "\033[0J" # clear screen
+printf "ABC123\n"
+printf "DEF456\n"
+printf "GHI789\n"
+printf "\033[?69h" # enable left/right margins
+printf "\033[2;4s" # scroll region left/right
+printf "\033[2;2H"
+printf "\033[M"
+```
+
+```
+|ABC123__|
+|DHI756__|
+|G___89__|
+```
diff --git a/website/app/vt/il/page.mdx b/website/app/vt/il/page.mdx
new file mode 100644
index 000000000..dc19e3297
--- /dev/null
+++ b/website/app/vt/il/page.mdx
@@ -0,0 +1,119 @@
+import VTSequence from "@/components/VTSequence";
+
+# Insert Line (IL)
+
+
+
+Inserts `n` lines at the current cursor position and shifts existing
+lines down.
+
+The parameter `n` must be an integer greater than or equal to 1. If `n` is less than
+or equal to 0, adjust `n` to be 1. If `n` is omitted, `n` defaults to 1.
+
+If the current cursor position is outside of the current scroll region,
+this sequence does nothing. The cursor is outside of the current scroll
+region if it is above the [top margin](#TODO), below the [bottom margin](#TODO),
+left of the [left margin](#TODO), or right of the [right margin](#TODO).
+
+This sequence unsets the pending wrap state.
+
+This sequence moves the cursor column to the left margin.
+
+From the current cursor row down `n` lines, insert blank lines colored
+with a background color according to the current SGR state. When a line is
+inserted, shift all existing content down one line. The bottommost row
+is the bottom margin. If content is shifted beyond the bottom margin,
+it is lost and the existing content beyond the bottom margin is preserved
+and not shifted.
+
+If a [left margin](#TODO) or [right margin](#TODO) is set, only the cells
+within and including the margins are blanked (when inserted) or shifted.
+Other existing contents to the left of the left margin or right of the
+right margin remains untouched.
+
+If a multi-cell character would be split, erase the full multi-cell
+character. For example, if "橋" is printed to the left of the left margin
+and shifting the line down as a result of IL would split the character,
+the cell should be erased.
+
+## Validation
+
+### IL V-1: Simple Insert Line
+
+```bash
+printf "\033[1;1H" # move to top-left
+printf "\033[0J" # clear screen
+printf "ABC\n"
+printf "DEF\n"
+printf "GHI\n"
+printf "\033[2;2H"
+printf "\033[L"
+```
+
+```
+|ABC_____|
+|c_______|
+|DEF_____|
+|GHI_____|
+```
+
+### IL V-2: Cursor Outside of Scroll Region
+
+```bash
+printf "\033[1;1H" # move to top-left
+printf "\033[0J" # clear screen
+printf "ABC\n"
+printf "DEF\n"
+printf "GHI\n"
+printf "\033[3;4r" # scroll region top/bottom
+printf "\033[2;2H"
+printf "\033[L"
+```
+
+```
+|ABC_____|
+|DEF_____|
+|GHI_____|
+```
+
+### IL V-3: Top/Bottom Scroll Regions
+
+```bash
+printf "\033[1;1H" # move to top-left
+printf "\033[0J" # clear screen
+printf "ABC\n"
+printf "DEF\n"
+printf "GHI\n"
+printf "123\n"
+printf "\033[1;3r" # scroll region top/bottom
+printf "\033[2;2H"
+printf "\033[L"
+```
+
+```
+|ABC_____|
+|_c______|
+|DEF_____|
+|123_____|
+```
+
+### IL V-4: Left/Right Scroll Regions
+
+```bash
+printf "\033[1;1H" # move to top-left
+printf "\033[0J" # clear screen
+printf "ABC123\n"
+printf "DEF456\n"
+printf "GHI789\n"
+printf "\033[?69h" # enable left/right margins
+printf "\033[2;4s" # scroll region left/right
+printf "\033[2;2H"
+printf "\033[L"
+```
+
+```
+|ABC123__|
+|Dc__56__|
+|GEF489__|
+|_HI7____|
+```