Replace vaxis with zterm #1
@@ -6,11 +6,6 @@ It contains information about me and my projects as well as blog entries about s
|
|||||||
|
|
||||||
## Open tasks
|
## Open tasks
|
||||||
|
|
||||||
- [ ] BUG: when served via `wish-serve` the corresponding outputs are not pushed through the ssh connection
|
|
||||||
- they are instead showed locally, which might cause issues with the docker container running in the background
|
|
||||||
- very likely it is `tui-website` which causes this issue
|
|
||||||
- not entirely as inputs are not passed through correctly to the below running application (i.e. `diffnav` via `serve git diff`)
|
|
||||||
- fex however works as expected
|
|
||||||
- [ ] Improve navigation
|
- [ ] Improve navigation
|
||||||
- [ ] Have clickable/navigatable links inside of the tui application
|
- [ ] Have clickable/navigatable links inside of the tui application
|
||||||
- [ ] Launch simple http server alongside tui application
|
- [ ] Launch simple http server alongside tui application
|
||||||
@@ -20,3 +15,4 @@ It contains information about me and my projects as well as blog entries about s
|
|||||||
## Branch: `own-tty-visuals`
|
## Branch: `own-tty-visuals`
|
||||||
|
|
||||||
- [ ] How can I support to run a sub-process inside of a given pane / layout?
|
- [ ] How can I support to run a sub-process inside of a given pane / layout?
|
||||||
|
- [ ] Create demo gifs using [vhs](https://github.com/charmbracelet/vhs)
|
||||||
|
|||||||
73
src/app.zig
73
src/app.zig
@@ -5,6 +5,7 @@ const terminal = @import("terminal.zig");
|
|||||||
const mergeTaggedUnions = @import("event.zig").mergeTaggedUnions;
|
const mergeTaggedUnions = @import("event.zig").mergeTaggedUnions;
|
||||||
const isTaggedUnion = @import("event.zig").isTaggedUnion;
|
const isTaggedUnion = @import("event.zig").isTaggedUnion;
|
||||||
|
|
||||||
|
const Key = @import("key.zig");
|
||||||
const SystemEvent = @import("event.zig").SystemEvent;
|
const SystemEvent = @import("event.zig").SystemEvent;
|
||||||
const Queue = @import("queue.zig").Queue;
|
const Queue = @import("queue.zig").Queue;
|
||||||
|
|
||||||
@@ -24,53 +25,64 @@ pub fn App(comptime E: type) type {
|
|||||||
|
|
||||||
queue: Queue(Event, 256) = .{},
|
queue: Queue(Event, 256) = .{},
|
||||||
thread: ?std.Thread = null,
|
thread: ?std.Thread = null,
|
||||||
quit: std.Thread.ResetEvent = .{},
|
quit_event: std.Thread.ResetEvent = .{},
|
||||||
termios: ?std.posix.termios = null,
|
termios: ?std.posix.termios = null,
|
||||||
|
attached_handler: bool = false,
|
||||||
|
|
||||||
pub const SignalHandler = struct {
|
pub const SignalHandler = struct {
|
||||||
context: *anyopaque,
|
context: *anyopaque,
|
||||||
callback: *const fn (context: *anyopaque) void,
|
callback: *const fn (context: *anyopaque) void,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init() @This() {
|
|
||||||
var winch_act = std.posix.Sigaction{
|
|
||||||
.handler = .{ .handler = @This().handleWinch },
|
|
||||||
.mask = std.posix.empty_sigset,
|
|
||||||
.flags = 0,
|
|
||||||
};
|
|
||||||
std.posix.sigaction(std.posix.SIG.WINCH, &winch_act, null) catch @panic("could not attach signal WINCH");
|
|
||||||
|
|
||||||
return .{};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start(this: *@This()) !void {
|
pub fn start(this: *@This()) !void {
|
||||||
if (this.thread) |_| return;
|
if (this.thread) |_| return;
|
||||||
|
|
||||||
try registerWinch(.{
|
if (!this.attached_handler) {
|
||||||
.context = this,
|
var winch_act = std.posix.Sigaction{
|
||||||
.callback = @This().winsizeCallback,
|
.handler = .{ .handler = @This().handleWinch },
|
||||||
});
|
.mask = std.posix.empty_sigset,
|
||||||
|
.flags = 0,
|
||||||
|
};
|
||||||
|
std.posix.sigaction(std.posix.SIG.WINCH, &winch_act, null) catch @panic("could not attach signal WINCH");
|
||||||
|
|
||||||
this.quit.reset();
|
try registerWinch(.{
|
||||||
|
.context = this,
|
||||||
|
.callback = @This().winsizeCallback,
|
||||||
|
});
|
||||||
|
this.attached_handler = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.quit_event.reset();
|
||||||
this.thread = try std.Thread.spawn(.{}, @This().run, .{this});
|
this.thread = try std.Thread.spawn(.{}, @This().run, .{this});
|
||||||
|
if (this.termios) |_| return;
|
||||||
|
|
||||||
var termios: std.posix.termios = undefined;
|
var termios: std.posix.termios = undefined;
|
||||||
try terminal.enableRawMode(&termios);
|
try terminal.enableRawMode(&termios);
|
||||||
this.termios = termios;
|
this.termios = termios;
|
||||||
try terminal.saveScreen();
|
try terminal.saveScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop(this: *@This()) !void {
|
pub fn interrupt(this: *@This()) !void {
|
||||||
if (this.termios) |*termios| {
|
this.quit_event.set();
|
||||||
try terminal.disableRawMode(termios);
|
|
||||||
try terminal.restoreScreen();
|
|
||||||
}
|
|
||||||
this.quit.set();
|
|
||||||
if (this.thread) |thread| {
|
if (this.thread) |thread| {
|
||||||
thread.join();
|
thread.join();
|
||||||
this.thread = null;
|
this.thread = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn stop(this: *@This()) !void {
|
||||||
|
try this.interrupt();
|
||||||
|
if (this.termios) |*termios| {
|
||||||
|
try terminal.disableRawMode(termios);
|
||||||
|
try terminal.restoreScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn quit(this: *@This()) void {
|
||||||
|
this.quit_event.set();
|
||||||
|
this.postEvent(.quit);
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the next available event, blocking until one is available.
|
/// Returns the next available event, blocking until one is available.
|
||||||
pub fn nextEvent(this: *@This()) Event {
|
pub fn nextEvent(this: *@This()) Event {
|
||||||
return this.queue.pop();
|
return this.queue.pop();
|
||||||
@@ -111,7 +123,7 @@ pub fn App(comptime E: type) type {
|
|||||||
var buf: [256]u8 = undefined;
|
var buf: [256]u8 = undefined;
|
||||||
while (true) {
|
while (true) {
|
||||||
// FIX: I still think that there is a race condition (I'm just waiting 'long' enough)
|
// FIX: I still think that there is a race condition (I'm just waiting 'long' enough)
|
||||||
this.quit.timedWait(20 * std.time.ns_per_ms) catch {
|
this.quit_event.timedWait(20 * std.time.ns_per_ms) catch {
|
||||||
const read_bytes = try terminal.read(buf[0..]);
|
const read_bytes = try terminal.read(buf[0..]);
|
||||||
// escape key presses
|
// escape key presses
|
||||||
if (buf[0] == 0x1b and read_bytes > 1) {
|
if (buf[0] == 0x1b and read_bytes > 1) {
|
||||||
@@ -121,17 +133,17 @@ pub fn App(comptime E: type) type {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const b = buf[0];
|
const b = buf[0];
|
||||||
const key: terminal.Key = switch (b) {
|
const key: Key = switch (b) {
|
||||||
0x00 => .{ .cp = '@', .mod = .{ .ctrl = true } },
|
0x00 => .{ .cp = '@', .mod = .{ .ctrl = true } },
|
||||||
0x08 => .{ .cp = terminal.Key.backspace },
|
0x08 => .{ .cp = Key.backspace },
|
||||||
0x09 => .{ .cp = terminal.Key.tab },
|
0x09 => .{ .cp = Key.tab },
|
||||||
0x0a, 0x0d => .{ .cp = terminal.Key.enter },
|
0x0a, 0x0d => .{ .cp = Key.enter },
|
||||||
0x01...0x07, 0x0b...0x0c, 0x0e...0x1a => .{ .cp = b + 0x60, .mod = .{ .ctrl = true } },
|
0x01...0x07, 0x0b...0x0c, 0x0e...0x1a => .{ .cp = b + 0x60, .mod = .{ .ctrl = true } },
|
||||||
0x1b => escape: {
|
0x1b => escape: {
|
||||||
std.debug.assert(read_bytes == 1);
|
std.debug.assert(read_bytes == 1);
|
||||||
break :escape .{ .cp = terminal.Key.escape };
|
break :escape .{ .cp = Key.escape };
|
||||||
},
|
},
|
||||||
0x7f => .{ .cp = terminal.Key.backspace },
|
0x7f => .{ .cp = Key.backspace },
|
||||||
else => {
|
else => {
|
||||||
var iter = terminal.code_point.Iterator{ .bytes = buf[0..read_bytes] };
|
var iter = terminal.code_point.Iterator{ .bytes = buf[0..read_bytes] };
|
||||||
while (iter.next()) |cp| {
|
while (iter.next()) |cp| {
|
||||||
@@ -146,7 +158,6 @@ pub fn App(comptime E: type) type {
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.postEvent(.quit);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
//! events. See `App` for more details about user defined events.
|
//! events. See `App` for more details about user defined events.
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const terminal = @import("terminal.zig");
|
const terminal = @import("terminal.zig");
|
||||||
|
const Key = @import("key.zig");
|
||||||
|
|
||||||
pub const Error = struct {
|
pub const Error = struct {
|
||||||
err: anyerror,
|
err: anyerror,
|
||||||
@@ -13,7 +14,7 @@ pub const SystemEvent = union(enum) {
|
|||||||
quit,
|
quit,
|
||||||
err: Error,
|
err: Error,
|
||||||
resize: terminal.Size,
|
resize: terminal.Size,
|
||||||
key: terminal.Key,
|
key: Key,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn mergeTaggedUnions(comptime A: type, comptime B: type) type {
|
pub fn mergeTaggedUnions(comptime A: type, comptime B: type) type {
|
||||||
|
|||||||
149
src/key.zig
Normal file
149
src/key.zig
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
//! Keybindings and Modifiers for user input detection and selection.
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const Modifier = struct {
|
||||||
|
shift: bool = false,
|
||||||
|
alt: bool = false,
|
||||||
|
ctrl: bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
cp: u21,
|
||||||
|
mod: Modifier = .{},
|
||||||
|
|
||||||
|
/// Compare _this_ `Key` with an _other_ `Key`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Configure `ctrl+c` to quit the application (done in main event loop of the application):
|
||||||
|
///
|
||||||
|
/// ```zig
|
||||||
|
/// switch (event) {
|
||||||
|
/// .quit => break,
|
||||||
|
/// .key => |key| {
|
||||||
|
/// // ctrl+c to quit
|
||||||
|
/// if (terminal.Key.matches(key, .{ .cp = 'c', .mod = .{ .ctrl = true } })) {
|
||||||
|
/// app.quit.set();
|
||||||
|
/// }
|
||||||
|
/// },
|
||||||
|
/// else => {},
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn matches(this: @This(), other: @This()) bool {
|
||||||
|
return std.meta.eql(this, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
// codepoints for keys
|
||||||
|
pub const tab: u21 = 0x09;
|
||||||
|
pub const enter: u21 = 0x0D;
|
||||||
|
pub const escape: u21 = 0x1B;
|
||||||
|
pub const space: u21 = 0x20;
|
||||||
|
pub const backspace: u21 = 0x7F;
|
||||||
|
|
||||||
|
// kitty key encodings (re-used here)
|
||||||
|
pub const insert: u21 = 57348;
|
||||||
|
pub const delete: u21 = 57349;
|
||||||
|
pub const left: u21 = 57350;
|
||||||
|
pub const right: u21 = 57351;
|
||||||
|
pub const up: u21 = 57352;
|
||||||
|
pub const down: u21 = 57353;
|
||||||
|
pub const page_up: u21 = 57354;
|
||||||
|
pub const page_down: u21 = 57355;
|
||||||
|
pub const home: u21 = 57356;
|
||||||
|
pub const end: u21 = 57357;
|
||||||
|
pub const caps_lock: u21 = 57358;
|
||||||
|
pub const scroll_lock: u21 = 57359;
|
||||||
|
pub const num_lock: u21 = 57360;
|
||||||
|
pub const print_screen: u21 = 57361;
|
||||||
|
pub const pause: u21 = 57362;
|
||||||
|
pub const menu: u21 = 57363;
|
||||||
|
pub const f1: u21 = 57364;
|
||||||
|
pub const f2: u21 = 57365;
|
||||||
|
pub const f3: u21 = 57366;
|
||||||
|
pub const f4: u21 = 57367;
|
||||||
|
pub const f5: u21 = 57368;
|
||||||
|
pub const f6: u21 = 57369;
|
||||||
|
pub const f7: u21 = 57370;
|
||||||
|
pub const f8: u21 = 57371;
|
||||||
|
pub const f9: u21 = 57372;
|
||||||
|
pub const f10: u21 = 57373;
|
||||||
|
pub const f11: u21 = 57374;
|
||||||
|
pub const f12: u21 = 57375;
|
||||||
|
pub const f13: u21 = 57376;
|
||||||
|
pub const f14: u21 = 57377;
|
||||||
|
pub const f15: u21 = 57378;
|
||||||
|
pub const @"f16": u21 = 57379;
|
||||||
|
pub const f17: u21 = 57380;
|
||||||
|
pub const f18: u21 = 57381;
|
||||||
|
pub const f19: u21 = 57382;
|
||||||
|
pub const f20: u21 = 57383;
|
||||||
|
pub const f21: u21 = 57384;
|
||||||
|
pub const f22: u21 = 57385;
|
||||||
|
pub const f23: u21 = 57386;
|
||||||
|
pub const f24: u21 = 57387;
|
||||||
|
pub const f25: u21 = 57388;
|
||||||
|
pub const f26: u21 = 57389;
|
||||||
|
pub const f27: u21 = 57390;
|
||||||
|
pub const f28: u21 = 57391;
|
||||||
|
pub const f29: u21 = 57392;
|
||||||
|
pub const f30: u21 = 57393;
|
||||||
|
pub const f31: u21 = 57394;
|
||||||
|
pub const @"f32": u21 = 57395;
|
||||||
|
pub const f33: u21 = 57396;
|
||||||
|
pub const f34: u21 = 57397;
|
||||||
|
pub const f35: u21 = 57398;
|
||||||
|
pub const kp_0: u21 = 57399;
|
||||||
|
pub const kp_1: u21 = 57400;
|
||||||
|
pub const kp_2: u21 = 57401;
|
||||||
|
pub const kp_3: u21 = 57402;
|
||||||
|
pub const kp_4: u21 = 57403;
|
||||||
|
pub const kp_5: u21 = 57404;
|
||||||
|
pub const kp_6: u21 = 57405;
|
||||||
|
pub const kp_7: u21 = 57406;
|
||||||
|
pub const kp_8: u21 = 57407;
|
||||||
|
pub const kp_9: u21 = 57408;
|
||||||
|
pub const kp_decimal: u21 = 57409;
|
||||||
|
pub const kp_divide: u21 = 57410;
|
||||||
|
pub const kp_multiply: u21 = 57411;
|
||||||
|
pub const kp_subtract: u21 = 57412;
|
||||||
|
pub const kp_add: u21 = 57413;
|
||||||
|
pub const kp_enter: u21 = 57414;
|
||||||
|
pub const kp_equal: u21 = 57415;
|
||||||
|
pub const kp_separator: u21 = 57416;
|
||||||
|
pub const kp_left: u21 = 57417;
|
||||||
|
pub const kp_right: u21 = 57418;
|
||||||
|
pub const kp_up: u21 = 57419;
|
||||||
|
pub const kp_down: u21 = 57420;
|
||||||
|
pub const kp_page_up: u21 = 57421;
|
||||||
|
pub const kp_page_down: u21 = 57422;
|
||||||
|
pub const kp_home: u21 = 57423;
|
||||||
|
pub const kp_end: u21 = 57424;
|
||||||
|
pub const kp_insert: u21 = 57425;
|
||||||
|
pub const kp_delete: u21 = 57426;
|
||||||
|
pub const kp_begin: u21 = 57427;
|
||||||
|
pub const media_play: u21 = 57428;
|
||||||
|
pub const media_pause: u21 = 57429;
|
||||||
|
pub const media_play_pause: u21 = 57430;
|
||||||
|
pub const media_reverse: u21 = 57431;
|
||||||
|
pub const media_stop: u21 = 57432;
|
||||||
|
pub const media_fast_forward: u21 = 57433;
|
||||||
|
pub const media_rewind: u21 = 57434;
|
||||||
|
pub const media_track_next: u21 = 57435;
|
||||||
|
pub const media_track_previous: u21 = 57436;
|
||||||
|
pub const media_record: u21 = 57437;
|
||||||
|
pub const lower_volume: u21 = 57438;
|
||||||
|
pub const raise_volume: u21 = 57439;
|
||||||
|
pub const mute_volume: u21 = 57440;
|
||||||
|
pub const left_shift: u21 = 57441;
|
||||||
|
pub const left_control: u21 = 57442;
|
||||||
|
pub const left_alt: u21 = 57443;
|
||||||
|
pub const left_super: u21 = 57444;
|
||||||
|
pub const left_hyper: u21 = 57445;
|
||||||
|
pub const left_meta: u21 = 57446;
|
||||||
|
pub const right_shift: u21 = 57447;
|
||||||
|
pub const right_control: u21 = 57448;
|
||||||
|
pub const right_alt: u21 = 57449;
|
||||||
|
pub const right_super: u21 = 57450;
|
||||||
|
pub const right_hyper: u21 = 57451;
|
||||||
|
pub const right_meta: u21 = 57452;
|
||||||
|
pub const iso_level_3_shift: u21 = 57453;
|
||||||
|
pub const iso_level_5_shift: u21 = 57454;
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const terminal = @import("../terminal.zig");
|
||||||
const isTaggedUnion = @import("../event.zig").isTaggedUnion;
|
const isTaggedUnion = @import("../event.zig").isTaggedUnion;
|
||||||
const Error = @import("../event.zig").Error;
|
const Error = @import("../event.zig").Error;
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ pub fn Layout(comptime Event: type) type {
|
|||||||
widget: Widget = undefined,
|
widget: Widget = undefined,
|
||||||
events: std.ArrayList(Event) = undefined,
|
events: std.ArrayList(Event) = undefined,
|
||||||
c: std.ArrayList(u8) = undefined,
|
c: std.ArrayList(u8) = undefined,
|
||||||
|
size: terminal.Size = undefined,
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, widget: Widget) @This() {
|
pub fn init(allocator: std.mem.Allocator, widget: Widget) @This() {
|
||||||
return .{
|
return .{
|
||||||
@@ -28,6 +30,12 @@ pub fn Layout(comptime Event: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle(this: *@This(), event: Event) !*std.ArrayList(Event) {
|
pub fn handle(this: *@This(), event: Event) !*std.ArrayList(Event) {
|
||||||
|
switch (event) {
|
||||||
|
.resize => |size| {
|
||||||
|
this.size = size;
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
this.events.clearRetainingCapacity();
|
this.events.clearRetainingCapacity();
|
||||||
if (this.widget.handle(event)) |e| {
|
if (this.widget.handle(event)) |e| {
|
||||||
try this.events.append(e);
|
try this.events.append(e);
|
||||||
|
|||||||
38
src/main.zig
38
src/main.zig
@@ -3,6 +3,8 @@ const terminal = @import("terminal.zig");
|
|||||||
const zlog = @import("zlog");
|
const zlog = @import("zlog");
|
||||||
|
|
||||||
const App = @import("app.zig").App(union(enum) {});
|
const App = @import("app.zig").App(union(enum) {});
|
||||||
|
const Renderer = @import("render.zig").PlainRenderer();
|
||||||
|
const Key = @import("key.zig");
|
||||||
|
|
||||||
pub const std_options = zlog.std_options;
|
pub const std_options = zlog.std_options;
|
||||||
const log = std.log.scoped(.default);
|
const log = std.log.scoped(.default);
|
||||||
@@ -20,7 +22,8 @@ pub fn main() !void {
|
|||||||
}
|
}
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
var app = App.init();
|
var app: App = .{};
|
||||||
|
var renderer: Renderer = .{};
|
||||||
|
|
||||||
var rawText = App.Widget.RawText.init(allocator);
|
var rawText = App.Widget.RawText.init(allocator);
|
||||||
const widget = App.Widget.createFrom(&rawText);
|
const widget = App.Widget.createFrom(&rawText);
|
||||||
@@ -40,29 +43,36 @@ pub fn main() !void {
|
|||||||
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
.quit => break,
|
.quit => break,
|
||||||
.resize => |size| {
|
|
||||||
// NOTE: draw actions should not happen here (still here for testing)
|
|
||||||
// NOTE: clearing the screen and positioning the cursor is only necessary for full screen applications
|
|
||||||
// - in-line applications should use relative movements instead and should only clear lines (which they draw)
|
|
||||||
// - in-line applications should not enter the alt screen
|
|
||||||
try terminal.clearScreen();
|
|
||||||
try terminal.setCursorPositionHome();
|
|
||||||
log.debug("Size := [x: {d}, y: {d}]", .{ size.cols, size.rows });
|
|
||||||
},
|
|
||||||
.key => |key| {
|
.key => |key| {
|
||||||
log.debug("received key: {any}", .{key});
|
|
||||||
// ctrl+c to quit
|
// ctrl+c to quit
|
||||||
if (terminal.Key.matches(key, .{ .cp = 'c', .mod = .{ .ctrl = true } })) {
|
if (Key.matches(key, .{ .cp = 'c', .mod = .{ .ctrl = true } })) {
|
||||||
app.quit.set();
|
app.quit();
|
||||||
}
|
}
|
||||||
|
if (Key.matches(key, .{ .cp = 'n', .mod = .{ .ctrl = true } })) {
|
||||||
|
try app.interrupt();
|
||||||
|
defer app.start() catch @panic("could not start app event loop");
|
||||||
|
var child = std.process.Child.init(&.{"hx"}, allocator);
|
||||||
|
_ = child.spawnAndWait() catch |err| {
|
||||||
|
app.postEvent(.{
|
||||||
|
.err = .{
|
||||||
|
.err = err,
|
||||||
|
.msg = "Spawning Helix failed",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.err => |err| {
|
||||||
|
log.err("Received {any} with message: {s}", .{ err.err, err.msg });
|
||||||
},
|
},
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
|
// NOTE: this currently re-renders the screen for every key-press -> which might be a bit of an overkill
|
||||||
const events = try layout.handle(event);
|
const events = try layout.handle(event);
|
||||||
for (events.items) |e| {
|
for (events.items) |e| {
|
||||||
app.postEvent(e);
|
app.postEvent(e);
|
||||||
}
|
}
|
||||||
log.debug("Layout result: {s}", .{(try layout.content()).items});
|
try renderer.render(try layout.content());
|
||||||
}
|
}
|
||||||
// TODO: I could use the ascii codes in vaxis
|
// TODO: I could use the ascii codes in vaxis
|
||||||
// - see https://gist.github.com/ConnerWill/d4b6c776b509add763e17f9f113fd25b
|
// - see https://gist.github.com/ConnerWill/d4b6c776b509add763e17f9f113fd25b
|
||||||
|
|||||||
48
src/render.zig
Normal file
48
src/render.zig
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
//! Renderer which holds the screen to compare with the previous screen for efficient rendering.
|
||||||
|
const std = @import("std");
|
||||||
|
const terminal = @import("terminal.zig");
|
||||||
|
|
||||||
|
pub fn BufferedRenderer() type {
|
||||||
|
return struct {
|
||||||
|
refresh: bool = false,
|
||||||
|
size: terminal.Size = undefined,
|
||||||
|
screen: std.ArrayList(u8) = undefined,
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) @This() {
|
||||||
|
return .{
|
||||||
|
.screen = std.ArrayList(u8).init(allocator),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(this: *@This()) void {
|
||||||
|
this.screen.deinit();
|
||||||
|
this.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resize(this: *@This(), size: terminal.Size) void {
|
||||||
|
// TODO: are there size changes which impact the corresponding rendered content?
|
||||||
|
// -> can I even be sure nothing needs to be re-rendered?
|
||||||
|
this.size = size;
|
||||||
|
this.refresh = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(this: *@This(), content: *std.ArrayList(u8)) !void {
|
||||||
|
// TODO: put the corresponding screen to the terminal
|
||||||
|
// -> determine diff between screen and new content and only update the corresponding characters of the terminal
|
||||||
|
_ = this;
|
||||||
|
_ = content;
|
||||||
|
@panic("Not yet implemented.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn PlainRenderer() type {
|
||||||
|
return struct {
|
||||||
|
pub fn render(this: *@This(), content: *std.ArrayList(u8)) !void {
|
||||||
|
_ = this;
|
||||||
|
try terminal.clearScreen();
|
||||||
|
try terminal.setCursorPositionHome();
|
||||||
|
_ = try terminal.write(content.items);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
23
src/style.zig
Normal file
23
src/style.zig
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
//! Helper function collection to provide ascii encodings for styling outputs.
|
||||||
|
//! Stylings are implemented such that they can be nested in anyway to support
|
||||||
|
//! multiple styles (i.e. bold and italic).
|
||||||
|
//!
|
||||||
|
//! Stylings however also include highlighting for specific terminal capabilities.
|
||||||
|
//! For example url highlighting.
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
// TODO: implement helper functions for the following stylings:
|
||||||
|
// - bold
|
||||||
|
// - italic
|
||||||
|
// - underline
|
||||||
|
// - curly line
|
||||||
|
// - strike through
|
||||||
|
// - reverse
|
||||||
|
// - blink
|
||||||
|
// - color:
|
||||||
|
// - foreground
|
||||||
|
// - background
|
||||||
|
|
||||||
|
// TODO: implement helper functions for terminal capabilities:
|
||||||
|
// - links / url display (osc 8)
|
||||||
|
// - show / hide cursor?
|
||||||
136
src/terminal.zig
136
src/terminal.zig
@@ -1,4 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const Key = @import("key.zig");
|
||||||
pub const code_point = @import("code_point");
|
pub const code_point = @import("code_point");
|
||||||
|
|
||||||
const log = std.log.scoped(.terminal);
|
const log = std.log.scoped(.terminal);
|
||||||
@@ -13,137 +14,6 @@ pub const Position = struct {
|
|||||||
row: u16,
|
row: u16,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Key = struct {
|
|
||||||
cp: u21,
|
|
||||||
mod: Modifier = .{},
|
|
||||||
|
|
||||||
pub fn matches(self: Key, other: Key) bool {
|
|
||||||
return std.meta.eql(self, other);
|
|
||||||
}
|
|
||||||
|
|
||||||
// codepoints for keys
|
|
||||||
pub const tab: u21 = 0x09;
|
|
||||||
pub const enter: u21 = 0x0D;
|
|
||||||
pub const escape: u21 = 0x1B;
|
|
||||||
pub const space: u21 = 0x20;
|
|
||||||
pub const backspace: u21 = 0x7F;
|
|
||||||
|
|
||||||
// kitty key encodings (re-used here)
|
|
||||||
pub const insert: u21 = 57348;
|
|
||||||
pub const delete: u21 = 57349;
|
|
||||||
pub const left: u21 = 57350;
|
|
||||||
pub const right: u21 = 57351;
|
|
||||||
pub const up: u21 = 57352;
|
|
||||||
pub const down: u21 = 57353;
|
|
||||||
pub const page_up: u21 = 57354;
|
|
||||||
pub const page_down: u21 = 57355;
|
|
||||||
pub const home: u21 = 57356;
|
|
||||||
pub const end: u21 = 57357;
|
|
||||||
pub const caps_lock: u21 = 57358;
|
|
||||||
pub const scroll_lock: u21 = 57359;
|
|
||||||
pub const num_lock: u21 = 57360;
|
|
||||||
pub const print_screen: u21 = 57361;
|
|
||||||
pub const pause: u21 = 57362;
|
|
||||||
pub const menu: u21 = 57363;
|
|
||||||
pub const f1: u21 = 57364;
|
|
||||||
pub const f2: u21 = 57365;
|
|
||||||
pub const f3: u21 = 57366;
|
|
||||||
pub const f4: u21 = 57367;
|
|
||||||
pub const f5: u21 = 57368;
|
|
||||||
pub const f6: u21 = 57369;
|
|
||||||
pub const f7: u21 = 57370;
|
|
||||||
pub const f8: u21 = 57371;
|
|
||||||
pub const f9: u21 = 57372;
|
|
||||||
pub const f10: u21 = 57373;
|
|
||||||
pub const f11: u21 = 57374;
|
|
||||||
pub const f12: u21 = 57375;
|
|
||||||
pub const f13: u21 = 57376;
|
|
||||||
pub const f14: u21 = 57377;
|
|
||||||
pub const f15: u21 = 57378;
|
|
||||||
pub const @"f16": u21 = 57379;
|
|
||||||
pub const f17: u21 = 57380;
|
|
||||||
pub const f18: u21 = 57381;
|
|
||||||
pub const f19: u21 = 57382;
|
|
||||||
pub const f20: u21 = 57383;
|
|
||||||
pub const f21: u21 = 57384;
|
|
||||||
pub const f22: u21 = 57385;
|
|
||||||
pub const f23: u21 = 57386;
|
|
||||||
pub const f24: u21 = 57387;
|
|
||||||
pub const f25: u21 = 57388;
|
|
||||||
pub const f26: u21 = 57389;
|
|
||||||
pub const f27: u21 = 57390;
|
|
||||||
pub const f28: u21 = 57391;
|
|
||||||
pub const f29: u21 = 57392;
|
|
||||||
pub const f30: u21 = 57393;
|
|
||||||
pub const f31: u21 = 57394;
|
|
||||||
pub const @"f32": u21 = 57395;
|
|
||||||
pub const f33: u21 = 57396;
|
|
||||||
pub const f34: u21 = 57397;
|
|
||||||
pub const f35: u21 = 57398;
|
|
||||||
pub const kp_0: u21 = 57399;
|
|
||||||
pub const kp_1: u21 = 57400;
|
|
||||||
pub const kp_2: u21 = 57401;
|
|
||||||
pub const kp_3: u21 = 57402;
|
|
||||||
pub const kp_4: u21 = 57403;
|
|
||||||
pub const kp_5: u21 = 57404;
|
|
||||||
pub const kp_6: u21 = 57405;
|
|
||||||
pub const kp_7: u21 = 57406;
|
|
||||||
pub const kp_8: u21 = 57407;
|
|
||||||
pub const kp_9: u21 = 57408;
|
|
||||||
pub const kp_decimal: u21 = 57409;
|
|
||||||
pub const kp_divide: u21 = 57410;
|
|
||||||
pub const kp_multiply: u21 = 57411;
|
|
||||||
pub const kp_subtract: u21 = 57412;
|
|
||||||
pub const kp_add: u21 = 57413;
|
|
||||||
pub const kp_enter: u21 = 57414;
|
|
||||||
pub const kp_equal: u21 = 57415;
|
|
||||||
pub const kp_separator: u21 = 57416;
|
|
||||||
pub const kp_left: u21 = 57417;
|
|
||||||
pub const kp_right: u21 = 57418;
|
|
||||||
pub const kp_up: u21 = 57419;
|
|
||||||
pub const kp_down: u21 = 57420;
|
|
||||||
pub const kp_page_up: u21 = 57421;
|
|
||||||
pub const kp_page_down: u21 = 57422;
|
|
||||||
pub const kp_home: u21 = 57423;
|
|
||||||
pub const kp_end: u21 = 57424;
|
|
||||||
pub const kp_insert: u21 = 57425;
|
|
||||||
pub const kp_delete: u21 = 57426;
|
|
||||||
pub const kp_begin: u21 = 57427;
|
|
||||||
pub const media_play: u21 = 57428;
|
|
||||||
pub const media_pause: u21 = 57429;
|
|
||||||
pub const media_play_pause: u21 = 57430;
|
|
||||||
pub const media_reverse: u21 = 57431;
|
|
||||||
pub const media_stop: u21 = 57432;
|
|
||||||
pub const media_fast_forward: u21 = 57433;
|
|
||||||
pub const media_rewind: u21 = 57434;
|
|
||||||
pub const media_track_next: u21 = 57435;
|
|
||||||
pub const media_track_previous: u21 = 57436;
|
|
||||||
pub const media_record: u21 = 57437;
|
|
||||||
pub const lower_volume: u21 = 57438;
|
|
||||||
pub const raise_volume: u21 = 57439;
|
|
||||||
pub const mute_volume: u21 = 57440;
|
|
||||||
pub const left_shift: u21 = 57441;
|
|
||||||
pub const left_control: u21 = 57442;
|
|
||||||
pub const left_alt: u21 = 57443;
|
|
||||||
pub const left_super: u21 = 57444;
|
|
||||||
pub const left_hyper: u21 = 57445;
|
|
||||||
pub const left_meta: u21 = 57446;
|
|
||||||
pub const right_shift: u21 = 57447;
|
|
||||||
pub const right_control: u21 = 57448;
|
|
||||||
pub const right_alt: u21 = 57449;
|
|
||||||
pub const right_super: u21 = 57450;
|
|
||||||
pub const right_hyper: u21 = 57451;
|
|
||||||
pub const right_meta: u21 = 57452;
|
|
||||||
pub const iso_level_3_shift: u21 = 57453;
|
|
||||||
pub const iso_level_5_shift: u21 = 57454;
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Modifier = struct {
|
|
||||||
shift: bool = false,
|
|
||||||
alt: bool = false,
|
|
||||||
ctrl: bool = false,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ref: https://vt100.net/docs/vt510-rm/DECRPM.html
|
// Ref: https://vt100.net/docs/vt510-rm/DECRPM.html
|
||||||
pub const ReportMode = enum {
|
pub const ReportMode = enum {
|
||||||
not_recognized,
|
not_recognized,
|
||||||
@@ -188,6 +58,10 @@ pub fn read(buf: []u8) !usize {
|
|||||||
return try std.posix.read(std.posix.STDIN_FILENO, buf);
|
return try std.posix.read(std.posix.STDIN_FILENO, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write(buf: []const u8) !usize {
|
||||||
|
return try std.posix.write(std.posix.STDERR_FILENO, buf);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn getCursorPosition() !Position {
|
pub fn getCursorPosition() !Position {
|
||||||
// Needs Raw mode (no wait for \n) to work properly cause
|
// Needs Raw mode (no wait for \n) to work properly cause
|
||||||
// control sequence will not be written without it.
|
// control sequence will not be written without it.
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const terminal = @import("../terminal.zig");
|
||||||
|
|
||||||
const isTaggedUnion = @import("../event.zig").isTaggedUnion;
|
const isTaggedUnion = @import("../event.zig").isTaggedUnion;
|
||||||
const Error = @import("../event.zig").Error;
|
const Error = @import("../event.zig").Error;
|
||||||
|
const Key = @import("../key.zig");
|
||||||
|
|
||||||
pub fn Widget(comptime Event: type) type {
|
pub fn Widget(comptime Event: type) type {
|
||||||
if (!isTaggedUnion(Event)) {
|
if (!isTaggedUnion(Event)) {
|
||||||
@@ -8,6 +11,8 @@ pub fn Widget(comptime Event: type) type {
|
|||||||
}
|
}
|
||||||
return struct {
|
return struct {
|
||||||
c: std.ArrayList(u8) = undefined,
|
c: std.ArrayList(u8) = undefined,
|
||||||
|
key: Key = undefined,
|
||||||
|
size: terminal.Size = undefined,
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator) @This() {
|
pub fn init(allocator: std.mem.Allocator) @This() {
|
||||||
return .{ .c = std.ArrayList(u8).init(allocator) };
|
return .{ .c = std.ArrayList(u8).init(allocator) };
|
||||||
@@ -19,15 +24,24 @@ pub fn Widget(comptime Event: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle(this: *@This(), event: Event) ?Event {
|
pub fn handle(this: *@This(), event: Event) ?Event {
|
||||||
// ignore the event for now
|
switch (event) {
|
||||||
_ = this;
|
// store the received size
|
||||||
_ = event;
|
.resize => |size| {
|
||||||
|
this.size = size;
|
||||||
|
},
|
||||||
|
.key => |key| {
|
||||||
|
this.key = key;
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn content(this: *@This()) !*std.ArrayList(u8) {
|
pub fn content(this: *@This()) !*std.ArrayList(u8) {
|
||||||
this.c.clearRetainingCapacity();
|
this.c.clearRetainingCapacity();
|
||||||
try this.c.appendSlice("This is a simple test");
|
const writer = this.c.writer();
|
||||||
|
try std.fmt.format(writer, "The terminal has a reported size of [cols: {d}, rows: {d}]\n", .{ this.size.cols, this.size.rows });
|
||||||
|
try std.fmt.format(writer, "User entered key: {any}\n", .{this.key});
|
||||||
return &this.c;
|
return &this.c;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user