mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
implement auto-wrap, always enabled for now (ignores mode 7)
This commit is contained in:
@ -26,6 +26,11 @@ pub const Cell = struct {
|
|||||||
bold: u1 = 0,
|
bold: u1 = 0,
|
||||||
underline: u1 = 0,
|
underline: u1 = 0,
|
||||||
inverse: u1 = 0,
|
inverse: u1 = 0,
|
||||||
|
|
||||||
|
/// If 1, this line is soft-wrapped. Only the last cell in a row
|
||||||
|
/// should have this set. The first cell of the next row is actually
|
||||||
|
/// part of this row in raw input.
|
||||||
|
wrap: u1 = 0,
|
||||||
} = .{},
|
} = .{},
|
||||||
|
|
||||||
/// True if the cell should be skipped for drawing
|
/// True if the cell should be skipped for drawing
|
||||||
@ -152,9 +157,11 @@ pub fn resize(self: *Screen, alloc: Allocator, rows: usize, cols: usize) !void {
|
|||||||
self.rows = rows;
|
self.rows = rows;
|
||||||
self.cols = cols;
|
self.cols = cols;
|
||||||
|
|
||||||
|
// TODO: reflow due to soft wrap
|
||||||
|
|
||||||
// If we're increasing height, then copy all rows (start at 0).
|
// If we're increasing height, then copy all rows (start at 0).
|
||||||
// Otherwise start at the latest row that includes the bottom row,
|
// Otherwise start at the latest row that includes the bottom row,
|
||||||
// aka trip the top.
|
// aka strip the top.
|
||||||
var y: usize = if (rows >= old.rows) 0 else old.rows - rows;
|
var y: usize = if (rows >= old.rows) 0 else old.rows - rows;
|
||||||
const start = y;
|
const start = y;
|
||||||
const col_end = @minimum(old.cols, cols);
|
const col_end = @minimum(old.cols, cols);
|
||||||
|
@ -62,6 +62,9 @@ const Cursor = struct {
|
|||||||
|
|
||||||
// pen is the current cell styling to apply to new cells.
|
// pen is the current cell styling to apply to new cells.
|
||||||
pen: Screen.Cell = .{ .char = 0 },
|
pen: Screen.Cell = .{ .char = 0 },
|
||||||
|
|
||||||
|
// The last column flag (LCF) used to do soft wrapping.
|
||||||
|
pending_wrap: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Initialize a new terminal.
|
/// Initialize a new terminal.
|
||||||
@ -198,6 +201,18 @@ pub fn print(self: *Terminal, c: u21) !void {
|
|||||||
const tracy = trace(@src());
|
const tracy = trace(@src());
|
||||||
defer tracy.end();
|
defer tracy.end();
|
||||||
|
|
||||||
|
// If we're soft-wrapping, then handle that first.
|
||||||
|
if (self.cursor.pending_wrap) {
|
||||||
|
// Mark that the cell is wrapped, which guarantees that there is
|
||||||
|
// at least one cell after it in the next row.
|
||||||
|
const cell = self.screen.getCell(self.cursor.y, self.cursor.x);
|
||||||
|
cell.attrs.wrap = 1;
|
||||||
|
|
||||||
|
// Move to the next line
|
||||||
|
self.index();
|
||||||
|
self.cursor.x = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Build our cell
|
// Build our cell
|
||||||
const cell = self.screen.getCell(self.cursor.y, self.cursor.x);
|
const cell = self.screen.getCell(self.cursor.y, self.cursor.x);
|
||||||
cell.* = self.cursor.pen;
|
cell.* = self.cursor.pen;
|
||||||
@ -206,9 +221,12 @@ pub fn print(self: *Terminal, c: u21) !void {
|
|||||||
// Move the cursor
|
// Move the cursor
|
||||||
self.cursor.x += 1;
|
self.cursor.x += 1;
|
||||||
|
|
||||||
// TODO: wrap
|
// 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
|
||||||
|
// if we need to rather than check once.
|
||||||
if (self.cursor.x == self.cols) {
|
if (self.cursor.x == self.cols) {
|
||||||
self.cursor.x -= 1;
|
self.cursor.x -= 1;
|
||||||
|
self.cursor.pending_wrap = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,6 +264,9 @@ pub fn decaln(self: *Terminal) void {
|
|||||||
///
|
///
|
||||||
/// This unsets the pending wrap state without wrapping.
|
/// This unsets the pending wrap state without wrapping.
|
||||||
pub fn index(self: *Terminal) void {
|
pub fn index(self: *Terminal) void {
|
||||||
|
// Unset pending wrap state
|
||||||
|
self.cursor.pending_wrap = false;
|
||||||
|
|
||||||
// If we're at the end of the screen, scroll up. This is surprisingly
|
// If we're at the end of the screen, scroll up. This is surprisingly
|
||||||
// common because most terminals live with a full screen so we do this
|
// common because most terminals live with a full screen so we do this
|
||||||
// check first.
|
// check first.
|
||||||
@ -315,6 +336,9 @@ pub fn setCursorPos(self: *Terminal, row: usize, col: usize) void {
|
|||||||
|
|
||||||
self.cursor.x = @minimum(params.x_max, col) -| 1;
|
self.cursor.x = @minimum(params.x_max, col) -| 1;
|
||||||
self.cursor.y = @minimum(params.y_max, row + params.y_offset) -| 1;
|
self.cursor.y = @minimum(params.y_max, row + params.y_offset) -| 1;
|
||||||
|
|
||||||
|
// Unset pending wrap state
|
||||||
|
self.cursor.pending_wrap = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Erase the display.
|
/// Erase the display.
|
||||||
@ -548,9 +572,9 @@ pub fn carriageReturn(self: *Terminal) void {
|
|||||||
|
|
||||||
// TODO: left/right margin mode
|
// TODO: left/right margin mode
|
||||||
// TODO: origin mode
|
// TODO: origin mode
|
||||||
// TODO: wrap state
|
|
||||||
|
|
||||||
self.cursor.x = 0;
|
self.cursor.x = 0;
|
||||||
|
self.cursor.pending_wrap = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Linefeed moves the cursor to the next line.
|
/// Linefeed moves the cursor to the next line.
|
||||||
@ -721,6 +745,21 @@ test "Terminal: input with no control characters" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "Terminal: soft wrap" {
|
||||||
|
var t = try init(testing.allocator, 3, 80);
|
||||||
|
defer t.deinit(testing.allocator);
|
||||||
|
|
||||||
|
// Basic grid writing
|
||||||
|
for ("hello") |c| try t.print(c);
|
||||||
|
try testing.expectEqual(@as(usize, 1), t.cursor.y);
|
||||||
|
try testing.expectEqual(@as(usize, 2), t.cursor.x);
|
||||||
|
{
|
||||||
|
var str = try t.plainString(testing.allocator);
|
||||||
|
defer testing.allocator.free(str);
|
||||||
|
try testing.expectEqualStrings("hel\nlo", 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);
|
||||||
@ -739,6 +778,28 @@ test "Terminal: linefeed and carriage return" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "Terminal: linefeed unsets pending wrap" {
|
||||||
|
var t = try init(testing.allocator, 5, 80);
|
||||||
|
defer t.deinit(testing.allocator);
|
||||||
|
|
||||||
|
// Basic grid writing
|
||||||
|
for ("hello") |c| try t.print(c);
|
||||||
|
try testing.expect(t.cursor.pending_wrap == true);
|
||||||
|
t.linefeed();
|
||||||
|
try testing.expect(t.cursor.pending_wrap == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Terminal: carriage return unsets pending wrap" {
|
||||||
|
var t = try init(testing.allocator, 5, 80);
|
||||||
|
defer t.deinit(testing.allocator);
|
||||||
|
|
||||||
|
// Basic grid writing
|
||||||
|
for ("hello") |c| try t.print(c);
|
||||||
|
try testing.expect(t.cursor.pending_wrap == true);
|
||||||
|
t.carriageReturn();
|
||||||
|
try testing.expect(t.cursor.pending_wrap == false);
|
||||||
|
}
|
||||||
|
|
||||||
test "Terminal: backspace" {
|
test "Terminal: backspace" {
|
||||||
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);
|
||||||
@ -788,6 +849,13 @@ test "Terminal: setCursorPosition" {
|
|||||||
try testing.expectEqual(@as(usize, 79), t.cursor.x);
|
try testing.expectEqual(@as(usize, 79), t.cursor.x);
|
||||||
try testing.expectEqual(@as(usize, 79), t.cursor.y);
|
try testing.expectEqual(@as(usize, 79), t.cursor.y);
|
||||||
|
|
||||||
|
// Should reset pending wrap
|
||||||
|
t.setCursorPos(0, 80);
|
||||||
|
try t.print('c');
|
||||||
|
try testing.expect(t.cursor.pending_wrap);
|
||||||
|
t.setCursorPos(0, 80);
|
||||||
|
try testing.expect(!t.cursor.pending_wrap);
|
||||||
|
|
||||||
// Origin mode
|
// Origin mode
|
||||||
t.mode_origin = true;
|
t.mode_origin = true;
|
||||||
|
|
||||||
|
@ -48,11 +48,11 @@ pub fn Stream(comptime Handler: type) type {
|
|||||||
//log.debug("char: {x}", .{c});
|
//log.debug("char: {x}", .{c});
|
||||||
const actions = self.parser.next(c);
|
const actions = self.parser.next(c);
|
||||||
for (actions) |action_opt| {
|
for (actions) |action_opt| {
|
||||||
// if (action_opt) |action| {
|
if (action_opt) |action| {
|
||||||
// if (action != .print) {
|
if (action != .print) {
|
||||||
// log.info("action: {}", .{action});
|
log.info("action: {}", .{action});
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
switch (action_opt orelse continue) {
|
switch (action_opt orelse continue) {
|
||||||
.print => |p| if (@hasDecl(T, "print")) try self.handler.print(p),
|
.print => |p| if (@hasDecl(T, "print")) try self.handler.print(p),
|
||||||
.execute => |code| try self.execute(code),
|
.execute => |code| try self.execute(code),
|
||||||
|
Reference in New Issue
Block a user