Merge pull request #656 from mitchellh/enable-lr

Enable Left/Right Margin Mode
This commit is contained in:
Mitchell Hashimoto
2023-10-10 16:26:30 -07:00
committed by GitHub
3 changed files with 76 additions and 13 deletions

View File

@ -717,6 +717,12 @@ pub fn print(self: *Terminal, c: u21) !void {
self.insertBlanks(width); self.insertBlanks(width);
} }
// Our right margin depends where our cursor is now.
const right_limit = if (self.screen.cursor.x > self.scrolling_region.right)
self.cols
else
self.scrolling_region.right + 1;
switch (width) { switch (width) {
// Single cell is very easy: just write in the cell // Single cell is very easy: just write in the cell
1 => _ = @call(.always_inline, printCell, .{ self, c }), 1 => _ = @call(.always_inline, printCell, .{ self, c }),
@ -725,11 +731,11 @@ pub fn print(self: *Terminal, c: u21) !void {
// using two cells: the first is flagged "wide" and has the // using two cells: the first is flagged "wide" and has the
// wide char. The second is guaranteed to be a spacer if // wide char. The second is guaranteed to be a spacer if
// we're not at the end of the line. // we're not at the end of the line.
2 => if (self.cols > 1) { 2 => if ((right_limit - self.scrolling_region.left) > 1) {
// If we don't have space for the wide char, we need // If we don't have space for the wide char, we need
// to insert spacers and wrap. Then we just print the wide // to insert spacers and wrap. Then we just print the wide
// char as normal. // char as normal.
if (self.screen.cursor.x == self.cols - 1) { if (self.screen.cursor.x == right_limit - 1) {
const spacer_head = self.printCell(' '); const spacer_head = self.printCell(' ');
spacer_head.attrs.wide_spacer_head = true; spacer_head.attrs.wide_spacer_head = true;
try self.printWrap(); try self.printWrap();
@ -757,7 +763,7 @@ pub fn print(self: *Terminal, c: u21) !void {
// If we're at the column limit, then we need to wrap the next time. // If we're at the column limit, then we need to wrap the next time.
// This is unlikely so we do the increment above and decrement here // This is unlikely so we do the increment above and decrement here
// if we need to rather than check once. // if we need to rather than check once.
if (self.screen.cursor.x == self.cols) { if (self.screen.cursor.x == right_limit) {
self.screen.cursor.x -= 1; self.screen.cursor.x -= 1;
self.screen.cursor.pending_wrap = true; self.screen.cursor.pending_wrap = true;
} }
@ -835,7 +841,7 @@ fn printWrap(self: *Terminal) !void {
// Move to the next line // Move to the next line
try self.index(); try self.index();
self.screen.cursor.x = 0; self.screen.cursor.x = self.scrolling_region.left;
} }
fn clearWideSpacerHead(self: *Terminal) void { fn clearWideSpacerHead(self: *Terminal) void {
@ -1329,12 +1335,13 @@ pub fn cursorLeft(self: *Terminal, count_req: usize) void {
} }
// The margins we can move to. // The margins we can move to.
// TODO: if cursor is left of the left margin, assume left margin to be 0.
// verified with xterm. don't forget when left margins are implemented!
const left_margin = 0;
const right_margin = self.cols - 1;
const top = self.scrolling_region.top; const top = self.scrolling_region.top;
const bottom = self.scrolling_region.bottom; const bottom = self.scrolling_region.bottom;
const right_margin = self.scrolling_region.right;
const left_margin = if (self.screen.cursor.x < self.scrolling_region.left)
0
else
self.scrolling_region.left;
while (true) { while (true) {
// We can move at most to the left margin. // We can move at most to the left margin.
@ -1820,7 +1827,7 @@ pub fn setLeftAndRightMargin(self: *Terminal, left_req: usize, right_req: usize)
if (!self.modes.get(.enable_left_and_right_margin)) return; if (!self.modes.get(.enable_left_and_right_margin)) return;
const left = @max(1, left_req); const left = @max(1, left_req);
const right = @min(self.rows, if (right_req == 0) self.rows else right_req); const right = @min(self.cols, if (right_req == 0) self.cols else right_req);
if (left >= right) return; if (left >= right) return;
self.scrolling_region.left = left - 1; self.scrolling_region.left = left - 1;
@ -2227,6 +2234,57 @@ test "Terminal: print invoke charset single" {
} }
} }
test "Terminal: print right margin wrap" {
var t = try init(testing.allocator, 10, 5);
defer t.deinit(testing.allocator);
try t.printString("123456789");
t.modes.set(.enable_left_and_right_margin, true);
t.setLeftAndRightMargin(3, 5);
t.setCursorPos(1, 5);
try t.printString("XY");
{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("1234X6789\n Y", str);
}
}
test "Terminal: print right margin outside" {
var t = try init(testing.allocator, 10, 5);
defer t.deinit(testing.allocator);
try t.printString("123456789");
t.modes.set(.enable_left_and_right_margin, true);
t.setLeftAndRightMargin(3, 5);
t.setCursorPos(1, 6);
try t.printString("XY");
{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("12345XY89", str);
}
}
test "Terminal: print right margin outside wrap" {
var t = try init(testing.allocator, 10, 5);
defer t.deinit(testing.allocator);
try t.printString("123456789");
t.modes.set(.enable_left_and_right_margin, true);
t.setLeftAndRightMargin(3, 5);
t.setCursorPos(1, 10);
try t.printString("XY");
{
var str = try t.plainString(testing.allocator);
defer testing.allocator.free(str);
try testing.expectEqualStrings("123456789X\n Y", str);
}
}
test "Terminal: linefeed and carriage return" { test "Terminal: linefeed and carriage return" {
var t = try init(testing.allocator, 80, 80); var t = try init(testing.allocator, 80, 80);
defer t.deinit(testing.allocator); defer t.deinit(testing.allocator);
@ -2702,6 +2760,8 @@ test "Terminal: setLeftAndRightMargin left only" {
try t.printString("GHI"); try t.printString("GHI");
t.modes.set(.enable_left_and_right_margin, true); t.modes.set(.enable_left_and_right_margin, true);
t.setLeftAndRightMargin(2, 0); t.setLeftAndRightMargin(2, 0);
try testing.expectEqual(@as(usize, 1), t.scrolling_region.left);
try testing.expectEqual(@as(usize, t.cols - 1), t.scrolling_region.right);
t.setCursorPos(1, 2); t.setCursorPos(1, 2);
try t.insertLines(1); try t.insertLines(1);

View File

@ -184,6 +184,7 @@ const entries: []const ModeEntry = &.{
.{ .name = "enable_mode_3", .value = 40 }, .{ .name = "enable_mode_3", .value = 40 },
.{ .name = "reverse_wrap", .value = 45 }, .{ .name = "reverse_wrap", .value = 45 },
.{ .name = "keypad_keys", .value = 66 }, .{ .name = "keypad_keys", .value = 66 },
.{ .name = "enable_left_and_right_margin", .value = 69 },
.{ .name = "mouse_event_normal", .value = 1000 }, .{ .name = "mouse_event_normal", .value = 1000 },
.{ .name = "mouse_event_button", .value = 1002 }, .{ .name = "mouse_event_button", .value = 1002 },
.{ .name = "mouse_event_any", .value = 1003 }, .{ .name = "mouse_event_any", .value = 1003 },
@ -200,10 +201,6 @@ const entries: []const ModeEntry = &.{
.{ .name = "bracketed_paste", .value = 2004 }, .{ .name = "bracketed_paste", .value = 2004 },
.{ .name = "synchronized_output", .value = 2026 }, .{ .name = "synchronized_output", .value = 2026 },
.{ .name = "grapheme_cluster", .value = 2027 }, .{ .name = "grapheme_cluster", .value = 2027 },
// Disabled for now until we ensure we get left/right margins working
// correctly in all sequences.
.{ .name = "enable_left_and_right_margin", .value = 69, .disabled = true },
}; };
test { test {

View File

@ -141,6 +141,12 @@ pub const ghostty: Source = .{
.{ .name = "XR", .value = .{ .string = "\\E[>0q" } }, .{ .name = "XR", .value = .{ .string = "\\E[>0q" } },
.{ .name = "xr", .value = .{ .string = "\\EP>\\|[ -~]+a\\E\\\\" } }, .{ .name = "xr", .value = .{ .string = "\\EP>\\|[ -~]+a\\E\\\\" } },
// DECSLRM (Left/Right Margins)
.{ .name = "Enmg", .value = .{ .string = "\\E[?69h" } },
.{ .name = "Dsmg", .value = .{ .string = "\\E[?69l" } },
.{ .name = "Clmg", .value = .{ .string = "\\E[s" } },
.{ .name = "Cmg", .value = .{ .string = "\\E[%i%p1%d;%p2%ds" } },
// These are all capabilities that should be pretty straightforward // These are all capabilities that should be pretty straightforward
// and map to input sequences. // and map to input sequences.
.{ .name = "bel", .value = .{ .string = "^G" } }, .{ .name = "bel", .value = .{ .string = "^G" } },