terminal: ED xterm audit

Fix multi-cell handling
Test all scenarios
This commit is contained in:
Mitchell Hashimoto
2023-10-08 14:48:06 -07:00
parent e618f536ea
commit eab390344a
2 changed files with 620 additions and 138 deletions

View File

@ -595,6 +595,17 @@ pub fn invokeCharset(
} }
} }
/// Print UTF-8 encoded string to the terminal. This string must be
/// a single line, newlines and carriage returns and other control
/// characters are not processed.
///
/// This is not public because it is only used for tests rigt now.
fn printString(self: *Terminal, str: []const u8) !void {
const view = try std.unicode.Utf8View.init(str);
var it = view.iterator();
while (it.nextCodepoint()) |cp| try self.print(cp);
}
pub fn print(self: *Terminal, c: u21) !void { pub fn print(self: *Terminal, c: u21) !void {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
@ -999,7 +1010,7 @@ pub fn eraseDisplay(
self: *Terminal, self: *Terminal,
alloc: Allocator, alloc: Allocator,
mode: csi.EraseDisplay, mode: csi.EraseDisplay,
protected: bool, protected_req: bool,
) void { ) void {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
@ -1010,6 +1021,11 @@ pub fn eraseDisplay(
.attrs = .{ .has_bg = true }, .attrs = .{ .has_bg = true },
}; };
// We respect protected attributes if explicitly requested (probably
// a DECSEL sequence) or if our last protected mode was ISO even if its
// not currently set.
const protected = self.screen.protected_mode == .iso or protected_req;
switch (mode) { switch (mode) {
.complete => { .complete => {
var it = self.screen.rowIterator(.active); var it = self.screen.rowIterator(.active);
@ -1040,16 +1056,10 @@ pub fn eraseDisplay(
.below => { .below => {
// All lines to the right (including the cursor) // All lines to the right (including the cursor)
{ {
self.eraseLine(.right, protected_req);
const row = self.screen.getRow(.{ .active = self.screen.cursor.y }); const row = self.screen.getRow(.{ .active = self.screen.cursor.y });
row.setWrapped(false); row.setWrapped(false);
row.setDirty(true); row.setDirty(true);
for (self.screen.cursor.x..self.cols) |x| {
if (row.header().flags.grapheme) row.clearGraphemes(x);
const cell = row.getCellPtr(x);
if (protected and cell.attrs.protected) continue;
cell.* = pen;
cell.char = 0;
}
} }
// All lines below // All lines below
@ -1072,18 +1082,12 @@ pub fn eraseDisplay(
.above => { .above => {
// Erase to the left (including the cursor) // Erase to the left (including the cursor)
var x: usize = 0; self.eraseLine(.left, protected_req);
while (x <= self.screen.cursor.x) : (x += 1) {
const cell = self.screen.getCellPtr(.active, self.screen.cursor.y, x);
if (protected and cell.attrs.protected) continue;
cell.* = pen;
cell.char = 0;
}
// All lines above // All lines above
var y: usize = 0; var y: usize = 0;
while (y < self.screen.cursor.y) : (y += 1) { while (y < self.screen.cursor.y) : (y += 1) {
x = 0; var x: usize = 0;
while (x < self.cols) : (x += 1) { while (x < self.cols) : (x += 1) {
const cell = self.screen.getCellPtr(.active, y, x); const cell = self.screen.getCellPtr(.active, y, x);
if (protected and cell.attrs.protected) continue; if (protected and cell.attrs.protected) continue;
@ -1103,126 +1107,6 @@ pub fn eraseDisplay(
} }
} }
test "Terminal: eraseDisplay above" {
var t = try init(testing.allocator, 80, 80);
defer t.deinit(testing.allocator);
const pink = color.RGB{ .r = 0xFF, .g = 0x00, .b = 0x7F };
t.screen.cursor.pen = Screen.Cell{
.char = 'a',
.bg = pink,
.fg = pink,
.attrs = .{ .bold = true, .has_bg = true },
};
const cell_ptr = t.screen.getCellPtr(.active, 0, 0);
cell_ptr.* = t.screen.cursor.pen;
// verify the cell was set
var cell = t.screen.getCell(.active, 0, 0);
try testing.expect(cell.bg.eql(pink));
try testing.expect(cell.fg.eql(pink));
try testing.expect(cell.char == 'a');
try testing.expect(cell.attrs.bold);
// move the cursor below it
t.screen.cursor.y = 40;
t.screen.cursor.x = 40;
// erase above the cursor
t.eraseDisplay(testing.allocator, .above, false);
// check it was erased
cell = t.screen.getCell(.active, 0, 0);
try testing.expect(cell.bg.eql(pink));
try testing.expect(cell.fg.eql(.{}));
try testing.expect(cell.char == 0);
try testing.expect(!cell.attrs.bold);
try testing.expect(cell.attrs.has_bg);
// Check that our pen hasn't changed
try testing.expect(t.screen.cursor.pen.attrs.bold);
// check that another cell got the correct bg
cell = t.screen.getCell(.active, 0, 1);
try testing.expect(cell.bg.eql(pink));
}
test "Terminal: eraseDisplay below" {
var t = try init(testing.allocator, 80, 80);
defer t.deinit(testing.allocator);
const pink = color.RGB{ .r = 0xFF, .g = 0x00, .b = 0x7F };
t.screen.cursor.pen = Screen.Cell{
.char = 'a',
.bg = pink,
.fg = pink,
.attrs = .{ .bold = true, .has_bg = true },
};
const cell_ptr = t.screen.getCellPtr(.active, 60, 60);
cell_ptr.* = t.screen.cursor.pen;
// verify the cell was set
var cell = t.screen.getCell(.active, 60, 60);
try testing.expect(cell.bg.eql(pink));
try testing.expect(cell.fg.eql(pink));
try testing.expect(cell.char == 'a');
try testing.expect(cell.attrs.bold);
// erase below the cursor
t.eraseDisplay(testing.allocator, .below, false);
// check it was erased
cell = t.screen.getCell(.active, 60, 60);
try testing.expect(cell.bg.eql(pink));
try testing.expect(cell.fg.eql(.{}));
try testing.expect(cell.char == 0);
try testing.expect(!cell.attrs.bold);
try testing.expect(cell.attrs.has_bg);
// check that another cell got the correct bg
cell = t.screen.getCell(.active, 0, 1);
try testing.expect(cell.bg.eql(pink));
}
test "Terminal: eraseDisplay complete" {
var t = try init(testing.allocator, 80, 80);
defer t.deinit(testing.allocator);
const pink = color.RGB{ .r = 0xFF, .g = 0x00, .b = 0x7F };
t.screen.cursor.pen = Screen.Cell{
.char = 'a',
.bg = pink,
.fg = pink,
.attrs = .{ .bold = true, .has_bg = true },
};
var cell_ptr = t.screen.getCellPtr(.active, 60, 60);
cell_ptr.* = t.screen.cursor.pen;
cell_ptr = t.screen.getCellPtr(.active, 0, 0);
cell_ptr.* = t.screen.cursor.pen;
// verify the cell was set
var cell = t.screen.getCell(.active, 60, 60);
try testing.expect(cell.bg.eql(pink));
try testing.expect(cell.fg.eql(pink));
try testing.expect(cell.char == 'a');
try testing.expect(cell.attrs.bold);
// verify the cell was set
cell = t.screen.getCell(.active, 0, 0);
try testing.expect(cell.bg.eql(pink));
try testing.expect(cell.fg.eql(pink));
try testing.expect(cell.char == 'a');
try testing.expect(cell.attrs.bold);
// position our cursor between the cells
t.screen.cursor.y = 30;
// erase everything
t.eraseDisplay(testing.allocator, .complete, false);
// check they were erased
cell = t.screen.getCell(.active, 60, 60);
try testing.expect(cell.bg.eql(pink));
try testing.expect(cell.fg.eql(.{}));
try testing.expect(cell.char == 0);
try testing.expect(!cell.attrs.bold);
try testing.expect(cell.attrs.has_bg);
cell = t.screen.getCell(.active, 0, 0);
try testing.expect(cell.bg.eql(pink));
try testing.expect(cell.fg.eql(.{}));
try testing.expect(cell.char == 0);
try testing.expect(!cell.attrs.bold);
try testing.expect(cell.attrs.has_bg);
}
/// Erase the line. /// Erase the line.
pub fn eraseLine( pub fn eraseLine(
self: *Terminal, self: *Terminal,
@ -3983,8 +3867,6 @@ test "Terminal: eraseLine right protected requested" {
} }
} }
// ------------------- SPLIT
test "Terminal: eraseLine simple erase left" { test "Terminal: eraseLine simple erase left" {
const alloc = testing.allocator; const alloc = testing.allocator;
var t = try init(alloc, 5, 5); var t = try init(alloc, 5, 5);
@ -4233,6 +4115,467 @@ test "Terminal: eraseLine complete protected requested" {
} }
} }
test "Terminal: eraseDisplay simple erase below" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 5);
defer t.deinit(alloc);
for ("ABC") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("DEF") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("GHI") |c| try t.print(c);
t.setCursorPos(2, 2);
t.eraseDisplay(alloc, .below, false);
{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("ABC\nD", str);
}
}
test "Terminal: eraseDisplay erase below preserves SGR bg" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 5);
defer t.deinit(alloc);
for ("ABC") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("DEF") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("GHI") |c| try t.print(c);
t.setCursorPos(2, 2);
const pen: Screen.Cell = .{
.bg = .{ .r = 0xFF, .g = 0x00, .b = 0x00 },
.attrs = .{ .has_bg = true },
};
t.screen.cursor.pen = pen;
t.eraseDisplay(alloc, .below, false);
{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("ABC\nD", str);
for (1..5) |x| {
const cell = t.screen.getCell(.active, 1, x);
try testing.expectEqual(pen, cell);
}
}
}
test "Terminal: eraseDisplay below split multi-cell" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 5);
defer t.deinit(alloc);
try t.printString("AB橋C");
t.carriageReturn();
try t.linefeed();
try t.printString("DE橋F");
t.carriageReturn();
try t.linefeed();
try t.printString("GH橋I");
t.setCursorPos(2, 4);
t.eraseDisplay(alloc, .below, false);
{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("AB橋C\nDE", str);
}
}
test "Terminal: eraseDisplay below protected attributes respected with iso" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 5);
defer t.deinit(alloc);
t.setProtectedMode(.iso);
for ("ABC") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("DEF") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("GHI") |c| try t.print(c);
t.setCursorPos(2, 2);
t.eraseDisplay(alloc, .below, false);
{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("ABC\nDEF\nGHI", str);
}
}
test "Terminal: eraseDisplay below protected attributes ignored with dec most recent" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 5);
defer t.deinit(alloc);
t.setProtectedMode(.iso);
for ("ABC") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("DEF") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("GHI") |c| try t.print(c);
t.setProtectedMode(.dec);
t.setProtectedMode(.off);
t.setCursorPos(2, 2);
t.eraseDisplay(alloc, .below, false);
{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("ABC\nD", str);
}
}
test "Terminal: eraseDisplay below protected attributes ignored with dec set" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 5);
defer t.deinit(alloc);
t.setProtectedMode(.dec);
for ("ABC") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("DEF") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("GHI") |c| try t.print(c);
t.setCursorPos(2, 2);
t.eraseDisplay(alloc, .below, false);
{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("ABC\nD", str);
}
}
test "Terminal: eraseDisplay simple erase above" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 5);
defer t.deinit(alloc);
for ("ABC") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("DEF") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("GHI") |c| try t.print(c);
t.setCursorPos(2, 2);
t.eraseDisplay(alloc, .above, false);
{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("\n F\nGHI", str);
}
}
test "Terminal: eraseDisplay below protected attributes respected with force" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 5);
defer t.deinit(alloc);
t.setProtectedMode(.dec);
for ("ABC") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("DEF") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("GHI") |c| try t.print(c);
t.setCursorPos(2, 2);
t.eraseDisplay(alloc, .below, true);
{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("ABC\nDEF\nGHI", str);
}
}
test "Terminal: eraseDisplay erase above preserves SGR bg" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 5);
defer t.deinit(alloc);
for ("ABC") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("DEF") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("GHI") |c| try t.print(c);
t.setCursorPos(2, 2);
const pen: Screen.Cell = .{
.bg = .{ .r = 0xFF, .g = 0x00, .b = 0x00 },
.attrs = .{ .has_bg = true },
};
t.screen.cursor.pen = pen;
t.eraseDisplay(alloc, .above, false);
{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("\n F\nGHI", str);
for (0..2) |x| {
const cell = t.screen.getCell(.active, 1, x);
try testing.expectEqual(pen, cell);
}
}
}
test "Terminal: eraseDisplay above split multi-cell" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 5);
defer t.deinit(alloc);
try t.printString("AB橋C");
t.carriageReturn();
try t.linefeed();
try t.printString("DE橋F");
t.carriageReturn();
try t.linefeed();
try t.printString("GH橋I");
t.setCursorPos(2, 3);
t.eraseDisplay(alloc, .above, false);
{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("\n F\nGH橋I", str);
}
}
test "Terminal: eraseDisplay above protected attributes respected with iso" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 5);
defer t.deinit(alloc);
t.setProtectedMode(.iso);
for ("ABC") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("DEF") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("GHI") |c| try t.print(c);
t.setCursorPos(2, 2);
t.eraseDisplay(alloc, .above, false);
{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("ABC\nDEF\nGHI", str);
}
}
test "Terminal: eraseDisplay above protected attributes ignored with dec most recent" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 5);
defer t.deinit(alloc);
t.setProtectedMode(.iso);
for ("ABC") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("DEF") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("GHI") |c| try t.print(c);
t.setProtectedMode(.dec);
t.setProtectedMode(.off);
t.setCursorPos(2, 2);
t.eraseDisplay(alloc, .above, false);
{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("\n F\nGHI", str);
}
}
test "Terminal: eraseDisplay above protected attributes ignored with dec set" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 5);
defer t.deinit(alloc);
t.setProtectedMode(.dec);
for ("ABC") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("DEF") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("GHI") |c| try t.print(c);
t.setCursorPos(2, 2);
t.eraseDisplay(alloc, .above, false);
{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("\n F\nGHI", str);
}
}
test "Terminal: eraseDisplay above protected attributes respected with force" {
const alloc = testing.allocator;
var t = try init(alloc, 5, 5);
defer t.deinit(alloc);
t.setProtectedMode(.dec);
for ("ABC") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("DEF") |c| try t.print(c);
t.carriageReturn();
try t.linefeed();
for ("GHI") |c| try t.print(c);
t.setCursorPos(2, 2);
t.eraseDisplay(alloc, .above, true);
{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("ABC\nDEF\nGHI", str);
}
}
test "Terminal: eraseDisplay above" {
var t = try init(testing.allocator, 80, 80);
defer t.deinit(testing.allocator);
const pink = color.RGB{ .r = 0xFF, .g = 0x00, .b = 0x7F };
t.screen.cursor.pen = Screen.Cell{
.char = 'a',
.bg = pink,
.fg = pink,
.attrs = .{ .bold = true, .has_bg = true },
};
const cell_ptr = t.screen.getCellPtr(.active, 0, 0);
cell_ptr.* = t.screen.cursor.pen;
// verify the cell was set
var cell = t.screen.getCell(.active, 0, 0);
try testing.expect(cell.bg.eql(pink));
try testing.expect(cell.fg.eql(pink));
try testing.expect(cell.char == 'a');
try testing.expect(cell.attrs.bold);
// move the cursor below it
t.screen.cursor.y = 40;
t.screen.cursor.x = 40;
// erase above the cursor
t.eraseDisplay(testing.allocator, .above, false);
// check it was erased
cell = t.screen.getCell(.active, 0, 0);
try testing.expect(cell.bg.eql(pink));
try testing.expect(cell.fg.eql(.{}));
try testing.expect(cell.char == 0);
try testing.expect(!cell.attrs.bold);
try testing.expect(cell.attrs.has_bg);
// Check that our pen hasn't changed
try testing.expect(t.screen.cursor.pen.attrs.bold);
// check that another cell got the correct bg
cell = t.screen.getCell(.active, 0, 1);
try testing.expect(cell.bg.eql(pink));
}
test "Terminal: eraseDisplay below" {
var t = try init(testing.allocator, 80, 80);
defer t.deinit(testing.allocator);
const pink = color.RGB{ .r = 0xFF, .g = 0x00, .b = 0x7F };
t.screen.cursor.pen = Screen.Cell{
.char = 'a',
.bg = pink,
.fg = pink,
.attrs = .{ .bold = true, .has_bg = true },
};
const cell_ptr = t.screen.getCellPtr(.active, 60, 60);
cell_ptr.* = t.screen.cursor.pen;
// verify the cell was set
var cell = t.screen.getCell(.active, 60, 60);
try testing.expect(cell.bg.eql(pink));
try testing.expect(cell.fg.eql(pink));
try testing.expect(cell.char == 'a');
try testing.expect(cell.attrs.bold);
// erase below the cursor
t.eraseDisplay(testing.allocator, .below, false);
// check it was erased
cell = t.screen.getCell(.active, 60, 60);
try testing.expect(cell.bg.eql(pink));
try testing.expect(cell.fg.eql(.{}));
try testing.expect(cell.char == 0);
try testing.expect(!cell.attrs.bold);
try testing.expect(cell.attrs.has_bg);
// check that another cell got the correct bg
cell = t.screen.getCell(.active, 0, 1);
try testing.expect(cell.bg.eql(pink));
}
test "Terminal: eraseDisplay complete" {
var t = try init(testing.allocator, 80, 80);
defer t.deinit(testing.allocator);
const pink = color.RGB{ .r = 0xFF, .g = 0x00, .b = 0x7F };
t.screen.cursor.pen = Screen.Cell{
.char = 'a',
.bg = pink,
.fg = pink,
.attrs = .{ .bold = true, .has_bg = true },
};
var cell_ptr = t.screen.getCellPtr(.active, 60, 60);
cell_ptr.* = t.screen.cursor.pen;
cell_ptr = t.screen.getCellPtr(.active, 0, 0);
cell_ptr.* = t.screen.cursor.pen;
// verify the cell was set
var cell = t.screen.getCell(.active, 60, 60);
try testing.expect(cell.bg.eql(pink));
try testing.expect(cell.fg.eql(pink));
try testing.expect(cell.char == 'a');
try testing.expect(cell.attrs.bold);
// verify the cell was set
cell = t.screen.getCell(.active, 0, 0);
try testing.expect(cell.bg.eql(pink));
try testing.expect(cell.fg.eql(pink));
try testing.expect(cell.char == 'a');
try testing.expect(cell.attrs.bold);
// position our cursor between the cells
t.screen.cursor.y = 30;
// erase everything
t.eraseDisplay(testing.allocator, .complete, false);
// check they were erased
cell = t.screen.getCell(.active, 60, 60);
try testing.expect(cell.bg.eql(pink));
try testing.expect(cell.fg.eql(.{}));
try testing.expect(cell.char == 0);
try testing.expect(!cell.attrs.bold);
try testing.expect(cell.attrs.has_bg);
cell = t.screen.getCell(.active, 0, 0);
try testing.expect(cell.bg.eql(pink));
try testing.expect(cell.fg.eql(.{}));
try testing.expect(cell.char == 0);
try testing.expect(!cell.attrs.bold);
try testing.expect(cell.attrs.has_bg);
}
test "Terminal: eraseDisplay protected complete" { test "Terminal: eraseDisplay protected complete" {
const alloc = testing.allocator; const alloc = testing.allocator;
var t = try init(alloc, 10, 5); var t = try init(alloc, 10, 5);

139
website/app/vt/ed/page.mdx Normal file
View File

@ -0,0 +1,139 @@
import VTSequence from "@/components/VTSequence";
# Erase Display (ED)
<VTSequence sequence={["CSI", "Pn", "J"]} />
Erase display contents with behavior depending on the command `n`.
If `n` is unset, the value of `n` is 0. The only valid values for `n` are
0, 1, 2, or 3. If any other value of `n` is given, do not execute this sequence.
The remainder of the sequence documentation assumes a valid value of `n`.
For all valid values of `n` except 3, this sequence unsets the pending wrap state.
The cursor position will remain unchanged under all circumstances throughout
this sequence.
If [Select Character Selection Attribute (DECSCA)](#TODO) is enabled
or was the most recently enabled protection mode on the currently active screen,
protected attributes are ignored. Otherwise, protected attributes will be
respected. For more details on this specific logic for protected attribute
handling, see [Erase Character (ECH)](/vt/ech).
For all operations, if a multi-cell character would be split, erase the full multi-cell
character. For example, if "橋" is printed and the erase would only erase the
first or second cell of the two-cell character, both cells should be erased.
This sequence does not respect any scroll regions (top, bottom, left, or
right). The boundaries of the operation are the full visible screen.
If `n` is `0`, perform an **erase display below** operation. Erase all
cells to the right and below the cursor. The background color of erased cells
is colored according to the current SGR state.
If `n` is `1`, perform an **erase display above** operation. Erase all
cells to the left and above the cursor. The background color of erased cells
is colored according to the current SGR state.
If `n` is `2`, **erase the entire display**. This is the equivalent of
erase above (`n = 1`) and erase below (`n = 0`) both being executed.
If `n` is `3`, **erase only the scrollback region**. This does not affect
the visible display of the screen and does not move the cursor. The scrollback
region is the region of the terminal that is currently above the visible
area of the screen when the screen is scrolled completely to the bottom.
## Validation
### ED V-1: Simple Erase Below
```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[0J"
```
```
|ABC_____|
|Dc______|
|________|
```
### ED V-2: Erase Below SGR State
```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[41m"
printf "\033[0J"
```
```
|ABC_____|
|Dc______|
|________|
```
All the cells right and below of the cursor should be colored red.
### ED V-3: Erase Below Multi-Cell Character
```bash
printf "\033[1;1H" # move to top-left
printf "\033[0J" # clear screen
printf "AB橋C\n"
printf "DE橋F\n"
printf "GH橋I\n"
printf "\033[2;4H"
printf "\033[0J"
```
```
|AB橋C___|
|DE_c____|
|________|
```
### ED V-4: Simple Erase Above
```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[1J"
```
```
|________|
|_cF_____|
|GHI_____|
```
### ED V-5: Simple Erase Complete
```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[2J"
```
```
|________|
|_c______|
|________|
```