Very basic functionality
This commit is contained in:
parent
4fde566f6a
commit
5e52041e67
54
src/borders.zig
Normal file
54
src/borders.zig
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
pub const Border = struct {
|
||||||
|
top_left: u21,
|
||||||
|
top_right: u21,
|
||||||
|
bottom_left: u21,
|
||||||
|
bottom_right: u21,
|
||||||
|
top: u21,
|
||||||
|
bottom: u21,
|
||||||
|
left: u21,
|
||||||
|
right: u21,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const BasicBorder = Border{
|
||||||
|
.top_left = '┌',
|
||||||
|
.top_right = '┐',
|
||||||
|
.bottom_left = '└',
|
||||||
|
.bottom_right = '┘',
|
||||||
|
.top = '─',
|
||||||
|
.bottom = '─',
|
||||||
|
.left = '│',
|
||||||
|
.right = '│',
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const BoldBorder = Border{
|
||||||
|
.top_left = '┏',
|
||||||
|
.top_right = '┓',
|
||||||
|
.bottom_left = '┗',
|
||||||
|
.bottom_right = '┛',
|
||||||
|
.top = '━',
|
||||||
|
.bottom = '━',
|
||||||
|
.left = '┃',
|
||||||
|
.right = '┃',
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const DoubleBorder = Border{
|
||||||
|
.top_left = '╔',
|
||||||
|
.top_right = '╗',
|
||||||
|
.bottom_left = '╚',
|
||||||
|
.bottom_right = '╝',
|
||||||
|
.top = '═',
|
||||||
|
.bottom = '═',
|
||||||
|
.left = '║',
|
||||||
|
.right = '║',
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const BigBorder = Border{
|
||||||
|
.top_left = '▛',
|
||||||
|
.top_right = '▜',
|
||||||
|
.bottom_left = '▙',
|
||||||
|
.bottom_right = '▟',
|
||||||
|
.top = '▀',
|
||||||
|
.bottom = '▄',
|
||||||
|
.left = '▌',
|
||||||
|
.right = '▐',
|
||||||
|
};
|
34
src/colors.zig
Normal file
34
src/colors.zig
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
pub const Color = struct {
|
||||||
|
type: enum { Default, RGB },
|
||||||
|
red: u8,
|
||||||
|
green: u8,
|
||||||
|
blue: u8,
|
||||||
|
|
||||||
|
pub fn equal(self: Color, other: Color) bool {
|
||||||
|
if (self.type != other.type) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.type == .Default) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.red == other.red and self.green == other.green and self.blue == self.blue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Default = Color{
|
||||||
|
.type = .Default,
|
||||||
|
.red = undefined,
|
||||||
|
.green = undefined,
|
||||||
|
.blue = undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub inline fn RGB(red: u8, green: u8, blue: u8) Color {
|
||||||
|
return .{
|
||||||
|
.type = .RGB,
|
||||||
|
.red = red,
|
||||||
|
.green = green,
|
||||||
|
.blue = blue,
|
||||||
|
};
|
||||||
|
}
|
41
src/dimensions.zig
Normal file
41
src/dimensions.zig
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
pub const Anchor = enum {
|
||||||
|
TopLeft,
|
||||||
|
TopCenter,
|
||||||
|
TopRight,
|
||||||
|
CenterLeft,
|
||||||
|
Center,
|
||||||
|
CenterRight,
|
||||||
|
BottomLeft,
|
||||||
|
BottomCenter,
|
||||||
|
BottomRight,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Position = struct {
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Size = struct {
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Dimensions = struct {
|
||||||
|
size_type: enum { Relative, Absolute },
|
||||||
|
size: Size,
|
||||||
|
anchor: Anchor,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const CalculatedDimensions = struct {
|
||||||
|
size: Size,
|
||||||
|
pos: Position,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Fill = Dimensions{
|
||||||
|
.size_type = .Relative,
|
||||||
|
.anchor = .TopLeft,
|
||||||
|
.size = .{
|
||||||
|
.width = 100,
|
||||||
|
.height = 100,
|
||||||
|
},
|
||||||
|
};
|
83
src/main.zig
83
src/main.zig
@ -1,24 +1,73 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const term = @import("term.zig");
|
||||||
|
const borders = @import("borders.zig");
|
||||||
|
const pane = @import("pane.zig");
|
||||||
|
const color = @import("colors.zig");
|
||||||
|
const dim = @import("dimensions.zig");
|
||||||
|
|
||||||
|
var term_io: term.TermIO = undefined;
|
||||||
|
|
||||||
|
pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn {
|
||||||
|
pane.cleanup(&term_io);
|
||||||
|
std.builtin.default_panic(msg, error_return_trace, ret_addr);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
// Prints to stderr (it's a shortcut based on `std.io.getStdErr()`)
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
std.debug.print("All your {s} are belong to us.\n", .{"codebase"});
|
const allocator = gpa.allocator();
|
||||||
|
defer {
|
||||||
|
if (gpa.deinit() == .leak) {
|
||||||
|
std.debug.print("Memory was leaked D:\n", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// stdout is for the actual output of your application, for example if you
|
term_io = try term.getTermIO();
|
||||||
// are implementing gzip, then only the compressed bytes should be sent to
|
|
||||||
// stdout, not any debugging messages.
|
|
||||||
const stdout_file = std.io.getStdOut().writer();
|
|
||||||
var bw = std.io.bufferedWriter(stdout_file);
|
|
||||||
const stdout = bw.writer();
|
|
||||||
|
|
||||||
try stdout.print("Run `zig build test` to run the tests.\n", .{});
|
try pane.init(&term_io);
|
||||||
|
defer pane.cleanup(&term_io);
|
||||||
|
|
||||||
try bw.flush(); // don't forget to flush!
|
var top = pane.Pane{
|
||||||
}
|
.dimensions = dim.Fill,
|
||||||
|
.border = borders.BoldBorder,
|
||||||
test "simple test" {
|
.children = std.ArrayList(*pane.Pane).init(allocator),
|
||||||
var list = std.ArrayList(i32).init(std.testing.allocator);
|
.background = color.RGB(30, 30, 30),
|
||||||
defer list.deinit(); // try commenting this out and see if zig detects the memory leak!
|
.foreground = color.RGB(0, 255, 0),
|
||||||
try list.append(42);
|
};
|
||||||
try std.testing.expectEqual(@as(i32, 42), list.pop());
|
|
||||||
|
var child = pane.Pane{
|
||||||
|
.parent = &top,
|
||||||
|
.children = null,
|
||||||
|
.dimensions = .{
|
||||||
|
.anchor = .Center,
|
||||||
|
.size_type = .Relative,
|
||||||
|
.size = .{ .width = 50, .height = 50 },
|
||||||
|
},
|
||||||
|
.border = borders.BoldBorder,
|
||||||
|
.background = color.RGB(125, 0, 125),
|
||||||
|
.foreground = color.RGB(255, 125, 10),
|
||||||
|
};
|
||||||
|
|
||||||
|
pane.top_pane = ⊤
|
||||||
|
try top.children.?.append(&child);
|
||||||
|
|
||||||
|
const childWriter = child.writer(&term_io);
|
||||||
|
var key = term_io.getKey(false);
|
||||||
|
while (key.value != 113 and !pane.should_exit) {
|
||||||
|
try pane.tick(&term_io);
|
||||||
|
if (key.type == .ASCII and key.value == 111) {
|
||||||
|
try std.fmt.format(childWriter, "\x1b[2J", .{});
|
||||||
|
try term_io.flush();
|
||||||
|
}
|
||||||
|
if (key.type == .ASCII and key.value == 110) {
|
||||||
|
try std.fmt.format(childWriter, "Hello", .{});
|
||||||
|
try term_io.flush();
|
||||||
|
}
|
||||||
|
if (key.type == .ASCII and key.value == 109) {
|
||||||
|
try std.fmt.format(childWriter, "Hello\n", .{});
|
||||||
|
try term_io.flush();
|
||||||
|
}
|
||||||
|
key = term_io.getKey(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
top.children.?.deinit();
|
||||||
}
|
}
|
||||||
|
220
src/pane.zig
Normal file
220
src/pane.zig
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const color = @import("colors.zig");
|
||||||
|
const dim = @import("dimensions.zig");
|
||||||
|
const term = @import("term.zig");
|
||||||
|
const Border = @import("borders.zig").Border;
|
||||||
|
|
||||||
|
const TermIO = term.TermIO;
|
||||||
|
|
||||||
|
pub var top_pane: *Pane = undefined;
|
||||||
|
pub var focused_pane: ?*Pane = null;
|
||||||
|
pub var should_exit: bool = false;
|
||||||
|
var needs_redraw = true;
|
||||||
|
var redraw_count: usize = 0;
|
||||||
|
|
||||||
|
fn resize_signal(sig: i32) callconv(.C) void {
|
||||||
|
_ = sig;
|
||||||
|
needs_redraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_signal(sig: i32) callconv(.C) void {
|
||||||
|
_ = sig;
|
||||||
|
should_exit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(term_io: *TermIO) !void {
|
||||||
|
var resize_handler = std.posix.Sigaction{
|
||||||
|
.handler = .{ .handler = resize_signal },
|
||||||
|
.mask = std.posix.empty_sigset,
|
||||||
|
.flags = 0,
|
||||||
|
};
|
||||||
|
try std.posix.sigaction(std.posix.SIG.WINCH, &resize_handler, null);
|
||||||
|
|
||||||
|
var exit_handler = std.posix.Sigaction{
|
||||||
|
.handler = .{ .handler = exit_signal },
|
||||||
|
.mask = std.posix.empty_sigset,
|
||||||
|
.flags = 0,
|
||||||
|
};
|
||||||
|
try std.posix.sigaction(std.posix.SIG.INT, &exit_handler, null);
|
||||||
|
|
||||||
|
term_io.enterRawMode();
|
||||||
|
term_io.saveScreen() catch {};
|
||||||
|
term_io.hideCursor();
|
||||||
|
try term_io.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cleanup(term_io: *TermIO) void {
|
||||||
|
term_io.showCursor();
|
||||||
|
term_io.restoreScreen() catch {};
|
||||||
|
term_io.exitRawMode();
|
||||||
|
term_io.flush() catch {};
|
||||||
|
term_io.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(term_io: *TermIO) !void {
|
||||||
|
if (needs_redraw) {
|
||||||
|
needs_redraw = false;
|
||||||
|
const size = term.getTermSize(term_io);
|
||||||
|
|
||||||
|
term_io.clear();
|
||||||
|
try top_pane.ReDraw(term_io, size);
|
||||||
|
|
||||||
|
redraw_count += 1;
|
||||||
|
try std.fmt.format(top_pane.writer(term_io), "Resized {} times", .{redraw_count});
|
||||||
|
try term_io.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Overflow = struct {
|
||||||
|
x: enum { Wrap, Hidden },
|
||||||
|
y: enum { Scroll, Hidden },
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Cursor = struct {
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Pane = struct {
|
||||||
|
parent: ?*Pane = null,
|
||||||
|
children: ?std.ArrayList(*Pane),
|
||||||
|
border: ?Border = null,
|
||||||
|
cursor: Cursor = .{ .x = 0, .y = 0 },
|
||||||
|
overflow: Overflow = .{ .x = .Hidden, .y = .Hidden },
|
||||||
|
dimensions: dim.Dimensions,
|
||||||
|
calcDims: dim.CalculatedDimensions = undefined,
|
||||||
|
background: color.Color = color.Default,
|
||||||
|
foreground: color.Color = color.Default,
|
||||||
|
|
||||||
|
pub const WriterContext = struct { pane: *Pane, term_io: *const TermIO };
|
||||||
|
pub const Writer = std.io.Writer(WriterContext, std.fs.File.WriteError, write);
|
||||||
|
|
||||||
|
pub fn ReDraw(self: *Pane, term_io: *TermIO, parentDims: dim.CalculatedDimensions) !void {
|
||||||
|
self.calcDims = .{ .pos = parentDims.pos, .size = self.dimensions.size };
|
||||||
|
if (self.dimensions.size_type == .Relative) {
|
||||||
|
std.debug.assert(self.dimensions.size.width <= 100 and self.dimensions.size.height <= 100);
|
||||||
|
self.calcDims.size.width = (parentDims.size.width * self.dimensions.size.width) / 100;
|
||||||
|
self.calcDims.size.height = (parentDims.size.height * self.dimensions.size.height) / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
try term_io.setColor(self.background, self.foreground);
|
||||||
|
|
||||||
|
switch (self.dimensions.anchor) {
|
||||||
|
.TopLeft => {},
|
||||||
|
.TopCenter => {
|
||||||
|
self.calcDims.pos.x = parentDims.pos.x + (parentDims.size.width - self.calcDims.size.width) / 2;
|
||||||
|
},
|
||||||
|
.TopRight => {
|
||||||
|
self.calcDims.pos.x = parentDims.pos.x + parentDims.size.width - self.calcDims.size.width;
|
||||||
|
},
|
||||||
|
.CenterLeft => {
|
||||||
|
self.calcDims.pos.y = parentDims.pos.y + (parentDims.size.height - self.calcDims.size.height) / 2;
|
||||||
|
},
|
||||||
|
.Center => {
|
||||||
|
self.calcDims.pos.x = parentDims.pos.x + (parentDims.size.width - self.calcDims.size.width) / 2;
|
||||||
|
self.calcDims.pos.y = parentDims.pos.y + (parentDims.size.height - self.calcDims.size.height) / 2;
|
||||||
|
},
|
||||||
|
.CenterRight => {
|
||||||
|
self.calcDims.pos.x = parentDims.pos.x + parentDims.size.width - self.calcDims.size.width;
|
||||||
|
self.calcDims.pos.y = parentDims.pos.y + (parentDims.size.height - self.calcDims.size.height) / 2;
|
||||||
|
},
|
||||||
|
.BottomLeft => {
|
||||||
|
self.calcDims.pos.y = parentDims.pos.y + parentDims.size.height - self.calcDims.size.height;
|
||||||
|
},
|
||||||
|
.BottomCenter => {
|
||||||
|
self.calcDims.pos.y = parentDims.pos.y + parentDims.size.height - self.calcDims.size.height;
|
||||||
|
self.calcDims.pos.x = parentDims.pos.x + (parentDims.size.width - self.calcDims.size.width) / 2;
|
||||||
|
},
|
||||||
|
.BottomRight => {
|
||||||
|
self.calcDims.pos.y = parentDims.pos.y + parentDims.size.height - self.calcDims.size.height;
|
||||||
|
self.calcDims.pos.x = parentDims.pos.x + parentDims.size.width - self.calcDims.size.width;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.border) |border| {
|
||||||
|
var fill = false;
|
||||||
|
if (self.parent) |parent| {
|
||||||
|
fill = !self.background.equal(parent.background);
|
||||||
|
} else {
|
||||||
|
fill = !self.background.equal(color.Default);
|
||||||
|
}
|
||||||
|
try term_io.drawBox(self.calcDims, border, fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
try term_io.flush();
|
||||||
|
//_ = term_io.getKey(true);
|
||||||
|
|
||||||
|
if (self.children) |children| {
|
||||||
|
for (children.items) |child| {
|
||||||
|
try child.ReDraw(term_io, self.calcDims);
|
||||||
|
try child.focus(term_io);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn focus(self: *Pane, term_io: *const TermIO) !void {
|
||||||
|
focused_pane = self;
|
||||||
|
self.cursor = .{ .x = 0, .y = 0 };
|
||||||
|
|
||||||
|
self.moveCursor(term_io);
|
||||||
|
try term_io.setColor(self.background, self.foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nextLine(self: *Pane, term_io: *const TermIO) void {
|
||||||
|
self.cursor.x = 0;
|
||||||
|
self.cursor.y += 1;
|
||||||
|
|
||||||
|
self.moveCursor(term_io);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn moveCursor(self: *Pane, term_io: *const TermIO) void {
|
||||||
|
const borderWidth: u1 = if (self.border != null) 1 else 0;
|
||||||
|
term_io.moveCursor(self.calcDims.pos.x + self.cursor.x + borderWidth, self.calcDims.pos.y + self.cursor.y + borderWidth) catch {};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print(self: *Pane, term_io: *const TermIO, string: []const u8) !void {
|
||||||
|
const borderWidth: u2 = if (self.border != null) 1 else 0;
|
||||||
|
var space = self.calcDims.size.width - self.cursor.x - (2 * borderWidth);
|
||||||
|
var i: usize = 0;
|
||||||
|
|
||||||
|
while (string.len - i >= space) {
|
||||||
|
try term_io.print("{s}", .{string[i .. i + space]}, .{});
|
||||||
|
self.nextLine(term_io);
|
||||||
|
i += space;
|
||||||
|
space = self.calcDims.size.width - (2 * borderWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < string.len) {
|
||||||
|
try term_io.print("{s}", .{string[i..]}, .{});
|
||||||
|
self.cursor.x += string.len - i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writer(self: *Pane, term_io: *const TermIO) Writer {
|
||||||
|
return .{
|
||||||
|
.context = .{ .pane = self, .term_io = term_io },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(self: WriterContext, bytes: []const u8) std.fs.File.WriteError!usize {
|
||||||
|
if (self.pane != focused_pane) {
|
||||||
|
try self.pane.focus(self.term_io);
|
||||||
|
}
|
||||||
|
|
||||||
|
const borderWidth: u2 = if (self.pane.border != null) 1 else 0;
|
||||||
|
for (bytes) |byte| {
|
||||||
|
switch (byte) {
|
||||||
|
'\x1b' => continue,
|
||||||
|
'\n' => self.pane.nextLine(self.term_io),
|
||||||
|
else => {
|
||||||
|
self.pane.cursor.x += try self.term_io.stdout.write(&[_]u8{byte});
|
||||||
|
if (self.pane.cursor.x >= self.pane.calcDims.size.width - (2 * borderWidth)) {
|
||||||
|
self.pane.nextLine(self.term_io);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes.len;
|
||||||
|
}
|
||||||
|
};
|
563
src/term.zig
Normal file
563
src/term.zig
Normal file
@ -0,0 +1,563 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const fs = std.fs;
|
||||||
|
const posix = std.posix;
|
||||||
|
const borders = @import("borders.zig");
|
||||||
|
const dim = @import("dimensions.zig");
|
||||||
|
const color = @import("colors.zig");
|
||||||
|
|
||||||
|
var orig_termios: posix.termios = undefined;
|
||||||
|
|
||||||
|
pub const MenuError = error{ForgottenMenuItem};
|
||||||
|
|
||||||
|
pub const KeyType = enum {
|
||||||
|
ASCII,
|
||||||
|
SEQUENCE,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SequenceKey = enum(u8) {
|
||||||
|
UP,
|
||||||
|
DOWN,
|
||||||
|
LEFT,
|
||||||
|
RIGHT,
|
||||||
|
DELETE,
|
||||||
|
|
||||||
|
UNKNOWN,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Key = struct {
|
||||||
|
type: KeyType,
|
||||||
|
value: u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const PrintFlags = struct {
|
||||||
|
dim: bool = false,
|
||||||
|
bold: bool = false,
|
||||||
|
italic: bool = false,
|
||||||
|
underline: bool = false,
|
||||||
|
highlight: bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SelectMenuItem = struct {
|
||||||
|
name: []const u8,
|
||||||
|
selected: bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const MenuItem = struct {
|
||||||
|
name: []const u8,
|
||||||
|
value: usize,
|
||||||
|
disabled: bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SelectMenu = struct {
|
||||||
|
title: []const u8,
|
||||||
|
items: []SelectMenuItem,
|
||||||
|
focused_item: usize = 0,
|
||||||
|
cancelled: bool = false,
|
||||||
|
|
||||||
|
pub fn init(title: []const u8, items: []SelectMenuItem) SelectMenu {
|
||||||
|
return .{ .title = title, .items = items };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(self: *SelectMenu) void {
|
||||||
|
self.focused_item = 0;
|
||||||
|
self.cancelled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh(self: *SelectMenu, term_io: *TermIO) !void {
|
||||||
|
term_io.clear();
|
||||||
|
|
||||||
|
try term_io.print("{s}", .{self.title}, .{ .underline = true });
|
||||||
|
for (self.items, 0..) |_, i| {
|
||||||
|
try self.printItem(i, term_io);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show(self: *SelectMenu, term_io: *TermIO) !void {
|
||||||
|
try self.refresh(term_io);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const key: Key = term_io.getKey(true);
|
||||||
|
if (key.type == KeyType.SEQUENCE) {
|
||||||
|
const key_enum: SequenceKey = @enumFromInt(key.value);
|
||||||
|
switch (key_enum) {
|
||||||
|
SequenceKey.DOWN => try self.nextItem(term_io),
|
||||||
|
SequenceKey.UP => try self.prevItem(term_io),
|
||||||
|
else => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (key.value) {
|
||||||
|
13 => break, // Enter
|
||||||
|
' ' => {
|
||||||
|
try self.toggleSelection(term_io);
|
||||||
|
},
|
||||||
|
0x1b, 'q' => {
|
||||||
|
self.cancelled = true;
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
'j' => try self.nextItem(term_io),
|
||||||
|
'k' => try self.prevItem(term_io),
|
||||||
|
else => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn printItem(self: *SelectMenu, item: usize, term_io: *TermIO) !void {
|
||||||
|
if (self.items[item].selected) {
|
||||||
|
try term_io.output(1, item + 2, "[X] {s}", .{self.items[item].name}, .{ .highlight = item == self.focused_item });
|
||||||
|
} else {
|
||||||
|
try term_io.output(1, item + 2, "[ ] {s}", .{self.items[item].name}, .{ .highlight = item == self.focused_item });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toggleSelection(self: *SelectMenu, term_io: *TermIO) !void {
|
||||||
|
self.items[self.focused_item].selected = !self.items[self.focused_item].selected;
|
||||||
|
try self.printItem(self.focused_item, term_io);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nextItem(self: *SelectMenu, term_io: *TermIO) !void {
|
||||||
|
if (self.focused_item < self.items.len - 1) {
|
||||||
|
try self.focusItem(self.focused_item + 1, term_io);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prevItem(self: *SelectMenu, term_io: *TermIO) !void {
|
||||||
|
if (self.focused_item > 0) {
|
||||||
|
try self.focusItem(self.focused_item - 1, term_io);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn focusItem(self: *SelectMenu, item: usize, term_io: *TermIO) !void {
|
||||||
|
const previousSelection = self.focused_item;
|
||||||
|
self.focused_item = item;
|
||||||
|
|
||||||
|
if (previousSelection != self.focused_item) {
|
||||||
|
try self.printItem(previousSelection, term_io);
|
||||||
|
}
|
||||||
|
try self.printItem(self.focused_item, term_io);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Menu = struct {
|
||||||
|
title: []const u8,
|
||||||
|
items: []MenuItem,
|
||||||
|
selected_item: usize = 0,
|
||||||
|
selected_value: usize = 0,
|
||||||
|
|
||||||
|
pub fn init(title: []const u8, items: []MenuItem) Menu {
|
||||||
|
for (items) |item| {
|
||||||
|
std.debug.assert(item.value != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ .title = title, .items = items };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(self: *Menu) void {
|
||||||
|
self.selected_item = 0;
|
||||||
|
self.selected_value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh(self: *Menu, term_io: *TermIO) !void {
|
||||||
|
term_io.clear();
|
||||||
|
|
||||||
|
try term_io.print("{s}", .{self.title}, .{ .underline = true });
|
||||||
|
for (self.items, 0..) |_, i| {
|
||||||
|
try self.printItem(i, term_io);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show(self: *Menu, term_io: *TermIO) !void {
|
||||||
|
if (self.items[self.selected_item].disabled) {
|
||||||
|
try self.nextItem(term_io);
|
||||||
|
}
|
||||||
|
if (self.items[self.selected_item].disabled) {
|
||||||
|
self.selected_item = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.refresh(term_io);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const key: Key = term_io.getKey(true);
|
||||||
|
if (key.type == KeyType.SEQUENCE) {
|
||||||
|
const key_enum: SequenceKey = @enumFromInt(key.value);
|
||||||
|
switch (key_enum) {
|
||||||
|
SequenceKey.DOWN => try self.nextItem(term_io),
|
||||||
|
SequenceKey.UP => try self.prevItem(term_io),
|
||||||
|
else => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (key.value) {
|
||||||
|
13 => break, // Enter
|
||||||
|
'0' => {
|
||||||
|
if (!self.items[9].disabled) {
|
||||||
|
try self.selectItem(9, term_io);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'1'...'9' => {
|
||||||
|
if (!self.items[key.value - '1'].disabled) {
|
||||||
|
try self.selectItem(key.value - '1', term_io);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
0x1b, 'q' => {
|
||||||
|
self.selected_value = 0;
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
'j' => try self.nextItem(term_io),
|
||||||
|
'k' => try self.prevItem(term_io),
|
||||||
|
else => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.selected_value = self.items[self.selected_item].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn printItem(self: *Menu, item: usize, term_io: *TermIO) !void {
|
||||||
|
if (item < 10) {
|
||||||
|
try term_io.output(1, item + 2, "{d}: {s}", .{ item + 1 % 10, self.items[item].name }, .{ .dim = self.items[item].disabled, .highlight = item == self.selected_item });
|
||||||
|
} else {
|
||||||
|
try term_io.output(1, item + 2, " : {s}", .{self.items[item].name}, .{ .dim = self.items[item].disabled, .highlight = item == self.selected_item });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nextItem(self: *Menu, term_io: *TermIO) !void {
|
||||||
|
var i: usize = self.selected_item;
|
||||||
|
while (i < self.items.len and (self.items[i].disabled or i == self.selected_item)) {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
if (!self.items[i].disabled) {
|
||||||
|
try self.selectItem(i, term_io);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prevItem(self: *Menu, term_io: *TermIO) !void {
|
||||||
|
var i: usize = self.selected_item;
|
||||||
|
while (i > 0 and (self.items[i].disabled or i == self.selected_item)) {
|
||||||
|
i -= 1;
|
||||||
|
}
|
||||||
|
if (!self.items[i].disabled) {
|
||||||
|
try self.selectItem(i, term_io);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selectItem(self: *Menu, item: usize, term_io: *TermIO) !void {
|
||||||
|
const previousSelection = self.selected_item;
|
||||||
|
self.selected_item = item;
|
||||||
|
|
||||||
|
if (previousSelection != self.selected_item) {
|
||||||
|
try self.printItem(previousSelection, term_io);
|
||||||
|
}
|
||||||
|
try self.printItem(self.selected_item, term_io);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Input = struct {
|
||||||
|
pos_x: usize,
|
||||||
|
pos_y: usize,
|
||||||
|
max_len: usize,
|
||||||
|
|
||||||
|
cursor_pos: usize = 0,
|
||||||
|
len: usize = 0,
|
||||||
|
text: []u8,
|
||||||
|
_allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, x: usize, y: usize, max_len: usize) !Input {
|
||||||
|
return Input{
|
||||||
|
.pos_x = x,
|
||||||
|
.pos_y = y,
|
||||||
|
.max_len = max_len,
|
||||||
|
.text = try allocator.alloc(u8, max_len),
|
||||||
|
._allocator = allocator,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Input) void {
|
||||||
|
self._allocator.free(self.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(self: *Input) void {
|
||||||
|
@memset(self.text, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(self: *Input, term_io: *TermIO) !void {
|
||||||
|
try term_io.output(self.pos_x, self.pos_y, "{s}", .{self.text}, .{ .underline = true });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn focus(self: *Input, term_io: *TermIO) !void {
|
||||||
|
try term_io.moveCursor(self.pos_x + @min(self.cursor_pos, self.max_len - 1), self.pos_y);
|
||||||
|
term_io.showCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handleKey(self: *Input, term_io: *TermIO, key: Key) !void {
|
||||||
|
if (key.type == .ASCII) {
|
||||||
|
if (std.ascii.isPrint(key.value) and self.len < self.max_len) {
|
||||||
|
try self.insertChar(key.value);
|
||||||
|
try self.draw(term_io);
|
||||||
|
// try term_io.output(17 + input_len, 4, "{c}", .{key.value}, .{});
|
||||||
|
} else if (key.value == 0x7F and self.cursor_pos > 0 and self.len > 0) { // Backspace
|
||||||
|
try self.removeChar();
|
||||||
|
try self.draw(term_io);
|
||||||
|
// try term_io.output(17 + input_len, 4, " ", .{}, .{});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const key_enum: SequenceKey = @enumFromInt(key.value);
|
||||||
|
if (key_enum == SequenceKey.RIGHT and self.cursor_pos < self.len) {
|
||||||
|
self.cursor_pos += 1;
|
||||||
|
} else if (key_enum == SequenceKey.LEFT and self.cursor_pos > 0) {
|
||||||
|
self.cursor_pos -= 1;
|
||||||
|
} else if (key_enum == SequenceKey.DELETE and self.cursor_pos < self.len) {
|
||||||
|
self.cursor_pos += 1;
|
||||||
|
try self.removeChar();
|
||||||
|
try self.draw(term_io);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insertChar(self: *Input, char: u8) !void {
|
||||||
|
var i: usize = self.len;
|
||||||
|
while (i > self.cursor_pos) : (i -= 1) {
|
||||||
|
self.text[i] = self.text[i - 1];
|
||||||
|
}
|
||||||
|
self.text[self.cursor_pos] = char;
|
||||||
|
self.len += 1;
|
||||||
|
self.cursor_pos += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn removeChar(self: *Input) !void {
|
||||||
|
self.cursor_pos -= 1;
|
||||||
|
for (self.cursor_pos..self.len - 1) |i| {
|
||||||
|
self.text[i] = self.text[i + 1];
|
||||||
|
}
|
||||||
|
self.text[self.len - 1] = ' ';
|
||||||
|
self.len -= 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const TermIO = struct {
|
||||||
|
stdin: fs.File.Reader,
|
||||||
|
stdout: std.io.BufferedWriter(4096, fs.File.Writer).Writer,
|
||||||
|
tty_file: fs.File,
|
||||||
|
current_background: color.Color = color.Default,
|
||||||
|
current_foreground: color.Color = color.Default,
|
||||||
|
escape_sequence_queued: bool = false,
|
||||||
|
|
||||||
|
pub fn close(self: *const TermIO) void {
|
||||||
|
self.tty_file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flush(self: *TermIO) !void {
|
||||||
|
try self.stdout.context.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enterRawMode(term_io: *const TermIO) void {
|
||||||
|
orig_termios = posix.tcgetattr(term_io.tty_file.handle) catch unreachable;
|
||||||
|
|
||||||
|
var raw = orig_termios;
|
||||||
|
raw.iflag.BRKINT = false;
|
||||||
|
raw.iflag.ICRNL = false;
|
||||||
|
raw.iflag.INPCK = false;
|
||||||
|
raw.iflag.ISTRIP = false;
|
||||||
|
raw.iflag.IXON = false;
|
||||||
|
|
||||||
|
// raw.oflag.OPOST = false;
|
||||||
|
|
||||||
|
raw.cflag.CSIZE = posix.CSIZE.CS8;
|
||||||
|
|
||||||
|
raw.lflag.ECHO = false;
|
||||||
|
raw.lflag.ICANON = false;
|
||||||
|
raw.lflag.IEXTEN = false;
|
||||||
|
raw.lflag.ISIG = false;
|
||||||
|
|
||||||
|
raw.cc[@intFromEnum(posix.V.MIN)] = 0;
|
||||||
|
raw.cc[@intFromEnum(posix.V.TIME)] = 1;
|
||||||
|
|
||||||
|
posix.tcsetattr(term_io.tty_file.handle, posix.TCSA.FLUSH, raw) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exitRawMode(term_io: *const TermIO) void {
|
||||||
|
posix.tcsetattr(term_io.tty_file.handle, posix.TCSA.FLUSH, orig_termios) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn saveScreen(term_io: *const TermIO) !void {
|
||||||
|
try term_io.stdout.print("\x1b[s", .{});
|
||||||
|
try term_io.stdout.print("\x1b[?47h", .{});
|
||||||
|
try term_io.stdout.print("\x1b[?1049h", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn restoreScreen(term_io: *const TermIO) !void {
|
||||||
|
try term_io.stdout.print("\x1b[?1049l", .{});
|
||||||
|
try term_io.stdout.print("\x1b[?47l", .{});
|
||||||
|
try term_io.stdout.print("\x1b[u", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(term_io: *const TermIO) void {
|
||||||
|
term_io.stdout.print("\x1b[2J\x1b[H", .{}) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setColor(term_io: *const TermIO, background: color.Color, foreground: color.Color) !void {
|
||||||
|
try term_io.setBackgroundColor(background);
|
||||||
|
try term_io.setForegroundColor(foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setBackgroundColor(term_io: *const TermIO, background: color.Color) !void {
|
||||||
|
if (!background.equal(term_io.current_background)) {
|
||||||
|
switch (background.type) {
|
||||||
|
.Default => try term_io.print("\x1b[49m", .{}, .{}),
|
||||||
|
.RGB => try term_io.print("\x1b[48;2;{};{};{}m", .{ background.red, background.green, background.blue }, .{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setForegroundColor(term_io: *const TermIO, foreground: color.Color) !void {
|
||||||
|
if (!foreground.equal(term_io.current_foreground)) {
|
||||||
|
switch (foreground.type) {
|
||||||
|
.Default => try term_io.print("\x1b[39m", .{}, .{}),
|
||||||
|
.RGB => try term_io.print("\x1b[38;2;{};{};{}m", .{ foreground.red, foreground.green, foreground.blue }, .{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hideCursor(term_io: *const TermIO) void {
|
||||||
|
term_io.stdout.print("\x1b[?25l", .{}) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn showCursor(term_io: *const TermIO) void {
|
||||||
|
term_io.stdout.print("\x1b[?25h", .{}) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn moveCursor(term_io: *const TermIO, x: usize, y: usize) !void {
|
||||||
|
try term_io.stdout.print("\x1b[{d};{d}H", .{ y, x });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn saveTitle(term_io: *const TermIO) !void {
|
||||||
|
try term_io.stdout.print("\x1b[22;2t", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn restoreTitle(term_io: *const TermIO) !void {
|
||||||
|
try term_io.stdout.print("\x1b[23;2t", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setTitle(term_io: *const TermIO, title: []const u8) !void {
|
||||||
|
try term_io.stdout.print("\x1b]0;{s}\x1b\\", .{title});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print(term_io: *const TermIO, comptime format: []const u8, args: anytype, flags: PrintFlags) !void {
|
||||||
|
// try term_io.stdout.print("{s}{s}{s}{s}{s}" ++ format ++ "\x1b[0m", .{ if (flags.bold) "\x1b[1m" else "", if (flags.dim) "\x1b[2m" else "", if (flags.italic) "\x1b[3m" else "", if (flags.underline) "\x1b[4m" else "", if (flags.highlight) "\x1b[7m" else "" } ++ args);
|
||||||
|
_ = flags;
|
||||||
|
try term_io.stdout.print(format, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn output(term_io: *const TermIO, x: usize, y: usize, comptime format: []const u8, args: anytype, flags: PrintFlags) !void {
|
||||||
|
// try term_io.stdout.print("\x1b[{d};{d}H{s}{s}{s}{s}{s}" ++ format ++ "\x1b[0m", .{ y, x, if (flags.bold) "\x1b[1m" else "", if (flags.dim) "\x1b[2m" else "", if (flags.italic) "\x1b[3m" else "", if (flags.underline) "\x1b[4m" else "", if (flags.highlight) "\x1b[7m" else "" } ++ args);
|
||||||
|
_ = flags;
|
||||||
|
try term_io.stdout.print("\x1b[{d};{d}H" ++ format, .{ y, x } ++ args);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drawBox(term_io: *TermIO, dims: dim.CalculatedDimensions, border: borders.Border, fill: bool) !void {
|
||||||
|
if (dims.size.width < 2 or dims.size.height < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try term_io.output(dims.pos.x, dims.pos.y, "{u}", .{border.top_left}, .{});
|
||||||
|
for (0..dims.size.width - 2) |_| {
|
||||||
|
try term_io.print("{u}", .{border.top}, .{});
|
||||||
|
}
|
||||||
|
try term_io.print("{u}", .{border.top_right}, .{});
|
||||||
|
for (1..dims.size.height - 1) |h| {
|
||||||
|
try term_io.output(dims.pos.x, dims.pos.y + h, "{u}", .{border.left}, .{});
|
||||||
|
if (fill) {
|
||||||
|
try term_io.print("\x1b[{d}X", .{dims.size.width - 2}, .{});
|
||||||
|
}
|
||||||
|
try term_io.output(dims.pos.x + dims.size.width - 1, dims.pos.y + h, "{u}", .{border.right}, .{});
|
||||||
|
|
||||||
|
if (term_io.stdout.context.buf.len - term_io.stdout.context.end < dims.size.width * 3) {
|
||||||
|
try term_io.flush();
|
||||||
|
_ = term_io.getKey(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try term_io.output(dims.pos.x, dims.pos.y + dims.size.height - 1, "{u}", .{border.bottom_left}, .{});
|
||||||
|
for (0..dims.size.width - 2) |_| {
|
||||||
|
try term_io.print("{u}", .{border.bottom}, .{});
|
||||||
|
}
|
||||||
|
try term_io.print("{u}", .{border.bottom_right}, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getKey(term_io: *TermIO, block: bool) Key {
|
||||||
|
var escape_sequence: [10]u8 = undefined;
|
||||||
|
var sequence_len: usize = 0;
|
||||||
|
|
||||||
|
// peek doesn't exist in 0.12.0 :(
|
||||||
|
while (true) {
|
||||||
|
if (!term_io.escape_sequence_queued) {
|
||||||
|
const c = term_io.stdin.readByte() catch |err| {
|
||||||
|
switch (err) {
|
||||||
|
error.WouldBlock => if (!block) return .{ .type = KeyType.ASCII, .value = 0 },
|
||||||
|
error.EndOfStream => if (!block) return .{ .type = KeyType.ASCII, .value = 0 },
|
||||||
|
else => {
|
||||||
|
std.debug.print("Error: {}\n", .{err});
|
||||||
|
unreachable;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if (c != 0x1b and sequence_len == 0) {
|
||||||
|
return .{ .type = KeyType.ASCII, .value = c };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
term_io.escape_sequence_queued = false;
|
||||||
|
|
||||||
|
// Escape sequence
|
||||||
|
sequence_len = 0;
|
||||||
|
escape_sequence[sequence_len] = term_io.stdin.readByte() catch return .{ .type = KeyType.ASCII, .value = 0x1b };
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
sequence_len += 1;
|
||||||
|
const byte = term_io.stdin.readByte() catch
|
||||||
|
return decipherEscapeSequence(escape_sequence[0..sequence_len]);
|
||||||
|
if (byte == 0x1b) {
|
||||||
|
term_io.escape_sequence_queued = true;
|
||||||
|
return decipherEscapeSequence(escape_sequence[0..sequence_len]);
|
||||||
|
}
|
||||||
|
escape_sequence[sequence_len] = byte;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn getTermSize(term_io: *const TermIO) dim.CalculatedDimensions {
|
||||||
|
var ws: std.posix.winsize = undefined;
|
||||||
|
const ret = std.os.linux.ioctl(term_io.tty_file.handle, std.os.linux.T.IOCGWINSZ, @intFromPtr(&ws));
|
||||||
|
if (ret == -1 or ws.ws_col == 0) {
|
||||||
|
std.debug.print("Failed to get terminal size\n", .{});
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dim.CalculatedDimensions{
|
||||||
|
.pos = .{ .x = 1, .y = 1 },
|
||||||
|
.size = .{ .width = ws.ws_col, .height = ws.ws_row },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getTermIO() !TermIO {
|
||||||
|
var bw = std.io.bufferedWriter(std.io.getStdOut().writer());
|
||||||
|
return TermIO{
|
||||||
|
.stdin = std.io.getStdIn().reader(),
|
||||||
|
.stdout = bw.writer(),
|
||||||
|
.tty_file = try fs.cwd().openFile("/dev/tty", .{ .mode = .read_write }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decipherEscapeSequence(sequence: []u8) Key {
|
||||||
|
if (sequence.len == 2) {
|
||||||
|
if (std.mem.eql(u8, sequence, &[2]u8{ '[', 'A' })) return .{ .type = KeyType.SEQUENCE, .value = @intFromEnum(SequenceKey.UP) };
|
||||||
|
if (std.mem.eql(u8, sequence, &[2]u8{ '[', 'B' })) return .{ .type = KeyType.SEQUENCE, .value = @intFromEnum(SequenceKey.DOWN) };
|
||||||
|
if (std.mem.eql(u8, sequence, &[2]u8{ '[', 'C' })) return .{ .type = KeyType.SEQUENCE, .value = @intFromEnum(SequenceKey.RIGHT) };
|
||||||
|
if (std.mem.eql(u8, sequence, &[2]u8{ '[', 'D' })) return .{ .type = KeyType.SEQUENCE, .value = @intFromEnum(SequenceKey.LEFT) };
|
||||||
|
} else if (sequence.len == 3) {
|
||||||
|
if (std.mem.eql(u8, sequence, &[3]u8{ '[', '3', '~' })) return .{ .type = KeyType.SEQUENCE, .value = @intFromEnum(SequenceKey.DELETE) };
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ .type = KeyType.SEQUENCE, .value = @intFromEnum(SequenceKey.UNKNOWN) };
|
||||||
|
}
|
BIN
zig-out/bin/panes
Executable file
BIN
zig-out/bin/panes
Executable file
Binary file not shown.
BIN
zig-out/lib/libpanes.a
Normal file
BIN
zig-out/lib/libpanes.a
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user