mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
Merge pull request #533 from rockorager/dev
Charset / cursor state saving and retaining in the alternate screen
This commit is contained in:
@ -63,9 +63,27 @@ const point = @import("point.zig");
|
|||||||
const CircBuf = @import("circ_buf.zig").CircBuf;
|
const CircBuf = @import("circ_buf.zig").CircBuf;
|
||||||
const Selection = @import("Selection.zig");
|
const Selection = @import("Selection.zig");
|
||||||
const fastmem = @import("../fastmem.zig");
|
const fastmem = @import("../fastmem.zig");
|
||||||
|
const charsets = @import("charsets.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.screen);
|
const log = std.log.scoped(.screen);
|
||||||
|
|
||||||
|
/// State required for all charset operations.
|
||||||
|
const CharsetState = struct {
|
||||||
|
/// The list of graphical charsets by slot
|
||||||
|
charsets: CharsetArray = CharsetArray.initFill(charsets.Charset.utf8),
|
||||||
|
|
||||||
|
/// GL is the slot to use when using a 7-bit printable char (up to 127)
|
||||||
|
/// GR used for 8-bit printable chars.
|
||||||
|
gl: charsets.Slots = .G0,
|
||||||
|
gr: charsets.Slots = .G2,
|
||||||
|
|
||||||
|
/// Single shift where a slot is used for exactly one char.
|
||||||
|
single_shift: ?charsets.Slots = null,
|
||||||
|
|
||||||
|
/// An array to map a charset slot to a lookup table.
|
||||||
|
const CharsetArray = std.EnumArray(charsets.Slots, charsets.Charset);
|
||||||
|
};
|
||||||
|
|
||||||
/// Cursor represents the cursor state.
|
/// Cursor represents the cursor state.
|
||||||
pub const Cursor = struct {
|
pub const Cursor = struct {
|
||||||
/// x, y where the cursor currently exists (0-indexed). This x/y is
|
/// x, y where the cursor currently exists (0-indexed). This x/y is
|
||||||
@ -897,6 +915,18 @@ kitty_keyboard: kitty.KeyFlagStack = .{},
|
|||||||
/// Kitty graphics protocol state.
|
/// Kitty graphics protocol state.
|
||||||
kitty_images: kitty.graphics.ImageStorage = .{},
|
kitty_images: kitty.graphics.ImageStorage = .{},
|
||||||
|
|
||||||
|
/// The charset state
|
||||||
|
charset: CharsetState = .{},
|
||||||
|
|
||||||
|
/// The saved charset state. This state is saved / restored along with the
|
||||||
|
/// cursor state
|
||||||
|
saved_charset: CharsetState = .{},
|
||||||
|
|
||||||
|
/// The saved state of origin mode. This mode gets special handling in Screen
|
||||||
|
/// because it's state is saved/restored with the cursor and must have a state
|
||||||
|
/// independent to each screen (primary and alternate)
|
||||||
|
saved_origin_mode: bool = false,
|
||||||
|
|
||||||
/// Initialize a new screen.
|
/// Initialize a new screen.
|
||||||
pub fn init(
|
pub fn init(
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
|
@ -74,13 +74,6 @@ scrolling_region: ScrollingRegion,
|
|||||||
/// The last reported pwd, if any.
|
/// The last reported pwd, if any.
|
||||||
pwd: std.ArrayList(u8),
|
pwd: std.ArrayList(u8),
|
||||||
|
|
||||||
/// The charset state
|
|
||||||
charset: CharsetState = .{},
|
|
||||||
|
|
||||||
/// The saved charset state. This state is saved / restored along with the
|
|
||||||
/// cursor state
|
|
||||||
saved_charset: CharsetState = .{},
|
|
||||||
|
|
||||||
/// The color palette to use
|
/// The color palette to use
|
||||||
color_palette: color.Palette = color.default,
|
color_palette: color.Palette = color.default,
|
||||||
|
|
||||||
@ -111,23 +104,6 @@ flags: packed struct {
|
|||||||
mouse_format: MouseFormat = .x10,
|
mouse_format: MouseFormat = .x10,
|
||||||
} = .{},
|
} = .{},
|
||||||
|
|
||||||
/// State required for all charset operations.
|
|
||||||
const CharsetState = struct {
|
|
||||||
/// The list of graphical charsets by slot
|
|
||||||
charsets: CharsetArray = CharsetArray.initFill(charsets.Charset.utf8),
|
|
||||||
|
|
||||||
/// GL is the slot to use when using a 7-bit printable char (up to 127)
|
|
||||||
/// GR used for 8-bit printable chars.
|
|
||||||
gl: charsets.Slots = .G0,
|
|
||||||
gr: charsets.Slots = .G2,
|
|
||||||
|
|
||||||
/// Single shift where a slot is used for exactly one char.
|
|
||||||
single_shift: ?charsets.Slots = null,
|
|
||||||
|
|
||||||
/// An array to map a charset slot to a lookup table.
|
|
||||||
const CharsetArray = std.EnumArray(charsets.Slots, charsets.Charset);
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The event types that can be reported for mouse-related activities.
|
/// The event types that can be reported for mouse-related activities.
|
||||||
/// These are all mutually exclusive (hence in a single enum).
|
/// These are all mutually exclusive (hence in a single enum).
|
||||||
pub const MouseEvents = enum(u3) {
|
pub const MouseEvents = enum(u3) {
|
||||||
@ -227,8 +203,11 @@ pub fn alternateScreen(
|
|||||||
self.secondary_screen = old;
|
self.secondary_screen = old;
|
||||||
self.active_screen = .alternate;
|
self.active_screen = .alternate;
|
||||||
|
|
||||||
// Clear our pen
|
// Bring our pen with us
|
||||||
self.screen.cursor = .{};
|
self.screen.cursor = old.cursor;
|
||||||
|
|
||||||
|
// Bring our charset state with us
|
||||||
|
self.screen.charset = old.charset;
|
||||||
|
|
||||||
// Clear our selection
|
// Clear our selection
|
||||||
self.screen.selection = null;
|
self.screen.selection = null;
|
||||||
@ -423,8 +402,8 @@ fn plainString(self: *Terminal, alloc: Allocator) ![]const u8 {
|
|||||||
/// was already saved it is overwritten.
|
/// was already saved it is overwritten.
|
||||||
pub fn saveCursor(self: *Terminal) void {
|
pub fn saveCursor(self: *Terminal) void {
|
||||||
self.screen.saved_cursor = self.screen.cursor;
|
self.screen.saved_cursor = self.screen.cursor;
|
||||||
self.saved_charset = self.charset;
|
self.screen.saved_charset = self.screen.charset;
|
||||||
self.modes.save(.origin);
|
self.screen.saved_origin_mode = self.modes.get(.origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Restore cursor position and other state.
|
/// Restore cursor position and other state.
|
||||||
@ -433,8 +412,8 @@ pub fn saveCursor(self: *Terminal) void {
|
|||||||
/// If no save was done before values are reset to their initial values.
|
/// If no save was done before values are reset to their initial values.
|
||||||
pub fn restoreCursor(self: *Terminal) void {
|
pub fn restoreCursor(self: *Terminal) void {
|
||||||
self.screen.cursor = self.screen.saved_cursor;
|
self.screen.cursor = self.screen.saved_cursor;
|
||||||
self.charset = self.saved_charset;
|
self.screen.charset = self.screen.saved_charset;
|
||||||
_ = self.modes.restore(.origin);
|
self.modes.set(.origin, self.screen.saved_origin_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TODO: test
|
/// TODO: test
|
||||||
@ -583,7 +562,7 @@ pub fn setAttribute(self: *Terminal, attr: sgr.Attribute) !void {
|
|||||||
|
|
||||||
/// Set the charset into the given slot.
|
/// Set the charset into the given slot.
|
||||||
pub fn configureCharset(self: *Terminal, slot: charsets.Slots, set: charsets.Charset) void {
|
pub fn configureCharset(self: *Terminal, slot: charsets.Slots, set: charsets.Charset) void {
|
||||||
self.charset.charsets.set(slot, set);
|
self.screen.charset.charsets.set(slot, set);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Invoke the charset in slot into the active slot. If single is true,
|
/// Invoke the charset in slot into the active slot. If single is true,
|
||||||
@ -596,13 +575,13 @@ pub fn invokeCharset(
|
|||||||
) void {
|
) void {
|
||||||
if (single) {
|
if (single) {
|
||||||
assert(active == .GL);
|
assert(active == .GL);
|
||||||
self.charset.single_shift = slot;
|
self.screen.charset.single_shift = slot;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (active) {
|
switch (active) {
|
||||||
.GL => self.charset.gl = slot,
|
.GL => self.screen.charset.gl = slot,
|
||||||
.GR => self.charset.gr = slot,
|
.GR => self.screen.charset.gr = slot,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -769,11 +748,11 @@ fn printCell(self: *Terminal, unmapped_c: u21) *Screen.Cell {
|
|||||||
// TODO: non-utf8 handling, gr
|
// TODO: non-utf8 handling, gr
|
||||||
|
|
||||||
// If we're single shifting, then we use the key exactly once.
|
// If we're single shifting, then we use the key exactly once.
|
||||||
const key = if (self.charset.single_shift) |key_once| blk: {
|
const key = if (self.screen.charset.single_shift) |key_once| blk: {
|
||||||
self.charset.single_shift = null;
|
self.screen.charset.single_shift = null;
|
||||||
break :blk key_once;
|
break :blk key_once;
|
||||||
} else self.charset.gl;
|
} else self.screen.charset.gl;
|
||||||
const set = self.charset.charsets.get(key);
|
const set = self.screen.charset.charsets.get(key);
|
||||||
|
|
||||||
// UTF-8 or ASCII is used as-is
|
// UTF-8 or ASCII is used as-is
|
||||||
if (set == .utf8 or set == .ascii) break :c unmapped_c;
|
if (set == .utf8 or set == .ascii) break :c unmapped_c;
|
||||||
@ -1036,7 +1015,7 @@ pub fn eraseDisplay(
|
|||||||
defer tracy.end();
|
defer tracy.end();
|
||||||
|
|
||||||
// Erasing clears all attributes / colors _except_ the background
|
// Erasing clears all attributes / colors _except_ the background
|
||||||
self.screen.cursor.pen = if (!self.screen.cursor.pen.attrs.has_bg) .{} else .{
|
const pen: Screen.Cell = if (!self.screen.cursor.pen.attrs.has_bg) .{} else .{
|
||||||
.bg = self.screen.cursor.pen.bg,
|
.bg = self.screen.cursor.pen.bg,
|
||||||
.attrs = .{ .has_bg = true },
|
.attrs = .{ .has_bg = true },
|
||||||
};
|
};
|
||||||
@ -1047,7 +1026,7 @@ pub fn eraseDisplay(
|
|||||||
while (it.next()) |row| {
|
while (it.next()) |row| {
|
||||||
row.setWrapped(false);
|
row.setWrapped(false);
|
||||||
row.setDirty(true);
|
row.setDirty(true);
|
||||||
row.clear(self.screen.cursor.pen);
|
row.clear(pen);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unsets pending wrap state
|
// Unsets pending wrap state
|
||||||
@ -1066,7 +1045,7 @@ pub fn eraseDisplay(
|
|||||||
for (self.screen.cursor.x..self.cols) |x| {
|
for (self.screen.cursor.x..self.cols) |x| {
|
||||||
if (row.header().flags.grapheme) row.clearGraphemes(x);
|
if (row.header().flags.grapheme) row.clearGraphemes(x);
|
||||||
const cell = row.getCellPtr(x);
|
const cell = row.getCellPtr(x);
|
||||||
cell.* = self.screen.cursor.pen;
|
cell.* = pen;
|
||||||
cell.char = 0;
|
cell.char = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1079,7 +1058,7 @@ pub fn eraseDisplay(
|
|||||||
for (0..self.cols) |x| {
|
for (0..self.cols) |x| {
|
||||||
if (row.header().flags.grapheme) row.clearGraphemes(x);
|
if (row.header().flags.grapheme) row.clearGraphemes(x);
|
||||||
const cell = row.getCellPtr(x);
|
const cell = row.getCellPtr(x);
|
||||||
cell.* = self.screen.cursor.pen;
|
cell.* = pen;
|
||||||
cell.char = 0;
|
cell.char = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1093,7 +1072,7 @@ pub fn eraseDisplay(
|
|||||||
var x: usize = 0;
|
var x: usize = 0;
|
||||||
while (x <= self.screen.cursor.x) : (x += 1) {
|
while (x <= self.screen.cursor.x) : (x += 1) {
|
||||||
const cell = self.screen.getCellPtr(.active, self.screen.cursor.y, x);
|
const cell = self.screen.getCellPtr(.active, self.screen.cursor.y, x);
|
||||||
cell.* = self.screen.cursor.pen;
|
cell.* = pen;
|
||||||
cell.char = 0;
|
cell.char = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1103,7 +1082,7 @@ pub fn eraseDisplay(
|
|||||||
x = 0;
|
x = 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);
|
||||||
cell.* = self.screen.cursor.pen;
|
cell.* = pen;
|
||||||
cell.char = 0;
|
cell.char = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1151,6 +1130,9 @@ test "Terminal: eraseDisplay above" {
|
|||||||
try testing.expect(!cell.attrs.bold);
|
try testing.expect(!cell.attrs.bold);
|
||||||
try testing.expect(cell.attrs.has_bg);
|
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
|
// check that another cell got the correct bg
|
||||||
cell = t.screen.getCell(.active, 0, 1);
|
cell = t.screen.getCell(.active, 0, 1);
|
||||||
try testing.expect(cell.bg.eql(pink));
|
try testing.expect(cell.bg.eql(pink));
|
||||||
@ -1749,7 +1731,7 @@ pub fn kittyGraphics(
|
|||||||
/// Full reset
|
/// Full reset
|
||||||
pub fn fullReset(self: *Terminal, alloc: Allocator) void {
|
pub fn fullReset(self: *Terminal, alloc: Allocator) void {
|
||||||
self.primaryScreen(alloc, .{ .clear_on_exit = true, .cursor_save = true });
|
self.primaryScreen(alloc, .{ .clear_on_exit = true, .cursor_save = true });
|
||||||
self.charset = .{};
|
self.screen.charset = .{};
|
||||||
self.modes = .{};
|
self.modes = .{};
|
||||||
self.flags = .{};
|
self.flags = .{};
|
||||||
self.tabstops.reset(0);
|
self.tabstops.reset(0);
|
||||||
@ -2930,14 +2912,44 @@ test "Terminal: saveCursor" {
|
|||||||
defer t.deinit(alloc);
|
defer t.deinit(alloc);
|
||||||
|
|
||||||
t.screen.cursor.pen.attrs.bold = true;
|
t.screen.cursor.pen.attrs.bold = true;
|
||||||
t.charset.gr = .G3;
|
t.screen.charset.gr = .G3;
|
||||||
t.modes.set(.origin, true);
|
t.modes.set(.origin, true);
|
||||||
t.saveCursor();
|
t.saveCursor();
|
||||||
t.charset.gr = .G0;
|
t.screen.charset.gr = .G0;
|
||||||
t.screen.cursor.pen.attrs.bold = false;
|
t.screen.cursor.pen.attrs.bold = false;
|
||||||
t.modes.set(.origin, false);
|
t.modes.set(.origin, false);
|
||||||
t.restoreCursor();
|
t.restoreCursor();
|
||||||
try testing.expect(t.screen.cursor.pen.attrs.bold);
|
try testing.expect(t.screen.cursor.pen.attrs.bold);
|
||||||
try testing.expect(t.charset.gr == .G3);
|
try testing.expect(t.screen.charset.gr == .G3);
|
||||||
|
try testing.expect(t.modes.get(.origin));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Terminal: saveCursor with screen change" {
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var t = try init(alloc, 3, 3);
|
||||||
|
defer t.deinit(alloc);
|
||||||
|
|
||||||
|
t.screen.cursor.pen.attrs.bold = true;
|
||||||
|
t.screen.cursor.x = 2;
|
||||||
|
t.screen.charset.gr = .G3;
|
||||||
|
t.modes.set(.origin, true);
|
||||||
|
t.alternateScreen(alloc, .{
|
||||||
|
.cursor_save = true,
|
||||||
|
.clear_on_enter = true,
|
||||||
|
});
|
||||||
|
// make sure our cursor and charset have come with us
|
||||||
|
try testing.expect(t.screen.cursor.pen.attrs.bold);
|
||||||
|
try testing.expect(t.screen.cursor.x == 2);
|
||||||
|
try testing.expect(t.screen.charset.gr == .G3);
|
||||||
|
try testing.expect(t.modes.get(.origin));
|
||||||
|
t.screen.charset.gr = .G0;
|
||||||
|
t.screen.cursor.pen.attrs.bold = false;
|
||||||
|
t.modes.set(.origin, false);
|
||||||
|
t.primaryScreen(alloc, .{
|
||||||
|
.cursor_save = true,
|
||||||
|
.clear_on_enter = true,
|
||||||
|
});
|
||||||
|
try testing.expect(t.screen.cursor.pen.attrs.bold);
|
||||||
|
try testing.expect(t.screen.charset.gr == .G3);
|
||||||
try testing.expect(t.modes.get(.origin));
|
try testing.expect(t.modes.get(.origin));
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user