diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 22f259086..1966eb330 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -3158,6 +3158,7 @@ test "Terminal: horizontal tabs with left margin in origin mode" { } } +// X test "Terminal: horizontal tab back with cursor before left margin" { const alloc = testing.allocator; var t = try init(alloc, 20, 5); @@ -7148,6 +7149,7 @@ test "Terminal: scrollDown outside of scroll region" { } } +// X test "Terminal: scrollDown left/right scroll region" { const alloc = testing.allocator; var t = try init(alloc, 10, 10); @@ -7174,6 +7176,7 @@ test "Terminal: scrollDown left/right scroll region" { } } +// X test "Terminal: scrollDown outside of left/right scroll region" { const alloc = testing.allocator; var t = try init(alloc, 10, 10); @@ -7271,6 +7274,7 @@ test "Terminal: scrollUp top/bottom scroll region" { } } +// X test "Terminal: scrollUp left/right scroll region" { const alloc = testing.allocator; var t = try init(alloc, 10, 10); @@ -7338,6 +7342,7 @@ test "Terminal: scrollUp full top/bottom region" { } } +// X test "Terminal: scrollUp full top/bottomleft/right scroll region" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); @@ -7358,6 +7363,7 @@ test "Terminal: scrollUp full top/bottomleft/right scroll region" { } } +// X test "Terminal: tabClear single" { const alloc = testing.allocator; var t = try init(alloc, 30, 5); @@ -7370,6 +7376,7 @@ test "Terminal: tabClear single" { try testing.expectEqual(@as(usize, 16), t.screen.cursor.x); } +// X test "Terminal: tabClear all" { const alloc = testing.allocator; var t = try init(alloc, 30, 5); @@ -7381,6 +7388,7 @@ test "Terminal: tabClear all" { try testing.expectEqual(@as(usize, 29), t.screen.cursor.x); } +// X test "Terminal: printRepeat simple" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); @@ -7396,6 +7404,7 @@ test "Terminal: printRepeat simple" { } } +// X test "Terminal: printRepeat wrap" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); @@ -7411,6 +7420,7 @@ test "Terminal: printRepeat wrap" { } } +// X test "Terminal: printRepeat no previous character" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); @@ -7501,6 +7511,7 @@ test "Terminal: DECCOLM resets scroll region" { try testing.expectEqual(@as(usize, 79), t.scrolling_region.right); } +// X test "Terminal: printAttributes" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); diff --git a/src/terminal/new/Terminal.zig b/src/terminal/new/Terminal.zig index 03d0e0a90..b3f8dc35e 100644 --- a/src/terminal/new/Terminal.zig +++ b/src/terminal/new/Terminal.zig @@ -1709,6 +1709,91 @@ pub fn setAttribute(self: *Terminal, attr: sgr.Attribute) !void { try self.screen.setAttribute(attr); } +/// Print the active attributes as a string. This is used to respond to DECRQSS +/// requests. +/// +/// Boolean attributes are printed first, followed by foreground color, then +/// background color. Each attribute is separated by a semicolon. +pub fn printAttributes(self: *Terminal, buf: []u8) ![]const u8 { + var stream = std.io.fixedBufferStream(buf); + const writer = stream.writer(); + + // The SGR response always starts with a 0. See https://vt100.net/docs/vt510-rm/DECRPSS + try writer.writeByte('0'); + + const pen = self.screen.cursor.style; + var attrs = [_]u8{0} ** 8; + var i: usize = 0; + + if (pen.flags.bold) { + attrs[i] = '1'; + i += 1; + } + + if (pen.flags.faint) { + attrs[i] = '2'; + i += 1; + } + + if (pen.flags.italic) { + attrs[i] = '3'; + i += 1; + } + + if (pen.flags.underline != .none) { + attrs[i] = '4'; + i += 1; + } + + if (pen.flags.blink) { + attrs[i] = '5'; + i += 1; + } + + if (pen.flags.inverse) { + attrs[i] = '7'; + i += 1; + } + + if (pen.flags.invisible) { + attrs[i] = '8'; + i += 1; + } + + if (pen.flags.strikethrough) { + attrs[i] = '9'; + i += 1; + } + + for (attrs[0..i]) |c| { + try writer.print(";{c}", .{c}); + } + + switch (pen.fg_color) { + .none => {}, + .palette => |idx| if (idx >= 16) + try writer.print(";38:5:{}", .{idx}) + else if (idx >= 8) + try writer.print(";9{}", .{idx - 8}) + else + try writer.print(";3{}", .{idx}), + .rgb => |rgb| try writer.print(";38:2::{[r]}:{[g]}:{[b]}", rgb), + } + + switch (pen.bg_color) { + .none => {}, + .palette => |idx| if (idx >= 16) + try writer.print(";48:5:{}", .{idx}) + else if (idx >= 8) + try writer.print(";10{}", .{idx - 8}) + else + try writer.print(";4{}", .{idx}), + .rgb => |rgb| try writer.print(";48:2::{[r]}:{[g]}:{[b]}", rgb), + } + + return stream.getWritten(); +} + /// Return the current string value of the terminal. Newlines are /// encoded as "\n". This omits any formatting such as fg/bg. /// @@ -2654,6 +2739,26 @@ test "Terminal: horizontal tabs with left margin in origin mode" { } } +test "Terminal: horizontal tab back with cursor before left margin" { + const alloc = testing.allocator; + var t = try init(alloc, 20, 5); + defer t.deinit(alloc); + + t.modes.set(.origin, true); + t.saveCursor(); + t.modes.set(.enable_left_and_right_margin, true); + t.setLeftAndRightMargin(5, 0); + try t.restoreCursor(); + try t.horizontalTabBack(); + try t.print('X'); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("X", str); + } +} + test "Terminal: cursorPos resets wrap" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); @@ -3381,6 +3486,33 @@ test "Terminal: scrollUp top/bottom scroll region" { } } +test "Terminal: scrollUp 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); + const cursor = t.screen.cursor; + t.scrollUp(1); + try testing.expectEqual(cursor.x, t.screen.cursor.x); + try testing.expectEqual(cursor.y, t.screen.cursor.y); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("AEF423\nDHI756\nG 89", str); + } +} + test "Terminal: scrollUp preserves pending wrap" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); @@ -3420,6 +3552,26 @@ test "Terminal: scrollUp full top/bottom region" { } } +test "Terminal: scrollUp full top/bottomleft/right scroll region" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + try t.printString("top"); + t.setCursorPos(5, 1); + try t.printString("ABCDE"); + t.modes.set(.enable_left_and_right_margin, true); + t.setTopAndBottomMargin(2, 5); + t.setLeftAndRightMargin(2, 4); + t.scrollUp(4); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("top\n\n\n\nA E", str); + } +} + test "Terminal: scrollDown simple" { const alloc = testing.allocator; var t = try init(alloc, 5, 5); @@ -3471,6 +3623,60 @@ test "Terminal: scrollDown outside of scroll region" { } } +test "Terminal: scrollDown 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); + const cursor = t.screen.cursor; + t.scrollDown(1); + try testing.expectEqual(cursor.x, t.screen.cursor.x); + try testing.expectEqual(cursor.y, t.screen.cursor.y); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("A 23\nDBC156\nGEF489\n HI7", str); + } +} + +test "Terminal: scrollDown outside of 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(1, 1); + const cursor = t.screen.cursor; + t.scrollDown(1); + try testing.expectEqual(cursor.x, t.screen.cursor.x); + try testing.expectEqual(cursor.y, t.screen.cursor.y); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("A 23\nDBC156\nGEF489\n HI7", str); + } +} + test "Terminal: scrollDown preserves pending wrap" { const alloc = testing.allocator; var t = try init(alloc, 5, 10); @@ -6216,3 +6422,121 @@ test "Terminal: eraseLine complete protected requested" { try testing.expectEqualStrings(" X", str); } } + +test "Terminal: tabClear single" { + const alloc = testing.allocator; + var t = try init(alloc, 30, 5); + defer t.deinit(alloc); + + try t.horizontalTab(); + t.tabClear(.current); + t.setCursorPos(1, 1); + try t.horizontalTab(); + try testing.expectEqual(@as(usize, 16), t.screen.cursor.x); +} + +test "Terminal: tabClear all" { + const alloc = testing.allocator; + var t = try init(alloc, 30, 5); + defer t.deinit(alloc); + + t.tabClear(.all); + t.setCursorPos(1, 1); + try t.horizontalTab(); + try testing.expectEqual(@as(usize, 29), t.screen.cursor.x); +} + +test "Terminal: printRepeat simple" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + try t.printString("A"); + try t.printRepeat(1); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("AA", str); + } +} + +test "Terminal: printRepeat wrap" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + try t.printString(" A"); + try t.printRepeat(1); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings(" A\nA", str); + } +} + +test "Terminal: printRepeat no previous character" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + try t.printRepeat(1); + + { + const str = try t.plainString(testing.allocator); + defer testing.allocator.free(str); + try testing.expectEqualStrings("", str); + } +} + +test "Terminal: printAttributes" { + const alloc = testing.allocator; + var t = try init(alloc, 5, 5); + defer t.deinit(alloc); + + var storage: [64]u8 = undefined; + + { + try t.setAttribute(.{ .direct_color_fg = .{ .r = 1, .g = 2, .b = 3 } }); + defer t.setAttribute(.unset) catch unreachable; + const buf = try t.printAttributes(&storage); + try testing.expectEqualStrings("0;38:2::1:2:3", buf); + } + + { + try t.setAttribute(.bold); + try t.setAttribute(.{ .direct_color_bg = .{ .r = 1, .g = 2, .b = 3 } }); + defer t.setAttribute(.unset) catch unreachable; + const buf = try t.printAttributes(&storage); + try testing.expectEqualStrings("0;1;48:2::1:2:3", buf); + } + + { + try t.setAttribute(.bold); + try t.setAttribute(.faint); + try t.setAttribute(.italic); + try t.setAttribute(.{ .underline = .single }); + try t.setAttribute(.blink); + try t.setAttribute(.inverse); + try t.setAttribute(.invisible); + try t.setAttribute(.strikethrough); + try t.setAttribute(.{ .direct_color_fg = .{ .r = 100, .g = 200, .b = 255 } }); + try t.setAttribute(.{ .direct_color_bg = .{ .r = 101, .g = 102, .b = 103 } }); + defer t.setAttribute(.unset) catch unreachable; + const buf = try t.printAttributes(&storage); + try testing.expectEqualStrings("0;1;2;3;4;5;7;8;9;38:2::100:200:255;48:2::101:102:103", buf); + } + + { + try t.setAttribute(.{ .underline = .single }); + defer t.setAttribute(.unset) catch unreachable; + const buf = try t.printAttributes(&storage); + try testing.expectEqualStrings("0;4", buf); + } + + { + const buf = try t.printAttributes(&storage); + try testing.expectEqualStrings("0", buf); + } +}