Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions src/Vaxis.zig
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ pub const Options = struct {
/// requests. If not supplied, it won't be possible to request the system
/// clipboard
system_clipboard_allocator: ?std.mem.Allocator = null,
/// Leave the final primary-screen frame in scrollback on deinit.
preserve_screen_on_exit: bool = false,
};

/// the screen we write to
Expand Down Expand Up @@ -152,12 +154,17 @@ pub fn resetState(self: *Vaxis, tty: *IoWriter) !void {
try tty.writeAll(ctlseqs.erase_below_cursor);
try self.exitAltScreen(tty);
} else {
try tty.writeByte('\r');
var i: u16 = 0;
while (i < self.state.cursor.row) : (i += 1) {
try tty.writeAll(ctlseqs.ri);
if (self.opts.preserve_screen_on_exit) {
// Place the shell prompt on a fresh line without erasing the TUI frame.
try tty.writeAll("\r\n");
} else {
try tty.writeByte('\r');
var i: u16 = 0;
while (i < self.state.cursor.row) : (i += 1) {
try tty.writeAll(ctlseqs.ri);
}
try tty.writeAll(ctlseqs.erase_below_cursor);
}
try tty.writeAll(ctlseqs.erase_below_cursor);
}
if (self.state.color_scheme_updates) {
try tty.writeAll(ctlseqs.color_scheme_reset);
Expand Down Expand Up @@ -212,7 +219,8 @@ pub fn resize(
}
self.state.cursor.row = 0;
self.state.cursor.col = 0;
try tty.writeAll(ctlseqs.sgr_reset ++ ctlseqs.erase_below_cursor);
try tty.writeAll(ctlseqs.sgr_reset);
try tty.writeAll(ctlseqs.erase_below_cursor);
try tty.flush();
}

Expand Down
65 changes: 58 additions & 7 deletions src/vxfw/App.zig
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const std = @import("std");
const builtin = @import("builtin");
const vaxis = @import("../main.zig");
const vxfw = @import("vxfw.zig");

Expand All @@ -22,6 +23,8 @@ buffer: [1024]u8,
pub const Options = struct {
/// Frames per second
framerate: u8 = 60,
/// Render in the primary screen and preserve scrollback/history. Defaults to libvaxis behavior.
primary_screen_history: bool = false,
};

/// Create an application. We require stable pointers to do the set up, so this will create an App
Expand Down Expand Up @@ -54,6 +57,7 @@ pub fn deinit(self: *App) void {
pub fn run(self: *App, widget: vxfw.Widget, opts: Options) anyerror!void {
const tty = &self.tty;
const vx = &self.vx;
const use_primary_screen_history = opts.primary_screen_history;

var loop: EventLoop = .{ .tty = tty, .vaxis = vx };
try loop.start();
Expand All @@ -64,10 +68,19 @@ pub fn run(self: *App, widget: vxfw.Widget, opts: Options) anyerror!void {
// Also always initialize the app with a focus event
loop.postEvent(.focus_in);

try vx.enterAltScreen(tty.writer());
try vx.queryTerminal(tty.writer(), 1 * std.time.ns_per_s);
try vx.setBracketedPaste(tty.writer(), true);
try vx.subscribeToColorSchemeUpdates(tty.writer());
vx.opts.preserve_screen_on_exit = use_primary_screen_history;

if (!use_primary_screen_history) {
try vx.enterAltScreen(tty.writer());
}
if (builtin.os.tag == .windows) {
// Windows console input is event-driven and these OSC/CSI queries can render as garbage.
try vx.enableDetectedFeatures(tty.writer());
} else {
try vx.queryTerminal(tty.writer(), 1 * std.time.ns_per_s);
try vx.setBracketedPaste(tty.writer(), true);
try vx.subscribeToColorSchemeUpdates(tty.writer());
}

{
// This part deserves a comment. loop.init installs a signal handler for the tty. We wait to
Expand All @@ -82,6 +95,21 @@ pub fn run(self: *App, widget: vxfw.Widget, opts: Options) anyerror!void {

vxfw.DrawContext.init(vx.screen.width_method);

// Ensure we have a real initial size before the first layout. On Windows we cannot rely on
// receiving an initial winsize event.
const initial_ws = try getInitialWinsize(tty);
try vx.resize(self.allocator, tty.writer(), initial_ws);
if (use_primary_screen_history) {
// Reserve a dedicated canvas in primary screen so we don't overwrite existing shell history.
var row: u16 = 0;
while (row < vx.screen.height) : (row += 1) {
try tty.writer().writeAll("\r\n");
}
vx.state.cursor.row = vx.screen.height;
vx.state.cursor.col = 0;
try tty.writer().flush();
}

const framerate: u64 = if (opts.framerate > 0) opts.framerate else 60;
// Calculate tick rate
const tick_ms: u64 = @divFloor(std.time.ms_per_s, framerate);
Expand All @@ -105,7 +133,7 @@ pub fn run(self: *App, widget: vxfw.Widget, opts: Options) anyerror!void {
.phase = .capturing,
.cmds = vxfw.CommandList{},
.consume_event = false,
.redraw = false,
.redraw = true,
.quit = false,
};
defer ctx.cmds.deinit(self.allocator);
Expand Down Expand Up @@ -210,6 +238,8 @@ fn doLayout(
arena: *std.heap.ArenaAllocator,
) !vxfw.Surface {
const vx = &self.vx;
const screen_w: u16 = @max(@as(u16, 1), vx.screen.width);
const screen_h: u16 = @max(@as(u16, 1), vx.screen.height);

const draw_context: vxfw.DrawContext = .{
.arena = arena.allocator(),
Expand All @@ -219,13 +249,34 @@ fn doLayout(
.height = @intCast(vx.screen.height),
},
.cell_size = .{
.width = vx.screen.width_pix / vx.screen.width,
.height = vx.screen.height_pix / vx.screen.height,
.width = vx.screen.width_pix / screen_w,
.height = vx.screen.height_pix / screen_h,
},
};
return widget.draw(draw_context);
}

fn getInitialWinsize(tty: *vaxis.Tty) !vaxis.Winsize {
switch (builtin.os.tag) {
.windows => {
const windows = std.os.windows;
var console_info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
if (windows.kernel32.GetConsoleScreenBufferInfo(tty.stdout, &console_info) == 0)
return windows.unexpectedError(windows.kernel32.GetLastError());
const window_rect = console_info.srWindow;
const width = window_rect.Right - window_rect.Left + 1;
const height = window_rect.Bottom - window_rect.Top + 1;
return .{
.cols = @intCast(width),
.rows = @intCast(height),
.x_pixel = 0,
.y_pixel = 0,
};
},
else => return vaxis.Tty.getWinsize(tty.fd),
}
}

fn render(
self: *App,
surface: vxfw.Surface,
Expand Down