208 lines
5.3 KiB
Zig
208 lines
5.3 KiB
Zig
const std = @import("std");
|
|
const Key = @import("key.zig");
|
|
pub const code_point = @import("code_point");
|
|
|
|
const log = std.log.scoped(.terminal);
|
|
|
|
pub const Size = struct {
|
|
cols: u16,
|
|
rows: u16,
|
|
};
|
|
|
|
pub const Position = struct {
|
|
col: u16,
|
|
row: u16,
|
|
};
|
|
|
|
// Ref: https://vt100.net/docs/vt510-rm/DECRPM.html
|
|
pub const ReportMode = enum {
|
|
not_recognized,
|
|
set,
|
|
reset,
|
|
permanently_set,
|
|
permanently_reset,
|
|
};
|
|
|
|
/// Gets number of rows and columns in the terminal
|
|
pub fn getTerminalSize() Size {
|
|
var ws: std.posix.winsize = undefined;
|
|
_ = std.posix.system.ioctl(std.posix.STDIN_FILENO, std.posix.T.IOCGWINSZ, &ws);
|
|
return .{ .cols = ws.ws_col, .rows = ws.ws_row };
|
|
}
|
|
|
|
pub fn saveScreen() !void {
|
|
_ = try std.posix.write(std.posix.STDIN_FILENO, "\x1b[?47h");
|
|
}
|
|
|
|
pub fn restoreScreen() !void {
|
|
_ = try std.posix.write(std.posix.STDIN_FILENO, "\x1b[?47l");
|
|
}
|
|
|
|
pub fn enterAltScreen() !void {
|
|
_ = try std.posix.write(std.posix.STDIN_FILENO, "\x1b[?1049h");
|
|
}
|
|
|
|
pub fn existAltScreen() !void {
|
|
_ = try std.posix.write(std.posix.STDIN_FILENO, "\x1b[?1049l");
|
|
}
|
|
|
|
pub fn clearScreen() !void {
|
|
_ = try std.posix.write(std.posix.STDIN_FILENO, "\x1b[2J");
|
|
}
|
|
|
|
pub fn setCursorPositionHome() !void {
|
|
_ = try std.posix.write(std.posix.STDIN_FILENO, "\x1b[H");
|
|
}
|
|
|
|
pub fn read(buf: []u8) !usize {
|
|
return try std.posix.read(std.posix.STDIN_FILENO, buf);
|
|
}
|
|
|
|
pub fn write(buf: []const u8) !usize {
|
|
return try std.posix.write(std.posix.STDIN_FILENO, buf);
|
|
}
|
|
|
|
pub fn getCursorPosition() !Position {
|
|
// Needs Raw mode (no wait for \n) to work properly cause
|
|
// control sequence will not be written without it.
|
|
_ = try std.posix.write(std.posix.STDIN_FILENO, "\x1b[6n");
|
|
|
|
var buf: [64]u8 = undefined;
|
|
|
|
// format: \x1b, "[", R1,..., Rn, ";", C1, ..., Cn, "R"
|
|
const len = try std.posix.read(std.posix.STDIN_FILENO, &buf);
|
|
|
|
if (!isCursorPosition(buf[0..len])) {
|
|
return error.InvalidValueReturned;
|
|
}
|
|
|
|
var row: [8]u8 = undefined;
|
|
var col: [8]u8 = undefined;
|
|
|
|
var ridx: u3 = 0;
|
|
var cidx: u3 = 0;
|
|
|
|
var is_parsing_cols = false;
|
|
for (2..(len - 1)) |i| {
|
|
const b = buf[i];
|
|
if (b == ';') {
|
|
is_parsing_cols = true;
|
|
continue;
|
|
}
|
|
|
|
if (b == 'R') {
|
|
break;
|
|
}
|
|
|
|
if (is_parsing_cols) {
|
|
col[cidx] = buf[i];
|
|
cidx += 1;
|
|
} else {
|
|
row[ridx] = buf[i];
|
|
ridx += 1;
|
|
}
|
|
}
|
|
|
|
return .{
|
|
.row = try std.fmt.parseInt(u16, row[0..ridx], 10),
|
|
.col = try std.fmt.parseInt(u16, col[0..cidx], 10),
|
|
};
|
|
}
|
|
|
|
/// Function is more of a heuristic as opposed
|
|
/// to an exact check.
|
|
pub fn isCursorPosition(buf: []u8) bool {
|
|
if (buf.len < 6) {
|
|
return false;
|
|
}
|
|
|
|
if (buf[0] != 27 or buf[1] != '[') {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Sets the following
|
|
/// - IXON: disables start/stop output flow (reads CTRL-S, CTRL-Q)
|
|
/// - ICRNL: disables CR to NL translation (reads CTRL-M)
|
|
/// - IEXTEN: disable implementation defined functions (reads CTRL-V, CTRL-O)
|
|
/// - ECHO: user input is not printed to terminal
|
|
/// - ICANON: read runs for every input (no waiting for `\n`)
|
|
/// - ISIG: disable QUIT, ISIG, SUSP.
|
|
///
|
|
/// `bak`: pointer to store termios struct backup before
|
|
/// altering, this is used to disable raw mode.
|
|
pub fn enableRawMode(bak: *std.posix.termios) !void {
|
|
var termios = try std.posix.tcgetattr(std.posix.STDIN_FILENO);
|
|
bak.* = termios;
|
|
|
|
// termios flags used by termios(3)
|
|
termios.iflag.IGNBRK = false;
|
|
termios.iflag.BRKINT = false;
|
|
termios.iflag.PARMRK = false;
|
|
termios.iflag.ISTRIP = false;
|
|
termios.iflag.INLCR = false;
|
|
termios.iflag.IGNCR = false;
|
|
termios.iflag.ICRNL = false;
|
|
termios.iflag.IXON = false;
|
|
|
|
// messes with output -> not used
|
|
// termios.oflag.OPOST = false;
|
|
|
|
termios.lflag.ECHO = false;
|
|
termios.lflag.ECHONL = false;
|
|
termios.lflag.ICANON = false;
|
|
termios.lflag.ISIG = false;
|
|
termios.lflag.IEXTEN = false;
|
|
|
|
termios.cflag.CSIZE = .CS8;
|
|
termios.cflag.PARENB = false;
|
|
|
|
termios.cc[@intFromEnum(std.posix.V.MIN)] = 1;
|
|
termios.cc[@intFromEnum(std.posix.V.TIME)] = 0;
|
|
|
|
try std.posix.tcsetattr(
|
|
std.posix.STDIN_FILENO,
|
|
.FLUSH,
|
|
termios,
|
|
);
|
|
}
|
|
|
|
/// Reverts `enableRawMode` to restore initial functionality.
|
|
pub fn disableRawMode(bak: *std.posix.termios) !void {
|
|
try std.posix.tcsetattr(
|
|
std.posix.STDIN_FILENO,
|
|
.FLUSH,
|
|
bak.*,
|
|
);
|
|
}
|
|
|
|
// Ref
|
|
pub fn canSynchornizeOutput() !bool {
|
|
// Needs Raw mode (no wait for \n) to work properly cause
|
|
// control sequence will not be written without it.
|
|
_ = try std.posix.write(std.posix.STDIN_FILENO, "\x1b[?2026$p");
|
|
|
|
var buf: [64]u8 = undefined;
|
|
|
|
// format: \x1b, "[", "?", "2", "0", "2", "6", ";", n, "$", "y"
|
|
const len = try std.posix.read(std.posix.STDIN_FILENO, &buf);
|
|
if (!std.mem.eql(u8, buf[0..len], "\x1b[?2026;") or len < 9) {
|
|
return false;
|
|
}
|
|
|
|
// Check value of n
|
|
return getReportMode(buf[8]) == .reset;
|
|
}
|
|
|
|
fn getReportMode(ps: u8) ReportMode {
|
|
return switch (ps) {
|
|
'1' => ReportMode.set,
|
|
'2' => ReportMode.reset,
|
|
'3' => ReportMode.permanently_set,
|
|
'4' => ReportMode.permanently_reset,
|
|
else => ReportMode.not_recognized,
|
|
};
|
|
}
|