refactor: add renderer abstraction layer
This commit is contained in:
parent
9ff63fbed5
commit
b15fa47f30
47 changed files with 1419 additions and 1023 deletions
617
src/tui/tui.zig
617
src/tui/tui.zig
|
@ -1,14 +1,14 @@
|
|||
const std = @import("std");
|
||||
const nc = @import("notcurses");
|
||||
const tp = @import("thespian");
|
||||
const log = @import("log");
|
||||
const config = @import("config");
|
||||
const project_manager = @import("project_manager");
|
||||
const build_options = @import("build_options");
|
||||
const root = @import("root");
|
||||
|
||||
const tracy = @import("tracy");
|
||||
|
||||
pub const renderer = @import("renderer");
|
||||
|
||||
const command = @import("command.zig");
|
||||
const WidgetStack = @import("WidgetStack.zig");
|
||||
const Widget = @import("Widget.zig");
|
||||
|
@ -25,7 +25,7 @@ const Mutex = std.Thread.Mutex;
|
|||
const maxInt = std.math.maxInt;
|
||||
|
||||
a: Allocator,
|
||||
nc: nc.Context,
|
||||
rdr: renderer,
|
||||
config: config,
|
||||
frame_time: usize, // in microseconds
|
||||
frame_clock: tp.metronome,
|
||||
|
@ -43,32 +43,17 @@ mini_mode: ?MiniModeState = null,
|
|||
hover_focus: ?*Widget = null,
|
||||
commands: Commands = undefined,
|
||||
logger: log.Logger,
|
||||
drag: bool = false,
|
||||
drag_event: nc.Input = nc.input(),
|
||||
drag_source: ?*Widget = null,
|
||||
theme: Widget.Theme,
|
||||
escape_state: EscapeState = .none,
|
||||
escape_initial: ?nc.Input = null,
|
||||
escape_code: ArrayList(u8),
|
||||
bracketed_paste: bool = false,
|
||||
bracketed_paste_buffer: ArrayList(u8),
|
||||
idle_frame_count: usize = 0,
|
||||
unrendered_input_events_count: usize = 0,
|
||||
unflushed_events_count: usize = 0,
|
||||
init_timer: ?tp.timeout,
|
||||
sigwinch_signal: ?tp.signal = null,
|
||||
no_sleep: bool = false,
|
||||
mods: ModState = .{},
|
||||
final_exit: []const u8 = "normal",
|
||||
|
||||
const idle_frames = 1;
|
||||
|
||||
const ModState = struct {
|
||||
ctrl: bool = false,
|
||||
shift: bool = false,
|
||||
alt: bool = false,
|
||||
};
|
||||
|
||||
const init_delay = 1; // ms
|
||||
|
||||
const Self = @This();
|
||||
|
@ -91,20 +76,8 @@ fn start(args: StartArgs) tp.result {
|
|||
}
|
||||
|
||||
fn init(a: Allocator) !*Self {
|
||||
var opts = nc.Context.Options{
|
||||
.termtype = null,
|
||||
.loglevel = @intFromEnum(nc.LogLevel.silent),
|
||||
.margin_t = 0,
|
||||
.margin_r = 0,
|
||||
.margin_b = 0,
|
||||
.margin_l = 0,
|
||||
.flags = nc.Context.option.SUPPRESS_BANNERS | nc.Context.option.INHIBIT_SETLOCALE | nc.Context.option.NO_WINCH_SIGHANDLER,
|
||||
};
|
||||
if (tp.env.get().is("no-alternate"))
|
||||
opts.flags |= nc.Context.option.NO_ALTERNATE_SCREEN;
|
||||
const nc_ = try nc.Context.core_init(&opts, null);
|
||||
nc_.mice_enable(nc.mice.ALL_EVENTS) catch {};
|
||||
try nc_.linesigs_disable();
|
||||
var self = try a.create(Self);
|
||||
const ctx = try renderer.init(a, self, tp.env.get().is("no-alternate"));
|
||||
|
||||
var conf_buf: ?[]const u8 = null;
|
||||
var conf = root.read_config(a, &conf_buf);
|
||||
|
@ -120,18 +93,17 @@ fn init(a: Allocator) !*Self {
|
|||
const frame_time = std.time.us_per_s / conf.frame_rate;
|
||||
const frame_clock = try tp.metronome.init(frame_time);
|
||||
|
||||
const fd_stdin = try tp.file_descriptor.init("stdin", nc_.inputready_fd());
|
||||
const fd_stdin = try tp.file_descriptor.init("stdin", ctx.input_fd());
|
||||
// const fd_stdin = try tp.file_descriptor.init("stdin", std.os.STDIN_FILENO);
|
||||
const n = nc_.stdplane();
|
||||
const n = ctx.stdplane();
|
||||
|
||||
try frame_clock.start();
|
||||
try fd_stdin.wait_read();
|
||||
|
||||
var self = try a.create(Self);
|
||||
self.* = .{
|
||||
.a = a,
|
||||
.config = conf,
|
||||
.nc = nc_,
|
||||
.rdr = ctx,
|
||||
.frame_time = frame_time,
|
||||
.frame_clock = frame_clock,
|
||||
.frame_clock_running = true,
|
||||
|
@ -142,23 +114,23 @@ fn init(a: Allocator) !*Self {
|
|||
.input_mode = null,
|
||||
.input_listeners = EventHandler.List.init(a),
|
||||
.logger = log.logger("tui"),
|
||||
.escape_code = ArrayList(u8).init(a),
|
||||
.bracketed_paste_buffer = ArrayList(u8).init(a),
|
||||
.init_timer = try tp.timeout.init_ms(init_delay, tp.message.fmt(.{"init"})),
|
||||
.theme = theme,
|
||||
.no_sleep = tp.env.get().is("no-sleep"),
|
||||
};
|
||||
instance_ = self;
|
||||
defer instance_ = null;
|
||||
self.rdr.handler_ctx = self;
|
||||
self.rdr.dispatch_input = dispatch_input;
|
||||
self.rdr.dispatch_mouse = dispatch_mouse;
|
||||
self.rdr.dispatch_mouse_drag = dispatch_mouse_drag;
|
||||
self.rdr.dispatch_event = dispatch_event;
|
||||
try self.commands.init(self);
|
||||
errdefer self.deinit();
|
||||
try self.listen_sigwinch();
|
||||
self.mainview = try mainview.create(a, n);
|
||||
try self.initUI();
|
||||
try nc_.render();
|
||||
try ctx.render();
|
||||
try self.save_config();
|
||||
// self.request_mouse_cursor_support_detect();
|
||||
self.bracketed_paste_enable();
|
||||
if (tp.env.get().is("restore-session")) {
|
||||
command.executeName("restore_session", .{}) catch |e| self.logger.err("restore_session", e);
|
||||
self.logger.print("session restored", .{});
|
||||
|
@ -166,24 +138,11 @@ fn init(a: Allocator) !*Self {
|
|||
return self;
|
||||
}
|
||||
|
||||
pub fn initUI(self: *Self) !void {
|
||||
const n = self.nc.stdplane();
|
||||
var channels: u64 = 0;
|
||||
try nc.channels_set_fg_rgb(&channels, 0x88aa00);
|
||||
try nc.channels_set_bg_rgb(&channels, 0x000088);
|
||||
try nc.channels_set_bg_alpha(&channels, nc.ALPHA_TRANSPARENT);
|
||||
_ = try n.set_base(" ", 0, channels);
|
||||
try n.set_fg_rgb(0x40f040);
|
||||
try n.set_fg_rgb(0x00dddd);
|
||||
}
|
||||
|
||||
fn init_delayed(self: *Self) tp.result {
|
||||
if (self.input_mode) |_| {} else return cmds.enter_mode(self, command.Context.fmt(.{self.config.input_mode}));
|
||||
}
|
||||
|
||||
fn deinit(self: *Self) void {
|
||||
self.bracketed_paste_buffer.deinit();
|
||||
self.escape_code.deinit();
|
||||
if (self.input_mode) |*m| m.deinit();
|
||||
self.commands.deinit();
|
||||
self.fd_stdin.deinit();
|
||||
|
@ -194,7 +153,7 @@ fn deinit(self: *Self) void {
|
|||
self.frame_clock.stop() catch {};
|
||||
if (self.sigwinch_signal) |sig| sig.deinit();
|
||||
self.frame_clock.deinit();
|
||||
self.nc.stop();
|
||||
self.rdr.stop();
|
||||
self.logger.deinit();
|
||||
self.a.destroy(self);
|
||||
}
|
||||
|
@ -211,7 +170,7 @@ fn receive(self: *Self, from: tp.pid_ref, m: tp.message) tp.result {
|
|||
defer instance_ = null;
|
||||
errdefer self.deinit();
|
||||
errdefer self.fd_stdin.cancel() catch {};
|
||||
errdefer self.nc.leave_alternate_screen();
|
||||
errdefer self.rdr.leave_alternate_screen();
|
||||
self.receive_safe(from, m) catch |e| {
|
||||
if (std.mem.eql(u8, "normal", tp.error_text()))
|
||||
return e;
|
||||
|
@ -253,8 +212,8 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) tp.result {
|
|||
|
||||
if (try m.match(.{"sigwinch"})) {
|
||||
try self.listen_sigwinch();
|
||||
self.nc.refresh() catch |e| return self.logger.err("refresh", e);
|
||||
self.mainview.resize(Widget.Box.from(self.nc.stdplane()));
|
||||
self.rdr.refresh() catch |e| return self.logger.err("refresh", e);
|
||||
self.mainview.resize(Widget.Box.from(self.rdr.stdplane()));
|
||||
need_render();
|
||||
return;
|
||||
}
|
||||
|
@ -265,7 +224,7 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) tp.result {
|
|||
return;
|
||||
}
|
||||
|
||||
if (self.dispatch_input(m) catch |e| b: {
|
||||
if (self.dispatch_input_fd(m) catch |e| b: {
|
||||
self.logger.err("input dispatch", e);
|
||||
break :b true;
|
||||
})
|
||||
|
@ -341,14 +300,14 @@ fn render(self: *Self, current_time: i64) void {
|
|||
const more = ret: {
|
||||
const frame = tracy.initZone(@src(), .{ .name = "tui render" });
|
||||
defer frame.deinit();
|
||||
self.nc.stdplane().erase();
|
||||
self.rdr.stdplane().erase();
|
||||
break :ret self.mainview.render(&self.theme);
|
||||
};
|
||||
|
||||
{
|
||||
const frame = tracy.initZone(@src(), .{ .name = "notcurses render" });
|
||||
defer frame.deinit();
|
||||
self.nc.render() catch |e| self.logger.err("render", e);
|
||||
self.rdr.render() catch |e| self.logger.err("render", e);
|
||||
}
|
||||
|
||||
self.idle_frame_count = if (self.unrendered_input_events_count > 0)
|
||||
|
@ -370,14 +329,23 @@ fn render(self: *Self, current_time: i64) void {
|
|||
}
|
||||
}
|
||||
|
||||
fn dispatch_input(self: *Self, m: tp.message) error{Exit}!bool {
|
||||
fn dispatch_flush_input_event(self: *Self) tp.result {
|
||||
var buf: [32]u8 = undefined;
|
||||
if (self.input_mode) |mode|
|
||||
try mode.handler.send(tp.self_pid(), tp.message.fmtbuf(&buf, .{"F"}) catch |e| return tp.exit_error(e));
|
||||
}
|
||||
|
||||
fn dispatch_input_fd(self: *Self, m: tp.message) error{Exit}!bool {
|
||||
const frame = tracy.initZone(@src(), .{ .name = "tui input" });
|
||||
defer frame.deinit();
|
||||
var err: i64 = 0;
|
||||
var err_msg: []u8 = "";
|
||||
if (try m.match(.{ "fd", "stdin", "read_ready" })) {
|
||||
self.fd_stdin.wait_read() catch |e| return tp.exit_error(e);
|
||||
try self.dispatch_notcurses();
|
||||
self.rdr.process_input() catch |e| return tp.exit_error(e);
|
||||
try self.dispatch_flush_input_event();
|
||||
if (self.unrendered_input_events_count > 0 and !self.frame_clock_running)
|
||||
need_render();
|
||||
return true; // consume message
|
||||
}
|
||||
if (try m.match(.{ "fd", "stdin", "read_error", tp.extract(&err), tp.extract(&err_msg) })) {
|
||||
|
@ -386,199 +354,50 @@ fn dispatch_input(self: *Self, m: tp.message) error{Exit}!bool {
|
|||
return false;
|
||||
}
|
||||
|
||||
fn dispatch_notcurses(self: *Self) tp.result {
|
||||
var input_buffer: [256]nc.Input = undefined;
|
||||
|
||||
while (true) {
|
||||
const nivec = self.nc.getvec_nblock(&input_buffer) catch |e| return tp.exit_error(e);
|
||||
if (nivec.len == 0)
|
||||
break;
|
||||
for (nivec) |*ni| {
|
||||
if (ni.id == 27 or self.escape_state != .none) {
|
||||
try self.handle_escape(ni);
|
||||
continue;
|
||||
}
|
||||
self.dispatch_input_event(ni) catch |e|
|
||||
self.logger.err("input dispatch", e);
|
||||
}
|
||||
}
|
||||
if (self.escape_state == .init)
|
||||
try self.handle_escape_short();
|
||||
if (self.unflushed_events_count > 0)
|
||||
_ = try self.dispatch_flush_input_event();
|
||||
if (self.unrendered_input_events_count > 0 and !self.frame_clock_running)
|
||||
need_render();
|
||||
}
|
||||
|
||||
fn dispatch_input_event(self: *Self, ni: *nc.Input) tp.result {
|
||||
const keypress: u32 = ni.id;
|
||||
var buf: [256]u8 = undefined;
|
||||
fn dispatch_input(ctx: *anyopaque, cbor_msg: []const u8) void {
|
||||
const self: *Self = @ptrCast(@alignCast(ctx));
|
||||
const m: tp.message = .{ .buf = cbor_msg };
|
||||
const from = tp.self_pid();
|
||||
self.unrendered_input_events_count += 1;
|
||||
ni.modifiers &= nc.mod.CTRL | nc.mod.SHIFT | nc.mod.ALT | nc.mod.SUPER | nc.mod.META | nc.mod.HYPER;
|
||||
if (keypress == nc.key.RESIZE) return;
|
||||
try self.sync_mod_state(keypress, ni.modifiers);
|
||||
if (keypress == nc.key.MOTION) {
|
||||
if (ni.y == 0 and ni.x == 0 and ni.ypx == -1 and ni.xpx == -1) return;
|
||||
self.dispatch_mouse(ni.y, ni.x, tp.self_pid(), tp.message.fmtbuf(&buf, .{
|
||||
"M",
|
||||
ni.x,
|
||||
ni.y,
|
||||
ni.xpx,
|
||||
ni.ypx,
|
||||
}) catch |e| return tp.exit_error(e));
|
||||
} else if (keypress > nc.key.MOTION and keypress <= nc.key.BUTTON11) {
|
||||
if (ni.y == 0 and ni.x == 0 and ni.ypx == -1 and ni.xpx == -1) return;
|
||||
if (try self.detect_drag(ni)) return;
|
||||
self.dispatch_mouse(ni.y, ni.x, tp.self_pid(), tp.message.fmtbuf(&buf, .{
|
||||
"B",
|
||||
ni.evtype,
|
||||
keypress,
|
||||
nc.key_string(ni),
|
||||
ni.x,
|
||||
ni.y,
|
||||
ni.xpx,
|
||||
ni.ypx,
|
||||
}) catch |e| return tp.exit_error(e));
|
||||
} else {
|
||||
self.unflushed_events_count += 1;
|
||||
self.send_input(tp.self_pid(), tp.message.fmtbuf(&buf, .{
|
||||
"I",
|
||||
normalized_evtype(ni.evtype),
|
||||
keypress,
|
||||
if (@hasField(nc.Input, "eff_text")) ni.eff_text[0] else keypress,
|
||||
nc.key_string(ni),
|
||||
ni.modifiers,
|
||||
}) catch |e| return tp.exit_error(e));
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_flush_input_event(self: *Self) error{Exit}!bool {
|
||||
var buf: [32]u8 = undefined;
|
||||
self.unflushed_events_count = 0;
|
||||
tp.trace(tp.channel.input, m);
|
||||
self.input_listeners.send(from, m) catch {};
|
||||
if (self.keyboard_focus) |w|
|
||||
if (w.send(from, m) catch |e| ret: {
|
||||
self.logger.err("focus", e);
|
||||
break :ret false;
|
||||
})
|
||||
return;
|
||||
if (self.input_mode) |mode|
|
||||
try mode.handler.send(tp.self_pid(), tp.message.fmtbuf(&buf, .{"F"}) catch |e| return tp.exit_error(e));
|
||||
return false;
|
||||
mode.handler.send(from, m) catch |e| self.logger.err("input handler", e);
|
||||
}
|
||||
|
||||
fn detect_drag(self: *Self, ni: *nc.Input) error{Exit}!bool {
|
||||
return switch (ni.id) {
|
||||
nc.key.BUTTON1...nc.key.BUTTON3, nc.key.BUTTON6...nc.key.BUTTON9 => if (self.drag) self.detect_drag_end(ni) else self.detect_drag_begin(ni),
|
||||
else => false,
|
||||
};
|
||||
fn dispatch_mouse(ctx: *anyopaque, y: c_int, x: c_int, cbor_msg: []const u8) void {
|
||||
const self: *Self = @ptrCast(@alignCast(ctx));
|
||||
const m: tp.message = .{ .buf = cbor_msg };
|
||||
const from = tp.self_pid();
|
||||
self.unrendered_input_events_count += 1;
|
||||
self.send_mouse(y, x, from, m) catch |e| self.logger.err("dispatch mouse", e);
|
||||
}
|
||||
|
||||
fn detect_drag_begin(self: *Self, ni: *nc.Input) error{Exit}!bool {
|
||||
if (ni.evtype == nc.event_type.PRESS and self.drag_event.id == ni.id) {
|
||||
self.drag_source = self.find_coord_widget(@intCast(self.drag_event.y), @intCast(self.drag_event.x));
|
||||
self.drag_event = ni.*;
|
||||
self.drag = true;
|
||||
var buf: [256]u8 = undefined;
|
||||
_ = try self.send_mouse_drag(ni.y, ni.x, tp.self_pid(), tp.message.fmtbuf(&buf, .{
|
||||
"D",
|
||||
nc.event_type.PRESS,
|
||||
ni.id,
|
||||
nc.key_string(ni),
|
||||
ni.x,
|
||||
ni.y,
|
||||
ni.xpx,
|
||||
ni.ypx,
|
||||
}) catch |e| return tp.exit_error(e));
|
||||
return true;
|
||||
}
|
||||
if (ni.evtype == nc.event_type.PRESS)
|
||||
self.drag_event = ni.*
|
||||
else
|
||||
self.drag_event = nc.input();
|
||||
return false;
|
||||
}
|
||||
|
||||
fn detect_drag_end(self: *Self, ni: *nc.Input) error{Exit}!bool {
|
||||
var buf: [256]u8 = undefined;
|
||||
if (ni.id == self.drag_event.id and ni.evtype != nc.event_type.PRESS) {
|
||||
_ = try self.send_mouse_drag(ni.y, ni.x, tp.self_pid(), tp.message.fmtbuf(&buf, .{
|
||||
"D",
|
||||
nc.event_type.RELEASE,
|
||||
ni.id,
|
||||
nc.key_string(ni),
|
||||
ni.x,
|
||||
ni.y,
|
||||
ni.xpx,
|
||||
ni.ypx,
|
||||
}) catch |e| return tp.exit_error(e));
|
||||
self.drag = false;
|
||||
self.drag_event = nc.input();
|
||||
fn dispatch_mouse_drag(ctx: *anyopaque, y: c_int, x: c_int, dragging: bool, cbor_msg: []const u8) void {
|
||||
const self: *Self = @ptrCast(@alignCast(ctx));
|
||||
const m: tp.message = .{ .buf = cbor_msg };
|
||||
const from = tp.self_pid();
|
||||
self.unrendered_input_events_count += 1;
|
||||
if (dragging) {
|
||||
if (self.drag_source == null) self.drag_source = self.find_coord_widget(@intCast(y), @intCast(x));
|
||||
} else {
|
||||
self.drag_source = null;
|
||||
return true;
|
||||
}
|
||||
_ = try self.send_mouse_drag(ni.y, ni.x, tp.self_pid(), tp.message.fmtbuf(&buf, .{
|
||||
"D",
|
||||
ni.evtype,
|
||||
ni.id,
|
||||
nc.key_string(ni),
|
||||
ni.x,
|
||||
ni.y,
|
||||
ni.xpx,
|
||||
ni.ypx,
|
||||
}) catch |e| return tp.exit_error(e));
|
||||
return true;
|
||||
self.send_mouse_drag(y, x, from, m) catch |e| self.logger.err("dispatch mouse", e);
|
||||
}
|
||||
|
||||
fn normalized_evtype(evtype: c_uint) c_uint {
|
||||
return if (evtype == nc.event_type.UNKNOWN) @as(c_uint, @intCast(nc.event_type.PRESS)) else evtype;
|
||||
}
|
||||
|
||||
const EscapeState = enum { none, init, OSC, st, CSI };
|
||||
|
||||
fn handle_escape(self: *Self, ni: *nc.Input) tp.result {
|
||||
switch (self.escape_state) {
|
||||
.none => switch (ni.id) {
|
||||
'\x1B' => {
|
||||
self.escape_state = .init;
|
||||
self.escape_initial = ni.*;
|
||||
},
|
||||
else => unreachable,
|
||||
},
|
||||
.init => switch (ni.id) {
|
||||
']' => self.escape_state = .OSC,
|
||||
'[' => self.escape_state = .CSI,
|
||||
else => {
|
||||
try self.handle_escape_short();
|
||||
_ = try self.dispatch_input_event(ni);
|
||||
},
|
||||
},
|
||||
.OSC => switch (ni.id) {
|
||||
'\x1B' => self.escape_state = .st,
|
||||
'\\' => try self.handle_OSC_escape_code(),
|
||||
' '...'\\' - 1, '\\' + 1...127 => {
|
||||
const p = self.escape_code.addOne() catch |e| return tp.exit_error(e);
|
||||
p.* = @intCast(ni.id);
|
||||
},
|
||||
else => try self.handle_OSC_escape_code(),
|
||||
},
|
||||
.st => switch (ni.id) {
|
||||
'\\' => try self.handle_OSC_escape_code(),
|
||||
else => try self.handle_OSC_escape_code(),
|
||||
},
|
||||
.CSI => switch (ni.id) {
|
||||
'0'...'9', ';', ' ', '-', '?' => {
|
||||
const p = self.escape_code.addOne() catch |e| return tp.exit_error(e);
|
||||
p.* = @intCast(ni.id);
|
||||
},
|
||||
else => {
|
||||
const p = self.escape_code.addOne() catch |e| return tp.exit_error(e);
|
||||
p.* = @intCast(ni.id);
|
||||
try self.handle_CSI_escape_code();
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_escape_short(self: *Self) tp.result {
|
||||
self.escape_code.clearAndFree();
|
||||
self.escape_state = .none;
|
||||
defer self.escape_initial = null;
|
||||
if (self.escape_initial) |*ni|
|
||||
_ = try self.dispatch_input_event(ni);
|
||||
fn dispatch_event(ctx: *anyopaque, cbor_msg: []const u8) void {
|
||||
const self: *Self = @ptrCast(@alignCast(ctx));
|
||||
const m: tp.message = .{ .buf = cbor_msg };
|
||||
self.unrendered_input_events_count += 1;
|
||||
self.dispatch_flush_input_event() catch |e| self.logger.err("dispatch event flush", e);
|
||||
tp.self_pid().send_raw(m) catch |e| self.logger.err("dispatch event", e);
|
||||
}
|
||||
|
||||
fn find_coord_widget(self: *Self, y: usize, x: usize) ?*Widget {
|
||||
|
@ -626,11 +445,6 @@ fn send_widgets(self: *Self, from: tp.pid_ref, m: tp.message) error{Exit}!bool {
|
|||
self.mainview.send(from, m);
|
||||
}
|
||||
|
||||
fn dispatch_mouse(self: *Self, y: c_int, x: c_int, from: tp.pid_ref, m: tp.message) void {
|
||||
self.send_mouse(y, x, from, m) catch |e|
|
||||
self.logger.err("dispatch mouse", e);
|
||||
}
|
||||
|
||||
fn send_mouse(self: *Self, y: c_int, x: c_int, from: tp.pid_ref, m: tp.message) tp.result {
|
||||
tp.trace(tp.channel.input, m);
|
||||
_ = self.input_listeners.send(from, m) catch {};
|
||||
|
@ -656,12 +470,12 @@ fn send_mouse(self: *Self, y: c_int, x: c_int, from: tp.pid_ref, m: tp.message)
|
|||
}
|
||||
}
|
||||
|
||||
fn send_mouse_drag(self: *Self, y: c_int, x: c_int, from: tp.pid_ref, m: tp.message) error{Exit}!bool {
|
||||
fn send_mouse_drag(self: *Self, y: c_int, x: c_int, from: tp.pid_ref, m: tp.message) tp.result {
|
||||
tp.trace(tp.channel.input, m);
|
||||
_ = self.input_listeners.send(from, m) catch {};
|
||||
if (self.keyboard_focus) |w| {
|
||||
_ = try w.send(from, m);
|
||||
return false;
|
||||
return;
|
||||
} else if (self.find_coord_widget(@intCast(y), @intCast(x))) |w| {
|
||||
if (if (self.hover_focus) |h| h != w else true) {
|
||||
var buf: [256]u8 = undefined;
|
||||
|
@ -679,70 +493,13 @@ fn send_mouse_drag(self: *Self, y: c_int, x: c_int, from: tp.pid_ref, m: tp.mess
|
|||
}
|
||||
self.hover_focus = null;
|
||||
}
|
||||
return if (self.drag_source) |w|
|
||||
w.send(from, m)
|
||||
else
|
||||
false;
|
||||
}
|
||||
|
||||
fn send_input(self: *Self, from: tp.pid_ref, m: tp.message) void {
|
||||
tp.trace(tp.channel.input, m);
|
||||
if (self.bracketed_paste and self.handle_bracketed_paste_input(m) catch |e| {
|
||||
self.bracketed_paste_buffer.clearAndFree();
|
||||
self.bracketed_paste = false;
|
||||
return self.logger.err("bracketed paste input handler", e);
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
self.input_listeners.send(from, m) catch {};
|
||||
if (self.keyboard_focus) |w|
|
||||
if (w.send(from, m) catch |e| ret: {
|
||||
self.logger.err("focus", e);
|
||||
break :ret false;
|
||||
})
|
||||
return;
|
||||
if (self.input_mode) |mode|
|
||||
mode.handler.send(from, m) catch |e| self.logger.err("input handler", e);
|
||||
if (self.drag_source) |w| _ = try w.send(from, m);
|
||||
}
|
||||
|
||||
pub fn save_config(self: *const Self) !void {
|
||||
try root.write_config(self.config, self.a);
|
||||
}
|
||||
|
||||
fn sync_mod_state(self: *Self, keypress: u32, modifiers: u32) tp.result {
|
||||
if (keypress == nc.key.LCTRL or keypress == nc.key.RCTRL or keypress == nc.key.LALT or keypress == nc.key.RALT or
|
||||
keypress == nc.key.LSHIFT or keypress == nc.key.RSHIFT or keypress == nc.key.LSUPER or keypress == nc.key.RSUPER) return;
|
||||
if (nc.isCtrl(modifiers) and !self.mods.ctrl)
|
||||
try self.send_key(nc.event_type.PRESS, nc.key.LCTRL, "lctrl", modifiers);
|
||||
if (!nc.isCtrl(modifiers) and self.mods.ctrl)
|
||||
try self.send_key(nc.event_type.RELEASE, nc.key.LCTRL, "lctrl", modifiers);
|
||||
if (nc.isAlt(modifiers) and !self.mods.alt)
|
||||
try self.send_key(nc.event_type.PRESS, nc.key.LALT, "lalt", modifiers);
|
||||
if (!nc.isAlt(modifiers) and self.mods.alt)
|
||||
try self.send_key(nc.event_type.RELEASE, nc.key.LALT, "lalt", modifiers);
|
||||
if (nc.isShift(modifiers) and !self.mods.shift)
|
||||
try self.send_key(nc.event_type.PRESS, nc.key.LSHIFT, "lshift", modifiers);
|
||||
if (!nc.isShift(modifiers) and self.mods.shift)
|
||||
try self.send_key(nc.event_type.RELEASE, nc.key.LSHIFT, "lshift", modifiers);
|
||||
self.mods = .{
|
||||
.ctrl = nc.isCtrl(modifiers),
|
||||
.alt = nc.isAlt(modifiers),
|
||||
.shift = nc.isShift(modifiers),
|
||||
};
|
||||
}
|
||||
|
||||
fn send_key(self: *Self, event_type: c_int, keypress: u32, key_string: []const u8, modifiers: u32) tp.result {
|
||||
var buf: [256]u8 = undefined;
|
||||
self.send_input(tp.self_pid(), tp.message.fmtbuf(&buf, .{
|
||||
"I",
|
||||
event_type,
|
||||
keypress,
|
||||
keypress,
|
||||
key_string,
|
||||
modifiers,
|
||||
}) catch |e| return tp.exit_error(e));
|
||||
}
|
||||
|
||||
const cmds = struct {
|
||||
pub const Target = Self;
|
||||
const Ctx = command.Context;
|
||||
|
@ -757,13 +514,13 @@ const cmds = struct {
|
|||
var buf: [256]u8 = undefined;
|
||||
var buf_parent: [256]u8 = undefined;
|
||||
var z: i32 = 0;
|
||||
var n = self.nc.stdplane();
|
||||
var n = self.rdr.stdplane();
|
||||
while (n.below()) |n_| : (n = n_) {
|
||||
z -= 1;
|
||||
l.print("{d} {s} {s}", .{ z, n_.name(&buf), n_.parent().name(&buf_parent) });
|
||||
}
|
||||
z = 0;
|
||||
n = self.nc.stdplane();
|
||||
n = self.rdr.stdplane();
|
||||
while (n.above()) |n_| : (n = n_) {
|
||||
z += 1;
|
||||
l.print("{d} {s} {s}", .{ z, n_.name(&buf), n_.parent().name(&buf_parent) });
|
||||
|
@ -925,238 +682,6 @@ pub fn current() *Self {
|
|||
return if (instance_) |p| p else @panic("tui call out of context");
|
||||
}
|
||||
|
||||
const OSC = "\x1B]"; // Operating System Command
|
||||
const ST = "\x1B\\"; // String Terminator
|
||||
const BEL = "\x07";
|
||||
const OSC0_title = OSC ++ "0;";
|
||||
const OSC52_clipboard = OSC ++ "52;c;";
|
||||
const OSC52_clipboard_paste = OSC ++ "52;p;";
|
||||
const OSC22_cursor = OSC ++ "22;";
|
||||
const OSC22_cursor_reply = OSC ++ "22:";
|
||||
|
||||
pub fn set_terminal_title(text: []const u8) void {
|
||||
var writer = std.io.getStdOut().writer();
|
||||
var buf: [std.posix.PATH_MAX]u8 = undefined;
|
||||
const term_cmd = std.fmt.bufPrint(&buf, OSC0_title ++ "{s}" ++ BEL, .{text}) catch return;
|
||||
_ = writer.write(term_cmd) catch return;
|
||||
}
|
||||
|
||||
pub fn copy_to_system_clipboard(self: *const Self, text: []const u8) void {
|
||||
self.copy_to_system_clipboard_with_errors(text) catch |e| self.logger.err("copy_to_system_clipboard", e);
|
||||
}
|
||||
|
||||
pub fn copy_to_system_clipboard_with_errors(self: *const Self, text: []const u8) !void {
|
||||
var writer = std.io.getStdOut().writer();
|
||||
const encoder = std.base64.standard.Encoder;
|
||||
const size = OSC52_clipboard.len + encoder.calcSize(text.len) + ST.len;
|
||||
const buf = try self.a.alloc(u8, size);
|
||||
defer self.a.free(buf);
|
||||
@memcpy(buf[0..OSC52_clipboard.len], OSC52_clipboard);
|
||||
const b64 = encoder.encode(buf[OSC52_clipboard.len..], text);
|
||||
@memcpy(buf[OSC52_clipboard.len + b64.len ..], ST);
|
||||
_ = try writer.write(buf);
|
||||
}
|
||||
|
||||
pub fn write_stdout(self: *const Self, bytes: []const u8) void {
|
||||
_ = std.io.getStdOut().writer().write(bytes) catch |e| self.logger.err("stdout", e);
|
||||
}
|
||||
|
||||
pub fn request_system_clipboard(self: *const Self) void {
|
||||
self.write_stdout(OSC52_clipboard ++ "?" ++ ST);
|
||||
}
|
||||
|
||||
pub fn request_mouse_cursor_text(self: *const Self, push_or_pop: bool) void {
|
||||
if (push_or_pop) self.mouse_cursor_push("text") else self.mouse_cursor_pop();
|
||||
}
|
||||
|
||||
pub fn request_mouse_cursor_pointer(self: *const Self, push_or_pop: bool) void {
|
||||
if (push_or_pop) self.mouse_cursor_push("pointer") else self.mouse_cursor_pop();
|
||||
}
|
||||
|
||||
pub fn request_mouse_cursor_default(self: *const Self, push_or_pop: bool) void {
|
||||
if (push_or_pop) self.mouse_cursor_push("default") else self.mouse_cursor_pop();
|
||||
}
|
||||
|
||||
fn mouse_cursor_push(self: *const Self, comptime name: []const u8) void {
|
||||
self.write_stdout(OSC22_cursor ++ name ++ ST);
|
||||
}
|
||||
|
||||
fn mouse_cursor_pop(self: *const Self) void {
|
||||
self.write_stdout(OSC22_cursor ++ "default" ++ ST);
|
||||
}
|
||||
|
||||
fn match_code(self: *const Self, match: []const u8, skip: usize) bool {
|
||||
const code = self.escape_code.items;
|
||||
if (!(code.len >= match.len - skip)) return false;
|
||||
const code_prefix = code[0 .. match.len - skip];
|
||||
return std.mem.eql(u8, match[skip..], code_prefix);
|
||||
}
|
||||
|
||||
fn handle_OSC_escape_code(self: *Self) tp.result {
|
||||
self.escape_state = .none;
|
||||
self.escape_initial = null;
|
||||
defer self.escape_code.clearAndFree();
|
||||
const code = self.escape_code.items;
|
||||
if (self.match_code(OSC52_clipboard, OSC.len))
|
||||
return self.handle_system_clipboard(code[OSC52_clipboard.len - OSC.len ..]);
|
||||
if (self.match_code(OSC52_clipboard_paste, OSC.len))
|
||||
return self.handle_system_clipboard(code[OSC52_clipboard_paste.len - OSC.len ..]);
|
||||
if (self.match_code(OSC22_cursor_reply, OSC.len))
|
||||
return self.handle_mouse_cursor(code[OSC22_cursor_reply.len - OSC.len ..]);
|
||||
self.logger.print("ignored escape code: OSC {s}", .{std.fmt.fmtSliceEscapeLower(code)});
|
||||
}
|
||||
|
||||
fn handle_system_clipboard(self: *Self, base64: []const u8) tp.result {
|
||||
const decoder = std.base64.standard.Decoder;
|
||||
// try self.logger.print("clipboard: b64 {s}", .{base64});
|
||||
const text = self.a.alloc(
|
||||
u8,
|
||||
decoder.calcSizeForSlice(base64) catch |e| return tp.exit_error(e),
|
||||
) catch |e| return tp.exit_error(e);
|
||||
decoder.decode(text, base64) catch |e| return tp.exit_error(e);
|
||||
// try self.logger.print("clipboard: txt {s}", .{std.fmt.fmtSliceEscapeLower(text)});
|
||||
return tp.self_pid().send(.{ "system_clipboard", text });
|
||||
}
|
||||
|
||||
fn handle_mouse_cursor(self: *Self, text: []const u8) tp.result {
|
||||
self.logger.print("mouse cursor report: {s}", .{text});
|
||||
}
|
||||
|
||||
const CSI = "\x1B["; // Control Sequence Introducer
|
||||
const CSI_bracketed_paste_enable = CSI ++ "?2004h";
|
||||
const CSI_bracketed_paste_disable = CSI ++ "?2004h";
|
||||
const CIS_bracketed_paste_begin = CSI ++ "200~";
|
||||
const CIS_bracketed_paste_end = CSI ++ "201~";
|
||||
|
||||
fn handle_CSI_escape_code(self: *Self) tp.result {
|
||||
self.escape_state = .none;
|
||||
self.escape_initial = null;
|
||||
defer self.escape_code.clearAndFree();
|
||||
const code = self.escape_code.items;
|
||||
if (self.match_code(CIS_bracketed_paste_begin, CSI.len))
|
||||
return self.handle_bracketed_paste_begin();
|
||||
if (self.match_code(CIS_bracketed_paste_end, CSI.len))
|
||||
return self.handle_bracketed_paste_end();
|
||||
self.logger.print("ignored escape code: CSI {s}", .{std.fmt.fmtSliceEscapeLower(code)});
|
||||
}
|
||||
|
||||
fn handle_bracketed_paste_begin(self: *Self) tp.result {
|
||||
_ = try self.dispatch_flush_input_event();
|
||||
self.bracketed_paste_buffer.clearAndFree();
|
||||
self.bracketed_paste = true;
|
||||
}
|
||||
|
||||
fn handle_bracketed_paste_input(self: *Self, m: tp.message) !bool {
|
||||
var keypress: u32 = undefined;
|
||||
var egc: u32 = undefined;
|
||||
if (try m.match(.{ "I", tp.number, tp.extract(&keypress), tp.extract(&egc), tp.string, 0 })) {
|
||||
switch (keypress) {
|
||||
nc.key.ENTER => try self.bracketed_paste_buffer.appendSlice("\n"),
|
||||
else => if (!nc.key.synthesized_p(keypress)) {
|
||||
var buf: [6]u8 = undefined;
|
||||
const bytes = try nc.ucs32_to_utf8(&[_]u32{egc}, &buf);
|
||||
try self.bracketed_paste_buffer.appendSlice(buf[0..bytes]);
|
||||
} else {
|
||||
try self.handle_bracketed_paste_end();
|
||||
return false;
|
||||
},
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn handle_bracketed_paste_end(self: *Self) tp.result {
|
||||
defer self.bracketed_paste_buffer.clearAndFree();
|
||||
if (!self.bracketed_paste) return;
|
||||
self.bracketed_paste = false;
|
||||
return tp.self_pid().send(.{ "system_clipboard", self.bracketed_paste_buffer.items });
|
||||
}
|
||||
|
||||
fn bracketed_paste_enable(self: *const Self) void {
|
||||
self.write_stdout(CSI_bracketed_paste_enable);
|
||||
}
|
||||
|
||||
fn bracketed_paste_disable(self: *const Self) void {
|
||||
self.write_stdout(CSI_bracketed_paste_disable);
|
||||
}
|
||||
|
||||
pub inline fn fg_channels_from_style(channels: *u64, style: Widget.Theme.Style) void {
|
||||
if (style.fg) |fg| {
|
||||
nc.channels_set_fg_rgb(channels, fg) catch {};
|
||||
nc.channels_set_fg_alpha(channels, nc.ALPHA_OPAQUE) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
pub inline fn bg_channels_from_style(channels: *u64, style: Widget.Theme.Style) void {
|
||||
if (style.bg) |bg| {
|
||||
nc.channels_set_bg_rgb(channels, bg) catch {};
|
||||
nc.channels_set_bg_alpha(channels, nc.ALPHA_OPAQUE) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
pub inline fn channels_from_style(channels: *u64, style: Widget.Theme.Style) void {
|
||||
fg_channels_from_style(channels, style);
|
||||
bg_channels_from_style(channels, style);
|
||||
}
|
||||
|
||||
pub inline fn set_cell_style(cell: *nc.Cell, style: Widget.Theme.Style) void {
|
||||
channels_from_style(&cell.channels, style);
|
||||
if (style.fs) |fs| switch (fs) {
|
||||
.normal => nc.cell_set_styles(cell, nc.style.none),
|
||||
.bold => nc.cell_set_styles(cell, nc.style.bold),
|
||||
.italic => nc.cell_set_styles(cell, nc.style.italic),
|
||||
.underline => nc.cell_set_styles(cell, nc.style.underline),
|
||||
.undercurl => nc.cell_set_styles(cell, nc.style.undercurl),
|
||||
.strikethrough => nc.cell_set_styles(cell, nc.style.struck),
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn set_cell_style_fg(cell: *nc.Cell, style: Widget.Theme.Style) void {
|
||||
fg_channels_from_style(&cell.channels, style);
|
||||
}
|
||||
|
||||
pub inline fn set_cell_style_bg(cell: *nc.Cell, style: Widget.Theme.Style) void {
|
||||
bg_channels_from_style(&cell.channels, style);
|
||||
}
|
||||
|
||||
pub inline fn set_base_style(plane: *const nc.Plane, egc: [*c]const u8, style: Widget.Theme.Style) void {
|
||||
var channels: u64 = 0;
|
||||
channels_from_style(&channels, style);
|
||||
if (style.fg) |fg| plane.set_fg_rgb(fg) catch {};
|
||||
if (style.bg) |bg| plane.set_bg_rgb(bg) catch {};
|
||||
_ = plane.set_base(egc, 0, channels) catch {};
|
||||
}
|
||||
|
||||
pub fn set_base_style_alpha(plane: nc.Plane, egc: [*:0]const u8, style: Widget.Theme.Style, fg_alpha: c_uint, bg_alpha: c_uint) !void {
|
||||
var channels: u64 = 0;
|
||||
if (style.fg) |fg| {
|
||||
nc.channels_set_fg_rgb(&channels, fg) catch {};
|
||||
nc.channels_set_fg_alpha(&channels, fg_alpha) catch {};
|
||||
}
|
||||
if (style.bg) |bg| {
|
||||
nc.channels_set_bg_rgb(&channels, bg) catch {};
|
||||
nc.channels_set_bg_alpha(&channels, bg_alpha) catch {};
|
||||
}
|
||||
if (style.fg) |fg| plane.set_fg_rgb(fg) catch {};
|
||||
if (style.bg) |bg| plane.set_bg_rgb(bg) catch {};
|
||||
_ = plane.set_base(egc, 0, channels) catch {};
|
||||
}
|
||||
|
||||
pub inline fn set_style(plane: *const nc.Plane, style: Widget.Theme.Style) void {
|
||||
var channels: u64 = 0;
|
||||
channels_from_style(&channels, style);
|
||||
plane.set_channels(channels);
|
||||
if (style.fs) |fs| switch (fs) {
|
||||
.normal => plane.set_styles(nc.style.none),
|
||||
.bold => plane.set_styles(nc.style.bold),
|
||||
.italic => plane.set_styles(nc.style.italic),
|
||||
.underline => plane.set_styles(nc.style.underline),
|
||||
.undercurl => plane.set_styles(nc.style.undercurl),
|
||||
.strikethrough => plane.set_styles(nc.style.struck),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_mode() []const u8 {
|
||||
return if (current().input_mode) |m| m.name else "INI";
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue