mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-24 12:46:10 +03:00
terminal/new: primary/alt screen
This commit is contained in:
@ -4939,6 +4939,7 @@ test "Terminal: cursorIsAtPrompt" {
|
||||
try testing.expect(t.cursorIsAtPrompt());
|
||||
}
|
||||
|
||||
// X
|
||||
test "Terminal: cursorIsAtPrompt alternate screen" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 3, 2);
|
||||
@ -5491,6 +5492,7 @@ test "Terminal: saveCursor" {
|
||||
try testing.expect(t.modes.get(.origin));
|
||||
}
|
||||
|
||||
// X
|
||||
test "Terminal: saveCursor with screen change" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 3, 3);
|
||||
|
@ -8,6 +8,7 @@ const charsets = @import("../charsets.zig");
|
||||
const kitty = @import("../kitty.zig");
|
||||
const sgr = @import("../sgr.zig");
|
||||
const unicode = @import("../../unicode/main.zig");
|
||||
const Selection = @import("../Selection.zig");
|
||||
const PageList = @import("PageList.zig");
|
||||
const pagepkg = @import("page.zig");
|
||||
const point = @import("point.zig");
|
||||
@ -30,6 +31,9 @@ cursor: Cursor,
|
||||
/// The saved cursor
|
||||
saved_cursor: ?SavedCursor = null,
|
||||
|
||||
/// The selection for this screen (if any).
|
||||
selection: ?Selection = null,
|
||||
|
||||
/// The charset state
|
||||
charset: CharsetState = .{},
|
||||
|
||||
|
@ -1950,6 +1950,98 @@ pub fn printAttributes(self: *Terminal, buf: []u8) ![]const u8 {
|
||||
return stream.getWritten();
|
||||
}
|
||||
|
||||
/// Options for switching to the alternate screen.
|
||||
pub const AlternateScreenOptions = struct {
|
||||
cursor_save: bool = false,
|
||||
clear_on_enter: bool = false,
|
||||
clear_on_exit: bool = false,
|
||||
};
|
||||
|
||||
/// Switch to the alternate screen buffer.
|
||||
///
|
||||
/// The alternate screen buffer:
|
||||
/// * has its own grid
|
||||
/// * has its own cursor state (included saved cursor)
|
||||
/// * does not support scrollback
|
||||
///
|
||||
pub fn alternateScreen(
|
||||
self: *Terminal,
|
||||
options: AlternateScreenOptions,
|
||||
) void {
|
||||
//log.info("alt screen active={} options={} cursor={}", .{ self.active_screen, options, self.screen.cursor });
|
||||
|
||||
// TODO: test
|
||||
// TODO(mitchellh): what happens if we enter alternate screen multiple times?
|
||||
// for now, we ignore...
|
||||
if (self.active_screen == .alternate) return;
|
||||
|
||||
// If we requested cursor save, we save the cursor in the primary screen
|
||||
if (options.cursor_save) self.saveCursor();
|
||||
|
||||
// Switch the screens
|
||||
const old = self.screen;
|
||||
self.screen = self.secondary_screen;
|
||||
self.secondary_screen = old;
|
||||
self.active_screen = .alternate;
|
||||
|
||||
// Bring our charset state with us
|
||||
self.screen.charset = old.charset;
|
||||
|
||||
// Clear our selection
|
||||
self.screen.selection = null;
|
||||
|
||||
// Mark kitty images as dirty so they redraw
|
||||
self.screen.kitty_images.dirty = true;
|
||||
|
||||
// Bring our pen with us
|
||||
self.screen.cursor = old.cursor;
|
||||
self.screen.cursor.style_id = 0;
|
||||
self.screen.cursor.style_ref = null;
|
||||
self.screen.cursorAbsolute(old.cursor.x, old.cursor.y);
|
||||
|
||||
if (options.clear_on_enter) {
|
||||
self.eraseDisplay(.complete, false);
|
||||
}
|
||||
|
||||
// Update any style ref after we erase the display so we definitely have space
|
||||
self.screen.manualStyleUpdate() catch |err| {
|
||||
log.warn("style update failed entering alt screen err={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
/// Switch back to the primary screen (reset alternate screen mode).
|
||||
pub fn primaryScreen(
|
||||
self: *Terminal,
|
||||
options: AlternateScreenOptions,
|
||||
) void {
|
||||
//log.info("primary screen active={} options={}", .{ self.active_screen, options });
|
||||
|
||||
// TODO: test
|
||||
// TODO(mitchellh): what happens if we enter alternate screen multiple times?
|
||||
if (self.active_screen == .primary) return;
|
||||
|
||||
if (options.clear_on_exit) self.eraseDisplay(.complete, false);
|
||||
|
||||
// Switch the screens
|
||||
const old = self.screen;
|
||||
self.screen = self.secondary_screen;
|
||||
self.secondary_screen = old;
|
||||
self.active_screen = .primary;
|
||||
|
||||
// Clear our selection
|
||||
self.screen.selection = null;
|
||||
|
||||
// Mark kitty images as dirty so they redraw
|
||||
self.screen.kitty_images.dirty = true;
|
||||
|
||||
// Restore the cursor from the primary screen. This should not
|
||||
// fail because we should not have to allocate memory since swapping
|
||||
// screens does not create new cursors.
|
||||
if (options.cursor_save) self.restoreCursor() catch |err| {
|
||||
log.warn("restore cursor on primary screen failed err={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
/// Return the current string value of the terminal. Newlines are
|
||||
/// encoded as "\n". This omits any formatting such as fg/bg.
|
||||
///
|
||||
@ -6143,6 +6235,36 @@ test "Terminal: saveCursor" {
|
||||
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);
|
||||
|
||||
try t.setAttribute(.{ .bold = {} });
|
||||
t.screen.cursor.x = 2;
|
||||
t.screen.charset.gr = .G3;
|
||||
t.modes.set(.origin, true);
|
||||
t.alternateScreen(.{
|
||||
.cursor_save = true,
|
||||
.clear_on_enter = true,
|
||||
});
|
||||
// make sure our cursor and charset have come with us
|
||||
try testing.expect(t.screen.cursor.style.flags.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;
|
||||
try t.setAttribute(.{ .reset_bold = {} });
|
||||
t.modes.set(.origin, false);
|
||||
t.primaryScreen(.{
|
||||
.cursor_save = true,
|
||||
.clear_on_enter = true,
|
||||
});
|
||||
try testing.expect(t.screen.cursor.style.flags.bold);
|
||||
try testing.expect(t.screen.charset.gr == .G3);
|
||||
try testing.expect(t.modes.get(.origin));
|
||||
}
|
||||
|
||||
test "Terminal: saveCursor position" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 10, 5);
|
||||
@ -7251,3 +7373,19 @@ test "Terminal: cursorIsAtPrompt" {
|
||||
t.markSemanticPrompt(.prompt);
|
||||
try testing.expect(t.cursorIsAtPrompt());
|
||||
}
|
||||
|
||||
test "Terminal: cursorIsAtPrompt alternate screen" {
|
||||
const alloc = testing.allocator;
|
||||
var t = try init(alloc, 3, 2);
|
||||
defer t.deinit(alloc);
|
||||
|
||||
try testing.expect(!t.cursorIsAtPrompt());
|
||||
t.markSemanticPrompt(.prompt);
|
||||
try testing.expect(t.cursorIsAtPrompt());
|
||||
|
||||
// Secondary screen is never a prompt
|
||||
t.alternateScreen(.{});
|
||||
try testing.expect(!t.cursorIsAtPrompt());
|
||||
t.markSemanticPrompt(.prompt);
|
||||
try testing.expect(!t.cursorIsAtPrompt());
|
||||
}
|
||||
|
Reference in New Issue
Block a user